Android自定義View

內容會很多哦。如果這節自定義View描述有什麼錯誤的地方,麻煩指正一下。
如果不懂的同學,希望你們耐心看完本章節。我也是剛入門的。好了,廢話不多說!!


Android自定義View 是什麼?

顧名思義,自定義View就是我們自己定義的View,並且能和用戶進行交互的控件。我們使用的button都是繼承view這個父類的。當安卓內置的View不足以滿足我們日常的開發需求的時候,我們就可以通過自定義View來實現自己想要的效果。比如如下圖所示,跳過顯示廣告網頁直接進入主界面的按鈕。
這裏寫圖片描述


涉及到的類和方法?

onDraw():是View顯示界面的方法,在這個方法的內部進行描繪你想要的效果。一般都會涉及到幾個比較重要的類。

  • Paint 畫筆類,設置畫筆的粗細,顏色等屬性

  • Canvas 畫布類,提供各種畫各種圖形的api

  • Bitmap 位圖類,用來獲取圖片的信息。

  • matrix 矩陣類,用來控制圖片的現實


View的繪製流程

假設一輛車的生產過程。

  • 測量車的框架大小,根據車的框架大小來測量車內飾大小的,比如座位等。onMeasure()

  • 開始定製,根據測量的尺寸來進行生產。onDraw()

  • 生產完成之後,將需要的東西填充到框架裏去。onLayout()

  • 最後,該車具備了走的功能了,我們就能使用它了。onTouch() 用於和用戶進行交互


初步使用?

首先我創建一個類,RectView類。這個類需要繼承自View。其次覆寫裏面的方法,在這裏我們覆寫前面兩種方法。這裏寫圖片描述


public class RectView extends View {

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

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

}

我們首先簡單的畫一個矩形。此時我們需要覆寫onDraw()方法。

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //創建一個矩形區域,裏面的數字分別代表  left,top,right,bottom
        RectF rectF = new RectF(0, 0, 100, 100);
        //創建一個畫筆
        Paint paint = new Paint();
        //用畫布畫出一個矩形
        canvas.drawRect(rectF, paint);
    }

然後添加一下xml的標籤,相當於添加一個button按鈕這樣。

 <com.example.does.customview.RectView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

然後Run,這樣矩形就畫出來了。

這裏寫圖片描述

onDraw(Canvas canvas)中的canvas相當於畫布,不瞭解的話可以百度一下。理解爲一張畫布就行了。Paint()這個就是創建一個畫筆,RectF()中的Rect代表的意思是矩形,F代表的float類型,可以Ctrl點進去看源碼就知道了。
Canvas這個類裏面有許多方法,比如畫一個圓。我們修改一下,

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //創建一個矩形區域,裏面的數字分別代表  left,top,right,bottom
        RectF rectF = new RectF(0, 0, 500, 500);
        //創建一個畫筆
        Paint paint = new Paint();
        //用畫布畫出一個圓
        canvas.drawOval(rectF,paint);
    }

效果如下,
這裏寫圖片描述

當然,我們還可以對其描繪的東西做其它屬性的修改,

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //創建一個矩形區域,裏面的數字分別代表  left,top,right,bottom
        RectF rectF = new RectF(10, 10, 500, 500);
        //創建一個畫筆
        Paint paint = new Paint();
        //抗鋸齒
        paint.setAntiAlias(true);
        //設置一個空心圓
        paint.setStyle(Paint.Style.STROKE);
        //設置畫筆的顏色
        paint.setColor(Color.BLUE);
        //設置外邊粗一點
        paint.setStrokeWidth(5);
        //用畫布畫出一個圓
        canvas.drawOval(rectF,paint);
    }

這裏寫圖片描述

具體更多的屬性,自己自定的過程中再摸索其裏面更多的方法。這裏再多做演示了。


自定義控件view覆寫方法的調用順序

添加需要實現的其它方法,並且Log日誌打印

public class RectView extends View {


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

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

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.i("does", "onDraw: ");
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.i("does", "onMeasure: ");
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        Log.i("does", "onLayout: ");
    }
}

運行,看輸出日誌

07-15 12:37:22.155 3907-3907/com.example.does.customview I/does: onMeasure:
07-15 12:37:22.328 3907-3907/com.example.does.customview I/does: onMeasure:
07-15 12:37:22.329 3907-3907/com.example.does.customview I/does: onLayout:
07-15 12:37:22.388 3907-3907/com.example.does.customview I/does: onMeasure:
07-15 12:37:22.388 3907-3907/com.example.does.customview I/does: onLayout:
07-15 12:37:22.388 3907-3907/com.example.does.customview I/does: onDraw:

