在實際使用的過程當中,咱們常常會接到這樣一些需求,好比環形計步器,柱狀圖表,圓形頭像等等,這時咱們一般的思路是去Google 一下,看看 github 上是否有咱們須要的這些控件,可是若是網上收不到這樣的控件呢?這時咱們常常須要自定義 View 來知足需求。php
關於自定義控件,通常輝遵循一下幾個套路html
方法是用來從新測量,並設定控件的大小,咱們知道控件的大小是用 width 和 height 兩個標籤來設定的。一般有三種賦值狀況 :java
這時也許你就會有疑問,既然都已經有了這些屬性,那還重寫 onMeasure 幹嗎,直接調用 View 的方法不就好了嗎?可是你想一想,好比你設計了一個圓形控件,用戶在 width 和 height 都設置了 wrap_parent 屬性,同時又給你傳了一張長方形的圖片,那結果會怎麼樣?必然得讓你「方」啊。。因此這時就須要重寫 onMeasure 方法,設定其寬高相等。android
首先把 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
首先,不管是 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);
}
複製代碼
大功告成!可是善於思考的可能會發現:使用這種方式,咱們只能使用父類控件的屬性,可是咱們有時須要更多的功能,好比:圖片控件須要改變透明度,卡片控件須要設定陰影值等等,那麼父類控件的屬性顯然不夠用了,這時咱們就要開始實現自定義佈局。
因爲自定義佈局屬性通常只須要對 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);
}
複製代碼
本文是我對我的學習過程的總結,若是你們有發現錯誤,但願能在評論區指出,謝謝 🙏