自定義 View【基礎入門篇】

在實際使用的過程中,我們經常會接到這樣一些需求,比如環形計步器,柱狀圖表,圓形頭像等等,這時我們通常的思路是去Google 一下,看看 github 上是否有我們需要的這些控件,但是如果網上收不到這樣的控件呢?這時我們經常需要自定義 View 來滿足需求。

 

 

 


接下來讓我們開啓自定義控件之路

關於自定義控件,一般輝遵循一下幾個套路

  • 首先重寫 onMeasure() 方法
  • 其次重寫 onDraw() 方法
  • 總所周知 onMeasure()

方法是用來重新測量,並設定控件的大小,我們知道控件的大小是用 width 和 height 兩個標籤來設定的。通常有三種賦值情況 :

  • 首先直接賦值,比如直接給定 15dp 這樣確切的大小
  • 其次 match_parent
  • 當然還有 wrap_parent

這時也許你就會有疑問,既然都已經有了這些屬性,那還重寫 onMeasure 幹嘛,直接調用 View 的方法不就行了嗎?但是你想想,比如你設計了一個圓形控件,用戶在 width 和 height 都設置了 wrap_parent 屬性,同時又給你傳了一張長方形的圖片,那結果會怎麼樣?必然得讓你“方”啊。。所以這時就需要重寫 onMeasure 方法,設定其寬高相等。


那麼該如何重寫 onMeasure() 方法呢?

首先把 onMeasure() 打出來

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
複製代碼

這時大家不眠會好奇,明明是重繪大小,那麼給我提供寬高就行了呀?這個 int widthMeasureSpec, int heightMeasureSpec ,是個什麼鬼?其實很好理解,大家都知道計算機中數據是已二進制存儲的。同時,就像我之前講的 View 的大小賦值形式有三種,那麼在計算機中,要存儲二進制數,需要幾位二進制呢,答案很明瞭 -> 兩位。同時大家也發現,這兩個參數都是 int 型的。int 型數據在計算機中用 32 位存儲。所以聰明的 Google 就把這 30 位劃分爲兩部分。第一部分兩位拿來存類型,後面 28 位拿來存數據大小。


開始重寫 onMeasure() 方法

首先,無論是 width 還是 height ,我們都得先判斷類型,再去計算大小,so~ 咱先寫個方法專門用於計算並返回大小。

測量模式 表示意思
UNSPECIFIED 父容器沒有對當前View有任何限制,當前View可以任意取尺寸
EXACTLY 當前的尺寸就是當前View應該取的尺寸
AT_MOST 當前尺寸是當前View能取的最大尺寸
   private int getMySize(int defaultSize, int measureSpec) {
        // 設定一個默認大小 defaultSize
        int mySize = defaultSize;
        // 獲得類型
        int mode = MeasureSpec.getMode(measureSpec);
        // 獲得大小
        int size = MeasureSpec.getSize(measureSpec);
        
        switch (mode) {
            case MeasureSpec.UNSPECIFIED: {//如果沒有指定大小,就設置爲默認大小
                mySize = defaultSize;
                break;
            }
            case MeasureSpec.AT_MOST: {//如果測量模式是最大取值爲size
                //我們將大小取最大值,你也可以取其他值
                mySize = size;
                break;
            }
            case MeasureSpec.EXACTLY: {//如果是固定的大小,那就不要去改變它
                mySize = size;
                break;
            }
        }
        return mySize;
    }
複製代碼

然後,我們再從 onMeasure() 中調用它

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 分別獲得長寬大小
        int width = getMySize(100, widthMeasureSpec);
        int height = getMySize(100, heightMeasureSpec);
 
        // 這裏我已圓形控件舉例
        // 所以設定長寬相等
        if (width < height) {
            height = width;
        } else {
            width = height;
        }
        // 設置大小
        setMeasuredDimension(width, height);
    }
複製代碼

在 xml 中應用試試效果

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="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:gravity="center"
    tools:context=".activities.MainActivity">
 
    <com.entry.android_view_user_defined_first.views.MyView
        android:layout_width="100dp"
        android:layout_height="100dp"
        app:default_size="@drawable/ic_launcher_background"/>
 
</LinearLayout>
複製代碼

到這裏圖就已經重繪出來了,讓我們運行一下下

我們驚呆了,說好的控件呢??! 別急,咱還沒給他上色呢,所以它自然是透明的。所以現在重寫 onDraw() 方法,在 onDraw() 方法中 (這裏我爲了寫的方便,在 onDraw 方法中直接 new 了對象 { 嗷我沒有對象} ,但這是一種很容易導致內存泄露的行爲,大家注意下評論區)

