淺談Android自訂View

來源:互聯網
上載者:User

標籤:

當我們開發中遇到原生的組件無法滿足需求時,我們這時候就應該寫自訂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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.