自定義View快速入門(一)

思路

首先,爲什麼需要自定義View?

  1. 現有的View滿足不了你的需求,也沒有辦法從已有控件派生一個出來;界面元素需要自己繪製。
  2. 現有View可以滿足要求,把它做成自定義View只是爲了抽象:爲這個自定義View提供若干方法,方便調用着操縱View。通常做法是派生一個已有View,或者結合xml文件直接inflate。

目前常用的基本上是第二種方式,這種方式非常簡單,與通常的View使用方法基本相同,但是作用卻異常強大,擁有了這一層抽象,代碼更加整潔也更容易維護,通過抽取自定義View的公共操作方法也減少了冗餘代碼,雖然簡單,但不可忽視。大多數人感覺神祕的應該是第一種,自繪控件,完全自定義;但其實這兩種方式歸根結底全部都是自繪;不信你去看看TextView的源碼。只不過通常情況下系統幫我們繪製好了一些控件給開發者使用;OK,接下來就是一個問題。在講述之前我還是囉嗦地重申一下,複用已有View是最最常用也最有效的自定義View方式,必須熟練使用。

然後,瞭解自定義控件步驟

  1. 寫一個自定義控件類
  2. 在res/values下建一個attrs.xml文件,增加對控件的自定義屬性的定義
  3. 使用兩個參數的構造方法,將變量與attrs.xml文件中的屬性連接起來
  4. 在佈局中使用屬性
  5. 將自定義控件帶到xml中

最後,需要知道自定義控件的兩種方式

  1. 繼承ViewGroup, 例如:ViewGroup、LinearLayout、FrameLayout、RelativeLayout等
  2. 繼承View, 例如:View、TextView、ImageView、Button等。

自定義離不開基本繪製原理:

View的繪製基本上是由onMeasure()、onLayout()、onDraw()這三個函數完成

1.)測量(使用頻率高)-Measure過程是計算視圖大小,View measure過程相關方法主要有三個:

public final void measure(int widthMeasureSpec, int heightMeasureSpec)  
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)  
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  

measure調用onMeasure,onMeasure測量寬度、高度然後調用setMeasureDimension保存測量結果,measure,setMeasureDimension是final類型,view的子類不需要重寫,onMeasure在view的子類中重寫。

關於MeasureSpec:
(1) UPSPECIFIED :父容器對於子容器沒有任何限制,子容器想要多大就多大,對於控件尺寸來說,沒有任何參考意義
(2) EXACTLY父容器已經爲子容器設置了尺寸,子容器應當服從這些邊界,不論子容器想要多大的空間,代表的是精確的尺寸
(3) AT_MOST子容器可以是聲明大小內的任意大小,代表的是最大可獲得控件

2.)佈局-Layout過程用於設置視圖在屏幕中顯示的位置,會決定具體View的大小和位置,View layout過程相關方法主要要三個:

public void layout(int l, int t, int r, int b)
protected boolean setFrame(int left, int top, int right, int bottom)
protected void onLayout(boolean changed, int left, int top, int right, int bottom)

layout通過調用setFrame(l,t,r,b),l,t,r,b即子視圖在父視圖中的具體位置,onLayout一般只會在自定義ViewGroup中才會使用

3.)繪製-draw過程主要用於利用前兩步得到的參數,將視圖顯示在屏幕上,到這裏也就完成了整個的視圖繪製工作

canvas:畫布 paint:畫筆

public void draw(Canvas canvas)
protected void onDraw(Canvas canvas)

通過調用draw函數進行視圖繪製,在View類中onDraw函數是個空函數,最終的繪製需求需要在自定義的onDraw函數中進行實現,比如ImageView完成圖片的繪製,如果自定義ViewGroup這個函數則不需要重載。


自定義控件示例:

這裏先介紹繼承View的方式爲例,其實ViewGroup最終的繼承的也是View。現在模擬一個需求場景,製作一個圓形。

public class WidgetZk extends View {
    private static final String TAG = "WidgetZk";
    private int mColor;

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

    public WidgetZk(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.WidgetZk);
        mColor= array.getColor(R.styleable.WidgetZk_color, Color.BLUE);

        array.recycle();//重複使用
        Log.e(TAG, "attrsSize調用自定義" + mColor+"色");

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(widthMeasureSpec);

        switch (widthMode) {
            case MeasureSpec.EXACTLY:

                break;
            case MeasureSpec.AT_MOST:

                break;
            case MeasureSpec.UNSPECIFIED:

                break;
        }
        Log.e(TAG, "onMeasure--widthMode-->" + widthMode);
        Log.e(TAG, "onMeasure--widthSize-->" + widthSize);
        Log.e(TAG, "onMeasure--heightMode-->" + heightMode);
        Log.e(TAG, "onMeasure--heightSize-->" + heightSize);
    }
/** 
 * 當這個view和其子view被分配一個大小和位置時,被layout調用。 
 * @param changed 當前View的大小和位置改變了 
 * @param left 左部位置(相對於父視圖) 
 * @param top 頂部位置(相對於父視圖) 
 * @param right 右部位置(相對於父視圖) 
 * @param bottom 底部位置(相對於父視圖) 
 */ 
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        Log.e(TAG, "onLayout ");
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        Paint paint = new Paint();
        //設置自定義顏色,在xml中動態添加
        paint.setColor(mColor);
        //畫圓
        canvas.drawCircle(width / 2, height / 2, width/4, paint);
        Log.e(TAG, "onDraw");
    }
}

attrs.xml 最快了解自定義屬性attrs

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="WidgetZk">
        <!--添加屬性-->
        <attr name="color" format="color" />
    </declare-styleable>
</resources>

在佈局中使用

   <com.zk.customwidget.WidgetZk
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        zkbilian:color="@color/colorAccent" />

顯示效果

這裏寫圖片描述


到這裏已經講解完畢,謝謝大家花了這麼長事件來看我這篇文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章