自定義view

目標:

1、掌握自定義view的流程
2、掌握自定義view的三個方法
3、掌握自定義view實現(xiàn)方式
4、掌握自定義view屬性設(shè)置

一.xml的實質(zhì)

  1. xml不是必須,布局可以代碼寫
  2. xml是為了開發(fā)者開發(fā)布局便利,谷歌給開發(fā)者開發(fā)糖
  3. xml最終還是會轉(zhuǎn)成代碼執(zhí)行

二.View和ViewGroup

  1. View

1.1 View是用戶界面一個組件(控件)
1.2 View是一個矩形
1.3.View的職責是繪制和事件處理

  1. ViewGroup

2.1 ViewGroup是一個特殊的View,它繼承自View
2.2 ViewGroup可包含其他View(孩子)
2.3.ViewGroup常用layout的基類
2.4 ViewGroup定義了孩子的布局參數(shù)(帶layout_前綴的屬性)

3.View和ViewGroup的關(guān)系
繼承關(guān)系

image

組合關(guān)系

image

三.什么是自定義控件?

  1. 原生控件:SDK已經(jīng)有,Google提供
  2. 自定義控件: 開發(fā)者自己開發(fā)的控件,分三種
    a. 組合式控件:將現(xiàn)有控件進行組合,實現(xiàn)功能更加強大控件;
    b. 繼承現(xiàn)有控件: 對其控件的功能進行拓展;
    c. 重寫View(ViewGroup)實現(xiàn)全新的控件.

四.為什么要自定義View?
原有控件無法滿足我們的需求,所以需要自己實現(xiàn)想要的效果。

五.組合式控件 下拉選擇框
模塊化思想,提高代碼復(fù)用率

  1. 功能分析:
    a. 點擊箭頭,彈出下拉列表 (Popupwindow + ListView)
    b. 點擊列表選項,在編輯框中顯示內(nèi)容

  2. 實現(xiàn)步驟:
    a.繼承布局,重寫構(gòu)造;
    b.創(chuàng)建布局xml,加入到自定義控件里面;

  3. 實現(xiàn)對應(yīng)的功能.
    xml布局:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <EditText
        android:id="@+id/et"
        android:layout_margin="10dp"
        android:layout_width="match_parent"
        android:layout_height="50dp" />

    <ImageView
        android:id="@+id/iv"
        android:layout_alignParentRight="true"
        android:layout_marginRight="10dp"
        android:layout_marginTop="10dp"
        android:src="@drawable/down_arrow"
        android:layout_width="50dp"
        android:layout_height="50dp" />
</RelativeLayout>

public class SpinnerView extends RelativeLayout {

    private EditText mEt;
    private ImageView mIv;
    private ArrayList<String> mData;
    private PopupWindow mPop;

    public SpinnerView(Context context) {
        super(context);
    }

    //必須有的構(gòu)造
    public SpinnerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        //需要將寫的布局添加到這個自定義view中
        LayoutInflater.from(getContext()).inflate(R.layout.view_spinner, this);
        initView();
        initData();
    }

    private void initData() {
        mData = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            mData.add("數(shù)據(jù)"+i);
        }
    }

    private void initView() {
        mEt = findViewById(R.id.et);
        mIv = findViewById(R.id.iv);

        mIv.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                showPop();
            }
        });
    }

    private void showPop() {
        //判斷是否為空,實現(xiàn)PopupWindow復(fù)用
        if (mPop == null){
            mPop = new PopupWindow(mEt.getWidth(),400);
            ListView listView = new ListView(getContext());
            //listView.setBackgroundResource(R.drawable.listview_background);
            listView.setAdapter(new ArrayAdapter<String>(getContext(),android.R.layout.simple_list_item_1,mData));
            listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    String s = mData.get(position);
                    mEt.setText(s);
                    //將光標移動到某個位置
                    mEt.setSelection(s.length());
                    mPop.dismiss();
                }
            });
            //PopupWindow三要素之View
            mPop.setContentView(listView);
            mPop.setBackgroundDrawable(new ColorDrawable());
            mPop.setOutsideTouchable(true);
            mPop.setFocusable(true);
        }
        mPop.showAsDropDown(mEt);
    }
}

