看完這篇還不會自定義 View ,我跪搓衣板

自定義 View

在實際使用的過程當中,咱們常常會接到這樣一些需求,好比環形計步器,柱狀圖表,圓形頭像等等,這時咱們一般的思路是去Google 一下,看看 github 上是否有咱們須要的這些控件,可是若是網上收不到這樣的控件呢?這時咱們常常須要自定義 View 來知足需求。php


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

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

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

方法是用來從新測量,並設定控件的大小,咱們知道控件的大小是用 width 和 height 兩個標籤來設定的。一般有三種賦值狀況 :java

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

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


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

首先把 onMeasure() 打出來git

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

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


開始重寫 onMeasure() 方法

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

測量模式 表示意思
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() 中調用它app

@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 中應用試試效果ide

<?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 !

寫在最後

本文是我對我的學習過程的總結,若是你們有發現錯誤,但願能在評論區指出,謝謝 🙏

相關文章
相關標籤/搜索