在實際使用的過程中,我們經常會接到這樣一些需求,比如環形計步器,柱狀圖表,圓形頭像等等,這時我們通常的思路是去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
來源:掘金
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。