六.繼承現(xiàn)有控件
實現(xiàn)帶有刪除線的TextView

public class DeleteTextview extends TextView {
    public DeleteTextview(Context context) {
        super(context);
    }

    public DeleteTextview(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public DeleteTextview(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setDeleteLine(){
        getPaint().setFlags(Paint.STRIKE_THRU_TEXT_FLAG|Paint.ANTI_ALIAS_FLAG);
    }
}

七.View的繪制流程

a、Measure測量一個View的大小 (onMeasure)

b、Layout擺放一個View的位置 (onLayout)

c、Draw畫出View的顯示內(nèi)容 (onDraw)

其中measure是final的,無法重寫,雖然layout,draw不是final的,但是也不建議重寫該方法。
這三個方法都已經(jīng)寫好了View的邏輯,如果我們想實現(xiàn)自身的邏輯,而又不破壞View的工作流程,可以重寫onMeasure、onLayout、onDraw方法。

     /**
     * 代碼操作的時候,直接new對象
     * @param context
     */
    public CView(Context context) {
        super(context);
    }

    /**
     * 布局文件中設(shè)置屬性
     * @param context
     * @param attrs
     */
    public CView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * 自定義屬性
     * @param context
     * @param attrs
     * @param defStyleAttr
     */
    public CView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

7.1 View的測量(measure)

measure()--->onMeasure(實際測量工作)--->setMeasuredDimension(保存測量好的寬高)-->setMeasuredDimensionRaw(設(shè)置寬高)

Android系統(tǒng)在繪制View之前,必須對View進行測量,即告訴系統(tǒng)該畫一個多大的View,這個過程在onMeasure()方法中進行。

MeasureSpec類(onMeasure)

Android系統(tǒng)給我們提供了一個設(shè)計小而強的工具類--MeasureSpec類

1、MeasureSpec描述了父View對子View大小的期望。里面包含了測量模式和大小。

2、MeasureSpec類把測量模式和大小組合到一個32位的int型的數(shù)值中,其中高2位表示模式,低30位表示大小而在計算中使用位運算的原因是為了提高并優(yōu)化效率。

3、我們可以通過以下方式從MeasureSpec中提取模式和大小,該方法內(nèi)部是采用位移計算。

也可以通過MeasureSpec的靜態(tài)方法把大小和模式合成,該方法內(nèi)部只是簡單的相加。

  • 測量模式 -- 在對View進行測量時,Android提供了三種測量模式:
  1. EXACTLY
    即 精確值模式 ,當控件的layout_width屬性或layout_height屬性指定為具體數(shù)值時,例如android:layout_width="100dp",或者指定為match_parent屬性時,系統(tǒng)使用的是EXACTLY 模式。

  2. AT_MOST
    即 最大值模式 ,當控件的layout_width屬性或layout_height屬性指定為warp_content時,控件大小一般隨著控件的子控件或者內(nèi)容的變化而變化,此時控件的尺寸只要不超過父控件允許的最大尺寸即可。

  3. UNSPECIFIED
    這個屬性很奇怪,因為它不指定其大小測量的模式,View想多大就多大,通常情況下在繪制自定義View時才會使用。

  • View默認的onMeasure()方法只支持EXACTLY模式,所以如果在自定義控件的時候不重寫onMeasure()方法的話,就只能使用EXACTLY模式,且控件只可以響應(yīng)你指定的具體寬高值或者是match_parent屬性。如果要讓自定義的View支持wrap_content屬性,那么就必須重寫onMeasure()方法來指定wrap_content時的大小。
    而通過上面介紹的MeasureSpec這個類,我們就可以獲取View的測量模式和View想要繪制的大?。梢源蛴∪罩精@取)。

7.2View的布局(layout)

layout布局流程圖:

image

layout()
a. layout方法中接受四個參數(shù),是由父View提供,指定了子View在父View中的左、上、右、下的位置。父View在指定子View的位置時通常會根據(jù)子View在measure中測量的大小來決定。

b. 子View的位置通常還受有其他屬性左右,例如父View的orientation,gravity,自身的margin等等,特別是RelativeLayout,影響布局的因素非常多。

c. layout方法雖然可以被復(fù)寫,但是不建議去復(fù)寫,我們可以直接調(diào)用layout方法去確定自身的位置, 而且可以去復(fù)寫onLayout方法去確定子view的位置

setFrame()

a. setFrame方法是一個隱藏方法,所以作為應(yīng)用層程序員來說,無法重寫該方法。該方法體內(nèi)部通過比對本次的l、t、r、b四個值與上次是否相同來判斷自身的位置和大小是否發(fā)生了改變。

b. 如果發(fā)生了改變,將會調(diào)用invalidate請求重繪。

c. 記錄本次的l、t、r、b,用于下次比對。

d. 如果大小發(fā)生了變化,onSizeChanged方法,該方法在大多數(shù)View中都是空實現(xiàn),我們可以重寫該方法用于監(jiān)聽View大小發(fā)生變化的事件,在可以滾動的視圖中重載了該方法,用于重新根據(jù)大小計算出需要滾動的值,以便顯示之前顯示的區(qū)域。

onLayout()

a. onLayout是ViewGroup用來決定子View擺放位置的,各種布局的差異都在該方法中得到了體現(xiàn)。

b. onLayout比layout多一個參數(shù),changed,該參數(shù)是在setFrame通過比對上次的位置得出是否發(fā)生了變化,通常該參數(shù)沒有被使用的意義,因為父View位置和大小不變,并不能代表子View的位置和大小沒有發(fā)生改變。

7.3View的繪制(draw)

draw方法繪制要遵循一定的順序:

1.畫背景

2.畫邊緣

3.畫自身: ondraw方法

4.畫子View: dispatchDraw方法

6.畫滾動條

draw繪制流程:

image

draw()

draw是由ViewRoot的performTraversals方法發(fā)起,它將調(diào)用DecorView的draw方法,并把成員變量canvas傳給給draw方法。而在后面draw遍歷中,傳遞的都是同一個canvas。所以android的繪制是同一個window中的所有View都繪制在同一個畫布上。等繪制完成,將會通知WMS把canvas上的內(nèi)容繪制到屏幕上。自定義View時一般不重寫該方法。

onDraw()

a. View用來繪制自身的實現(xiàn)方法,如果我們想要自定義View,通常需要重載該方法。

b. 比如TextView中在該方法中繪制文字、光標和CompoundDrawable,

ImageView中相對簡單,只是繪制了圖片

八.繪制實戰(zhàn)

