標籤:
當我們開發中遇到原生的組件無法滿足需求時,我們這時候就應該寫自訂View來滿足一些特殊的組件需求。
自訂View
個人總結自訂View的概念分為兩種:
1、在同一個樣式的控制群組合多處要使用到,我們可以採用原生控制群組合一個View供其他地方調用,減少重複代碼。
舉個栗子:最常見的空白提示頁面,一般都是上面是表徵圖下面是文字就可以考慮以上方式。下面直接上代碼:
/***我們這裡因為是空白提示頁面,上下結構,所以我們選用的是LinearLayout*/<span style="font-size:18px;">public class NavigationView extends LinearLayout {private ImageView iv_nva;private TextView tv_nva_title;public NullDateView(Context context) {super(context);LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);inflater.inflate(R.layout.view_nulldate, this);//引用你自己寫的xml布局檔案tv_nva_title = (TextView) findViewById(R.id.tv_nva_title);iv_nva = (ImageView) findViewById(R.id.iv_nva);}/**這樣我們就可以傳入不同的參數,實現不同顯示的空白提示頁面了*/public void setData(int ivID,String title){tv_nva_title.setText(title);iv_nva.set.setImageResource(ivID);}}</span>
2、這種就比較相對複雜一些,不過這種方式更加的自訂,完全由你控制,控制項可以繪製成你想要的任何樣式。
一般的只需重寫兩個函數:onMeasure()、onDraw()。
onMeasure負責對當前View的尺寸進行測量。
onDraw負責把當前這個View繪製出來。
還得寫至少寫2個建構函式:
<span style="font-size:18px;">public MyView(Context context) { super(context);}public MyView(Context context, AttributeSet attrs) { super(context, attrs); }</span>
重寫OnMeasure方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
參數中的widthMeasureSpec和heightMeasureSpec包含寬和高的資訊、還包含測量模式。
也就是說,一個int整數,裡面放了測量模式和尺寸大小,接下來我們解釋一下為什麼一個int可以同時存放兩個資訊。
那麼Google是怎麼把一個int同時放測量模式和尺寸資訊呢?我們知道int型資料佔用32個bit,而google實現的是,將int資料的前面2個bit用於區分不同的配置模式,後面30個bit存放的是尺寸的資料。
那我們怎麼從int資料中提取測量模式和尺寸呢?放心,不用你每次都要寫一次移位<<和取且&操作,Android內建類MeasureSpec幫我們寫好了,我們只需按照下面方法就可以拿到啦:
int widthMode = MeasureSpec.getMode(widthMeasureSpec);int widthSize = MeasureSpec.getSize(widthMeasureSpec);
我們在設定寬高時有3個選擇:wrap_content、match_parent以及指定固定尺寸。
測量模式也有3種:UNSPECIFIED,EXACTLY,AT_MOST,這三種模式後面會詳細介紹。
UNSPECIFIED:父容器沒有對當前View有任何限制,當前View可以任意取尺寸
EXACTLY:當前的尺寸就是當前View應該取的尺寸
AT_MOST:當前尺寸是當前View能取的最大尺寸
例子:
如果我們要自訂一個View,並且是一個正方形的長寬都是100:
<span style="font-size:18px;">private int getMySize(int defaultSize, int measureSpec) { 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;}@Overrideprotected 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);}</span>
<span style="font-size:18px;"><com.ljw.MyView android:layout_width="match_parent" android:layout_height="100dp" android:background="#ff0000" /></span>
如果我們不重寫OnMeasure那麼他就會是寬鋪滿整個螢幕高為100dp,但是我們重寫了OnMeasure,並且設定了其寬高為100dp,所以你不置中設定的android:layout_width="match_parent"是沒用的。
重寫onDraw()
看方法名就知道,是繪製View的形狀。我們可以直接在畫板Canvas對象上繪製。
例子:
假設我們需要一個圓形View,結合我們上面重寫的onMeasure方法設定的寬高尺寸相等。
<span style="font-size:18px;">@Overrideprotected void onDraw(Canvas canvas) {//調用父View的onDraw函數,因為View這個類幫我們實現了一些// 基本的而繪製功能,比如繪製背景顏色、背景圖片等super.onDraw(canvas);int r = getMeasuredWidth() / 2;//也可以是getMeasuredHeight()/2,本例中我們已經將寬高設定相等了//圓心的橫座標為當前的View的左邊起始位置+半徑int centerX = getLeft() + r;//圓心的縱座標為當前的View的頂部起始位置+半徑int centerY = getTop() + r;Paint paint = new Paint();paint.setColor(Color.GREEN); //開始繪製canvas.drawCircle(centerX, centerY, r, paint);}</span>小結:以上兩個方法就可以實現自訂大小、自訂形狀的View了,當然我們真實的需求不僅僅是這些,我們還需要在布局檔案中加入自訂的屬性例如(android:layout_width)這樣的屬性。
自訂View的屬性attr
首先需要聲明自訂屬性,在res/values/styles.xml檔案裡面聲明(沒有該檔案請自行建立):
<span style="font-size:18px;"><resources> <!--name為聲明的"屬性集合"名,可以隨便取,但是最好是設定為跟我們的View一樣的名稱--> <declare-styleable name="MyView"> <!--聲明我們的屬性,名稱為default_size,取實值型別為尺寸類型(dp,px等)--> <attr name="default_size" format="dimension" /> </declare-styleable></resources></span>
屬性定義好,我們就可以直接在布局裡面直接使用了:
<span style="font-size:18px;"><?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:jw="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <com.ljw.MyView android:layout_width="match_parent" android:layout_height="100dp" jw:default_size="100dp" /></LinearLayout></span>
注意:需要在根標籤(LinearLayout)裡面設定命名空間,命名空間名稱可以隨便取,比如"jw",命名空間後面取得值是固定的:"http://schemas.android.com/apk/res-auto"
自訂的MyView裡的建構函式中,利用AttributeSet屬性把布局裡面的自訂屬性取出來:
private int defalutSize;public MyView(Context context, AttributeSet attrs) { super(context, attrs); //第二個參數就是我們在styles.xml檔案中的<declare-styleable>標籤,即屬性集合的標籤,在R檔案中名稱為R.styleable+name TypedArray tArr = context.obtainStyledAttributes(attrs, R.styleable.MyView); //第一個參數為屬性集合裡面的屬性,R檔案名稱:R.styleable+屬性集合名稱+底線+屬性名稱 //第二個參數為預設值 defalutSize = tArr.getDimensionPixelSize(R.styleable.MyView_default_size, 100); //最後記得將TypedArray對象釋放回收 a.recycle();}總結:綜合自訂View三點要素(重寫onMeasure()、onDraw()、自訂布局屬性)基本可以完成自訂View了!
以下是MyView的完整代碼:
/** * @Arthur Jovial * 2016-06-15 */public class MyView extends View { private int defalutSize; public MyView(Context context) { super(context); } public MyView(Context context, AttributeSet attrs) { super(context, attrs); //第二個參數就是我們在styles.xml檔案中的<declare-styleable>標籤,即屬性集合的標籤,在R檔案中名稱為R.styleable+name TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView); //第一個參數為屬性集合裡面的屬性,R檔案名稱:R.styleable+屬性集合名稱+底線+屬性名稱 //第二個參數為預設值 defalutSize = a.getDimensionPixelSize(R.styleable.MyView_default_size, 100); //將TypedArray對象釋放回收 a.recycle(); } private int getMySize(int defaultSize, int measureSpec) { 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; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = getMySize(defalutSize, widthMeasureSpec); int height = getMySize(defalutSize, heightMeasureSpec); if (width < height) { height = width; } else { width = height; } setMeasuredDimension(width, height); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas);//調用父類的onDraw函數,父類已經幫我們實現了一些比如繪製背景顏色、背景圖片等//也可以是getMeasuredHeight()/2,本例中我們已經將寬高設定相等了 int r = getMeasuredWidth() / 2; //圓心的橫座標為當前的View的左邊起始位置+半徑 int centerX = getLeft() + r; //圓心的縱座標為當前的View的頂部起始位置+半徑 int centerY = getTop() + r; Paint paint = new Paint(); paint.setColor(Color.GREEN); //開始繪製 canvas.drawCircle(centerX, centerY, r, paint); }}
總結:自訂View就講到這裡,相信大家也有所瞭解。其實並不複雜,看完這篇相信你也迫不及待想去嘗試繪製一些自己的View了吧!
自訂View瞭解了,下一篇我們將講解一下自訂ViewGroup!
淺談Android自訂View