我們通過 canvas (安卓的一個繪圖類對象進行圖形的繪製)

    @Override
    protected void onDraw(Canvas canvas) {
        // 調用父View的onDraw函數,因爲View這個類幫我們實現了一些
        // 基本的而繪製功能,比如繪製背景顏色、背景圖片等
        super.onDraw(canvas);
        int r = getMeasuredWidth() / 2;//也可以是getMeasuredHeight()/2,本例中我們已經將寬高設置相等了
        Log.d(TAG, r + "");
        // 圓心的橫座標爲當前的View的左邊起始位置+半徑
        int centerX = r;
        // 圓心的縱座標爲當前的View的頂部起始位置+半徑
        int centerY = r;
        // 定義灰色畫筆,繪製圓形
        Paint bacPaint = new Paint();
        bacPaint.setColor(Color.GRAY);
        canvas.drawCircle(centerX, centerY, r, bacPaint);
        // 定義藍色畫筆,繪製文字
        Paint paint = new Paint();
        paint.setColor(Color.BLUE);
        paint.setTextSize(60);
        canvas.drawText("大傻瓜", 0, r+paint.getTextSize()/2, paint);
    }
複製代碼

運行一下

大功告成!但是善於思考的可能會發現:使用這種方式,我們只能使用父類控件的屬性,但是我們有時需要更多的功能,比如:圖片控件需要改變透明度,卡片控件需要設定陰影值等等,那麼父類控件的屬性顯然不夠用了,這時我們就要開始實現自定義佈局。

自定義佈局屬性 xml 屬性


開始

由於自定義佈局屬性一般只需要對 onDraw() 進行操作。所以 onMeasure() 等方法的重寫我就不再囉嗦了,這裏我打算繼承字 view 實現一個類似 TextView 的控件。

首先,讓我們現在 res/values/styles 文件中增加一個自定義佈局屬性。

<resources>
 
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>
 
    <!--定義屬性集合名-->
    <declare-styleable name="MyView">
        <!--我們定義爲 default_size 屬性爲 屈指類型 像素 dp 等-->
        <attr name="text_size" format="dimension"/>
        <attr name="text_color" format="color"/>
        <attr name="text_text" format="string"/>
    </declare-styleable>
 
</resources>
複製代碼

這些標籤都是什麼意思呢?

首先:

MyView 是自定義佈局屬性的名字,也就是標籤也就是入口,在 onDraw 中,用 context.obtainStyledAttributes(attrs, R.styleable.MyView); 獲得自定義佈局屬性的全部子項。

其次:

attr 中的 name 便是你屬性的名字,比如說這個 text_size 、text_color 、text_text  這三個屬性,在 佈局文件中就是:

    <com.entry.android_view_user_defined_first.views.MyView
        android:layout_width="100dp"
        android:layout_height="100dp"
        app:text_text="hello world"
        app:text_size="20sp"
        app:text_color="@color/colorAccent"/>
複製代碼

最後:

format 標籤,format 標籤指定的是數據類型,具體可以看這篇,我在這裏就不重複了 -> blog.csdn.net/pgalxx/arti…


解析和引用

上面我們先定義了屬性,又在佈局中對其賦值,那麼實際中,我們如何在自定義控件裏,獲得它的實際值呢?讓我們先寫下構造方法,在構造方法中獲得這些值的大小:

    private int textSize;
    private String textText;
    private int textColor;
 
    public MyView(Context context) {
        super(context);
    }
 
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
 
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyView);
 
        textSize = array.getDimensionPixelSize(R.styleable.MyView_text_size, 15);
        textText = array.getString(R.styleable.MyView_text_text);
        textColor = array.getColor(R.styleable.MyView_text_color,Color.BLACK);
 
        array.recycle();
    }
複製代碼
  • 建立一個 TypeArray 對象,用於存儲自定義屬性所傳入的的值。obtainStyledAttributes 方法又兩個參數,第二個參數就是我們在styles.xml文件中的 標籤,即屬性集合的標籤,在R文件中名稱爲R.styleable+name
  • 然後根據 array 對象,獲取傳入的值。一般來說,它的方法有兩個屬性,第一個參數爲屬性集合裏面的屬性,R文件名稱:R.styleable+屬性集合名稱+下劃線+屬性名稱,第二個參數爲,如果沒有設置這個屬性,則設置的默認的值
  • 最後記得將TypedArray對象回收

來重寫下 onDraw() 方法。

由於在構造方法中,我們已經獲得基本的值,所以在 onDraw() 中,將這些東西繪製出來就行了,這裏直接上代碼:

    @Override
    protected void onDraw(Canvas canvas) {
        // 調用父View的onDraw函數,因爲View這個類幫我們實現了一些
        // 基本的而繪製功能,比如繪製背景顏色、背景圖片等
        super.onDraw(canvas);
        int r = getMeasuredWidth() / 2;//也可以是getMeasuredHeight()/2,本例中我們已經將寬高設置相等了
        // 圓心的橫座標爲當前的View的左邊起始位置+半徑
        int centerX = r;
        // 圓心的縱座標爲當前的View的頂部起始位置+半徑
        int centerY = r;
        // 定義灰色畫筆,繪製圓形
        Paint bacPaint = new Paint();
        bacPaint.setColor(Color.GRAY);
        canvas.drawCircle(centerX, centerY, r, bacPaint);
        // 定義藍色畫筆,繪製文字
        Paint paint = new Paint();
        paint.setColor(textColor);
        paint.setTextSize(textSize);
        canvas.drawText(textText, 0, r+paint.getTextSize()/2, paint);
    }
複製代碼

運行一下下:perfect !


作者:_yuanhao
鏈接:https://juejin.im/post/5d0f4286e51d4577770e73a5
來源:掘金
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

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