  1. 畫直線
int startX = 5;
int startY = 100;
int stopX = 195;
int stopY = 100;
canvas.drawLine(startX, startY, stopX, stopY, mPaint);

  1. 畫圓
canvas.drawCircle(100, 100, 40, mPaint);

3.畫空心圓

 //還是畫圓,改動畫筆即可
mPaint.setAntiAlias(true);//去鋸齒
mPaint.setStyle(Style.STROKE);//改變style可以畫出空心圓
mPaint.setStrokeWidth(5);
mPaint.setColor(Color.GREEN);

  1. 畫圖片
canvas.drawBitmap(mBitmap, 0, 0, mPaint);

  1. 畫三角形(多邊形)
mPath = new Path();
//規(guī)劃三角形的路徑
int x1 = 100, y1 = 5;
int x2 = 195, y2 = 195;
int x3 = 5, y3 = 195;

mPath.moveTo(x1, y1);
//連接第一個點和第二個點
mPath.lineTo(x2, y2);
mPath.lineTo(x3, y3);
mPath.lineTo(x1, y1);
canvas.drawPath(mPath, mPaint);

6.裁剪

canvas.clipPath(mPath);//先裁切再畫別的東西,比如圖片

  1. 畫扇形
mOval = new RectF(5, 5, 195, 195);
int startAngle = -90;
int sweepAngle = 60;
boolean useCenter = false;//是否畫扇形的兩條邊
canvas.drawArc(mOval, startAngle, sweepAngle, useCenter, mPaint);

九.ViewGroup的繪制流程
ViewGroup繼承View, ViewGroup的繪制流程遵循View的繪制的流程

