在實際使用的過程當中,咱們常常會接到這樣一些需求,好比環形計步器,柱狀圖表,圓形頭像等等,這時咱們一般的思路是去Google 一下,看看 github 上是否有咱們須要的這些控件,可是若是網上收不到這樣的控件呢?這時咱們常常須要自定義 View 來知足需求。html
關於自定義控件,通常輝遵循一下幾個套路java
方法是用來從新測量,並設定控件的大小,咱們知道控件的大小是用 width 和 height 兩個標籤來設定的。一般有三種賦值狀況 :android
這時也許你就會有疑問,既然都已經有了這些屬性,那還重寫 onMeasure 幹嗎,直接調用 View 的方法不就好了嗎?可是你想一想,好比你設計了一個圓形控件,用戶在 width 和 height 都設置了 wrap_parent 屬性,同時又給你傳了一張長方形的圖片,那結果會怎麼樣?必然得讓你「方」啊。。因此這時就須要重寫 onMeasure 方法,設定其寬高相等。git
首先把 onMeasure() 打出來github
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } 複製代碼
這時你們不眠會好奇,明明是重繪大小,那麼給我提供寬高就好了呀?這個 int widthMeasureSpec, int heightMeasureSpec ,是個什麼鬼?其實很好理解,你們都知道計算機中數據是已二進制存儲的。同時,就像我以前講的 View 的大小賦值形式有三種,那麼在計算機中,要存儲二進制數,須要幾位二進制呢,答案很明瞭 -> 兩位。同時你們也發現,這兩個參數都是 int 型的。int 型數據在計算機中用 32 位存儲。因此聰明的 Google 就把這 30 位劃分爲兩部分。第一部分兩位拿來存類型,後面 28 位拿來存數據大小。canvas
首先,不管是 width 仍是 height ,咱們都得先判斷類型,再去計算大小,so~ 咱先寫個方法專門用於計算並返回大小。markdown
測量模式 | 表示意思 |
---|---|
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); } 複製代碼
大功告成!可是善於思考的可能會發現:使用這種方式,咱們只能使用父類控件的屬性,可是咱們有時須要更多的功能,好比:圖片控件須要改變透明度,卡片控件須要設定陰影值等等,那麼父類控件的屬性顯然不夠用了,這時咱們就要開始實現自定義佈局。
因爲自定義佈局屬性通常只須要對 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(); } 複製代碼
因爲在構造方法中,咱們已經得到基本的值,因此在 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); } 複製代碼
本文是我對我的學習過程的總結,若是你們有發現錯誤,但願能在評論區指出,謝謝 🙏