顯示測量兩次,顯示,再測量,最後繪畫。
爲什麼這樣?因爲我們自定義View添加到xml佈局文件裏面去的,系統是先測量我們自定義控件的,寬度高度,然後再測量父控件的寬度高度,然後爲了保險一點,系統再次測量我們的自定義控件的高度,最後再畫出來。


onMeasure()方法講解

我們先用一個實例看看具體有什麼樣的效果。
我在onDraw()裏給canvas畫一個顏色,然後在xml裏面添加兩個寬高屬性,都是warp_content

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
       canvas.drawColor(Color.BLUE);
    }
 <com.example.does.customview.RectView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

運行,效果如下。

這裏寫圖片描述
如果我再修改xml的屬性,將高度改成50dp,出現的是這樣的效果,

 <com.example.does.customview.RectView
        android:layout_width="wrap_content"
        android:layout_height="50dp" />

這裏寫圖片描述

接着,我們在onMeasure方法裏添加一行代碼

  setMeasuredDimension(100,100);

最終的效果是這樣的
這裏寫圖片描述

神奇吧 ?!! 哈哈。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

1.widthMeasureSpec
2.heightMeasureSpec

Log日誌輸入看看分別是什麼值,將setMeasuredDimension(100,100);這行代碼註釋掉。

07-15 14:26:39.508 31129-31129/com.example.does.customview I/does: onMeasure: -2147482568
07-15 14:26:39.508 31129-31129/com.example.does.customview I/does: onMeasure: 1073741955

數值是什麼我們暫且不管它。

這兩個參數分別都是由兩個int類型構成的。我們創建一個變量抽離出來,

        int widethMode = MeasureSpec.getMode(widthMeasureSpec);
        int widethSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

打印log日誌,

07-15 14:35:35.704 3319-3319/com.example.does.customview I/does: heightMode: 1073741824 widthMode-2147483648
07-15 14:35:35.704 3319-3319/com.example.does.customview I/does: heightSize: 131 widthSize1080

數值我們也暫且不管它。

我們看看getMode裏面的源碼,源碼裏定義了三個變量,分別是

  1. AT_MOST 表示父控件對自定義控件的最大值
  2. EXACTLY 表示父控件對自定義控件的精確值
  3. UNSPECIFIED 表示父控件對自定義控件大小無限制 比如listview

    log打印輸出,

07-15 14:42:04.036 3319-3319/com.example.does.customview I/does: heightMode: 1073741824 widthMode-2147483648
07-15 14:42:04.036 3319-3319/com.example.does.customview I/does: heightSize: 131 widthSize1080
07-15 14:42:04.036 3319-3319/com.example.does.customview I/does: UNSPECIFIED : 0
07-15 14:42:04.036 3319-3319/com.example.does.customview I/does: AT_MOST : -2147483648
07-15 14:42:04.036 3319-3319/com.example.does.customview I/does: EXACTLY : 1073741824

爲了驗證這三個值,我們在onMeasure()方法裏添加switch語句。

  switch (widthMode){
            case MeasureSpec.UNSPECIFIED:
                Log.i("does", "UNSPECIFIED  : "+ MeasureSpec.UNSPECIFIED);
                break;
            case MeasureSpec.AT_MOST:
                Log.i("does", "AT_MOST     : "+ MeasureSpec.AT_MOST );
                break;
            case MeasureSpec.EXACTLY:
                Log.i("does", "EXACTLY      : "+ MeasureSpec.EXACTLY    );
                break;
            default:
                break;
        }

然後運行,發現輸出語句

07-16 01:19:06.835 5190-5190/com.example.does.customview I/does: heightMode: 1073741824 widthMode-2147483648
07-16 01:19:06.835 5190-5190/com.example.does.customview I/does: heightSize: 131 widthSize1080
07-16 01:19:06.835 5190-5190/com.example.does.customview I/does: AT_MOST : -2147483648

此時輸出的是AT_MOST的值。

如果我們將佈局文件裏,我們自定義屬性的寬高的wrap_content改成固定的值,輸出的則是EXACTLY。精確值嘛,有確定的值。

那UNSPECIFIED呢?如果我們的父控件改成用scrollview,那麼輸出的值便是UNSPECIFIED。


onLayout()方法講解

該方法是提供給ViewGroup來放置每個子控件的。通常我們需要配onMeasure來一起使用。
這個方法是每次佈局大小變化,或者位置改變的時候都會本調用。
onLayout(boolean changed, int l, int t, int r, int b) changed 這個控件是否更改位置或者尺寸了
l 相對於父控件的左邊的位置
t 相對於父控件的頂部的位置
r 相對於父控件的右邊的位置
b 相對於父控件的下部的位置
我們必須在這個方法內部將子控件有序的放置在合適的位置。View 提供了llayout(int l, int t, int r, int b)方法給我們。

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