  1. ViewGroup的測量
    相同點:measure - > onMeasure
    不同點:作為一個父容器,有責任去測量孩子,調(diào)用孩子measure方法,傳入期望

  2. ViewGroup的布局
    相同點:layout(ViewGroup父容器發(fā)起布局)
    不同點:作為一個父容器,有責任去布局孩子,在onLayout方法里面,調(diào)用孩子layout方法,指定孩子上下左右的位置

  3. ViewGroup的繪制
    相同點:draw -> onDraw
    不同點:ViewGroup默認實現(xiàn)dispatchDraw方法去繪制了孩子

  4. getWidth和getMeasuredWidth的區(qū)別
    a. getMeasuredWidth:獲取測量后寬高
    b. getWidth:獲取布局之后的寬高

十.案例

1.小圓球

  • 實現(xiàn)的功能:手指在屏幕上滑動,紅色的小球始終跟隨手指移動。

  • 實現(xiàn)的思路:
    1)自定義View,在onDraw中畫圓作為小球;
    2)重寫自定義View的onTouchEvent方法,記錄觸屏坐標,用新的坐標重新繪制小球;
    3)在布局中引用自定義View布局,運行程序,實現(xiàn)跟隨手指移動效果。

  • 關(guān)鍵技術(shù)點:自定義View應(yīng)用、觸摸事件處理、canvas繪圖、Paint應(yīng)用。

public class BallView extends View {
    private final Bitmap mBitmap;
    private final Paint mPaint;
    private float mMoveX = 60;
    private float mMoveY = 60;

    public BallView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ball);
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setAntiAlias(true);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //1.畫小球(圖片) 使用觸摸時的位置作為左上角
        //canvas.drawBitmap(mBitmap, mMoveX, mMoveY, mPaint);
        //2.畫圓,使用了觸摸屏幕時的位置作為圓心
        canvas.drawCircle(mMoveX,mMoveY,60,mPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //獲取觸摸屏幕時觸摸點的位置
        mMoveX = event.getX();
        mMoveY = event.getY();
        //重新繪制界面
        invalidate();
        return true;
    }
}

2.圓環(huán)進度條

  • 對于自定義view,很多時候需要使用到自定義屬性,我們向?qū)崿F(xiàn)一個view的自定義屬性,需要遵循以下幾部:

a.自定義一個CustomView(extends View )類
b.編寫values/attrs.xml,在其中編寫styleable和item等標簽元素
c.在布局文件中CustomView使用自定義的屬性(注意namespace)
導(dǎo)入自定義屬性,以下兩種方式都可(namespace)

d.在CustomView的構(gòu)造方法中通過TypedArray獲取

2.1 AttributeSet與TypedArray

構(gòu)造方法中的有個參數(shù)叫做AttributeSet(eg: MyTextView(Context context, AttributeSet attrs) )這個參數(shù)看名字就知道包含的是參數(shù)的集合,那么我能不能通過它去獲取我的自定義屬性呢?

首先AttributeSet中的確保存的是該View聲明的所有的屬性,并且外面的確可以通過它去獲?。ㄗ远x的)屬性,怎么做呢?

其實看下AttributeSet的方法就明白了,下面看備注1的代碼及打印結(jié)果。

public MyTextView(Context context, AttributeSet attrs) {
        super(context, attrs);

        int count = attrs.getAttributeCount();
        for (int i = 0; i < count; i++) {
            String attrName = attrs.getAttributeName(i);
            String attrVal = attrs.getAttributeValue(i);
            Log.e(TAG, "attrName = " + attrName + " , attrVal = " + attrVal);
        }

        // ==>use typedarray ...

    }

通過AttributeSet獲取的值,如果是引用都變成了@+數(shù)字的字符串。你說,這玩意你能看懂么?那么你看看最后一行使用TypedArray獲取的值,是不是瞬間明白了什么。

TypedArray其實是用來簡化我們的工作的,比如上例,如果布局中的屬性的值是引用類型(比如:@dimen/dp100),如果使用AttributeSet去獲得最終的像素值,那么需要第一步拿到id,第二步再去解析id。而TypedArray正是幫我們簡化了這個過程。

貼一下:如果通過AttributeSet獲取最終的像素值的過程:

int widthDimensionId = attrs.getAttributeResourceValue(0, -1);
Log.e(TAG, "layout_width= "+getResources().getDimension(widthDimensionId));

ok,現(xiàn)在別人問你TypedArray存在的意義,你就可以告訴他了。

代碼:

values/attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyProgressBar">
        <!--使用系統(tǒng)的寬高屬性-->
        <attr name="android:layout_width"/>
        <attr name="android:layout_height"/>
        <!--自定義的-->
        <!--外環(huán)顏色-->
        <attr name="ringColor" format="color"/>
        <!--外環(huán)寬度-->
        <attr name="ringWidth" format="dimension"/>
        <!--內(nèi)圓顏色-->
        <attr name="circleColor" format="color"/>
        <!--進度文本大小顏色-->
        <attr name="android:textSize"/>
        <attr name="android:textColor"/>
        <!--進度 扇形的掃過角度-->
        <attr name="sweepAngle" format="integer"/>
        <attr name="startAngle" format="integer"/>
    </declare-styleable>
</resources>

自定義View:

public class MyProgressBar extends View {
    private static final String TAG = "MyProgressBar";
    private float mHeight;
    private float mWidth;
    private int mRingColor;
    private float mRingWidth;
    private int mCircleColor;
    private float mTextSize;
    private int mTextColor;
    private int mStartAngle;
    private int mSweepAngle;
    private float mRadius;
    private float mCenterXY;
    private Paint mCirclePaint;
    private Paint mTextPaint;
    private String mProgress;
    private RectF mRectF;
    private Paint mSweepPaint;
    private final float mDy;

    public MyProgressBar(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        /*for (int i = 0; i < attrs.getAttributeCount(); i++) {
            String attributeName = attrs.getAttributeName(i);
            //如果是引用類型的話,attributeValue就變成了@+數(shù)字的字符串
            String attributeValue = attrs.getAttributeValue(i);
            //getResources().getColor(attributeValue)
            //getResources().getString(attributeValue)
            Log.d(TAG, "MyProgressBar: "+"attributeName:"+attributeName+",attributeValue"+attributeValue);
        }*/
        //系統(tǒng)提供的一個幫助類,可以直接過去里面的屬性值,就算是引用類型的,也可以直接獲取
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyProgressBar);
        if (ta != null){
            //獲取屬性
            mHeight = ta.getDimension(R.styleable.MyProgressBar_android_layout_height, 200);
            mWidth = ta.getDimension(R.styleable.MyProgressBar_android_layout_width, 200);
            mRingColor = ta.getColor(R.styleable.MyProgressBar_ringColor, 0);
            mRingWidth = ta.getDimension(R.styleable.MyProgressBar_ringWidth, 10);
            mCircleColor = ta.getColor(R.styleable.MyProgressBar_circleColor, 0);
            mTextSize = ta.getDimension(R.styleable.MyProgressBar_android_textSize, 16);
            mTextColor = ta.getColor(R.styleable.MyProgressBar_android_textColor, 0);
            mStartAngle = ta.getInteger(R.styleable.MyProgressBar_startAngle, -90);
            mSweepAngle = ta.getInteger(R.styleable.MyProgressBar_sweepAngle, 0);

            ta.recycle();//釋放資源
        }

        mCirclePaint = new Paint();
        mCirclePaint.setColor(mCircleColor);
        mCirclePaint.setAntiAlias(true);

        mTextPaint = new Paint();
        mTextPaint.setColor(mTextColor);
        mTextPaint.setTextSize(mTextSize);
        mTextPaint.setTextAlign(Paint.Align.CENTER);

        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
        float v1 = fontMetrics.descent - fontMetrics.ascent;
        mDy = v1/2-fontMetrics.descent;

        float v = mSweepAngle * 1.0f / 360 * 100;
        mProgress = (int)v+" %";

        mSweepPaint = new Paint();
        mSweepPaint.setAntiAlias(true);
        mSweepPaint.setColor(mRingColor);
        mSweepPaint.setStrokeWidth(mRingWidth);
        mSweepPaint.setStyle(Paint.Style.STROKE);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int max = (int) Math.max(mWidth, mHeight);
        setMeasuredDimension(max,max);
        //setMeasuredDimension((int) mWidth,(int)mHeight);
        mRadius = max*1.0f/4;
        mCenterXY = max *1.0f/2;

        float v = max * 0.9f;
        mRectF = new RectF(max*0.1f, max*0.1f,v , v);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //1.話里面的圓
        canvas.drawCircle(mCenterXY,mCenterXY,mRadius,mCirclePaint);
        //2.畫文字
        canvas.drawText(mProgress,mCenterXY,mCenterXY+mDy,mTextPaint);

        //3.畫進度,扇形
        canvas.drawArc(mRectF,mStartAngle,mSweepAngle,false,mSweepPaint);
    }

    /**
     *
     * @param progress 0-100
     */
    public void setProgress(int progress) {
        mSweepAngle =(int)(progress*3.6f) ;
        mProgress = progress+" %";
        invalidate();//只能在ui線程中調(diào)用
        //postInvalidate();// 可以在非ui線程中調(diào)用
    }
}

布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:xts="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.edu.cumulus.balldemo.MainActivity">

    <com.edu.cumulus.balldemo.MyProgressBar
        android:id="@+id/myProgressBar"
        android:background="#55000000"
        xts:circleColor="@color/colorAccent"
        xts:ringColor="@color/colorPrimary"
        xts:ringWidth="20dp"
        xts:sweepAngle="89"
        xts:startAngle="-90"
        android:text="@string/app_name"
        android:textSize="20sp"
        android:textColor="#ffffff"
        android:layout_width="300dp"
        android:layout_height="300dp" />

    <Button
        android:id="@+id/btn"
        android:text="開始"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

MainActivity使用,點擊按鈕,模擬下載:

  private void start() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i <= 100; i++) {
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    mMyProgressBar.setProgress(i);
                }
            }
        }).start();
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 目標: 1、掌握自定義view的流程2、掌握自定義view的三個方法3、掌握自定義view實現(xiàn)方式4、掌握自定義v...
    Anwfly閱讀 1,880評論 2 7
  • 目標: 1、掌握自定義view的流程2、掌握自定義view的三個方法3、掌握自定義view實現(xiàn)方式4、掌握自定義v...
    煙刺痛了眼_a1b7閱讀 415評論 0 0
  • 做為一名Android開發(fā)者,自定義View應(yīng)該是我們工作中繞不開的話題,畢竟系統(tǒng)提供的View有限,有時很難滿足...
    SheHuan閱讀 1,928評論 13 55
  • 一:自定義View繪制流程函數(shù)調(diào)用鏈 二.幾個重要的函數(shù) 1.構(gòu)造函數(shù) 構(gòu)造函數(shù)是View的入口,可以用于初始化一...
    請叫我財迷閱讀 1,842評論 0 3
  • 文/滄海一笑 晨光微煦,大作的鈴聲敲醒了很想懶覺的你。 每日緊張的早上就像一場速戰(zhàn),洗漱整裝早點一個都不能少。 在...
    滄海一笑_閱讀 713評論 6 16

友情鏈接更多精彩內(nèi)容