Android必知必會之自定義組件

一 概述

自定義組件是Android工程師必須瞭解並且經常會使用的知識點,本文就是對該知識點的簡單總結。

具體而言,自定義組件有三種方式:

  1. 繼承現有組件,拓展其功能

  2. 組合現有組件,實現模板化

  3. 直接繼承View,重寫onDraw方法,進行重繪

    直接繼承ViewGroup(或其子類),重寫onLayout/onMeasure方法,進行自定義佈局

二 繼承現有組件,拓展其功能

步驟如下:

  1. 根據要實現的效果,找到功能相近的組件

  2. 繼承該組件,實現自有功能

    具體而言,以下幾個方法需要注意,

    構造方法

    首先,如果要在XML文件中直接使用自定義組件,需要實現具有兩個參數的構造方法。

    在構造方法中,主要實現獲取XML佈局文件中寫入的自定義屬性的值。

    自定義屬性的方法如下:

    在res/values目錄下定義attrs.xml文件,該文件中定義了自定義組件支持的自定義屬性。

    具體寫法爲:

    在resources標籤下加入declare-styleable標籤,並給其一個標籤名,一般選擇自定義類名作爲標籤名;

    然後,在後面自定義屬性,以標籤attr包裹,內容包括自定義屬性名name與自定義屬性的類型format。

    形式如下所示:

    <resources>
        <declare-styleable name="一般爲自定義組件類名">
            <attr name="自定義屬性名" format="">
            <attr name="自定義屬性名" format="">
            ... ...
        </declare-styleable>
    </resources>
    

    這就定義了自定義組件的自定義屬性,之後就可以在XML佈局文件中使用這些自定義屬性了。

    使用的時候,要在xml文件中寫入xmlns,即xml命名空間,形式爲:

    xmlns:custom="http://schemas.android.com/apk/res-auto"

    或:

    xmlns:custom="http://schemas.android.com/apk/manifest中的package名"

    之後就可以“custom:自定義屬性名”來使用自定義屬性了。

    在構造方法中,通過操作TypedArray來獲取自定義屬性內容,代碼如下:

    TypedArray options = context.obtainStyledAttributes(attrs,
    						R.styleable.PaintDoubleWordsTextView, 0, 0);
     
    int numOfOptions = options.getIndexCount();
     
    for (int i = 0; i < numOfOptions; ++i) {
        int index = options.getIndex(i);
        switch (i) {
            case R.styleable.PaintDoubleWordsTextView_word0 :
                word0 = options.getString(index);
                break;
            case R.styleable.PaintDoubleWordsTextView_word0Size :
                word0Size = options.getInt(index, DEFAULT_TEXT_SIZE);
                break;
            case R.styleable.PaintDoubleWordsTextView_word1 :
                word1 = options.getString(index);
                break;
            case R.styleable.PaintDoubleWordsTextView_word1Size :
                word1Size = options.getInt(index, DEFAULT_TEXT_SIZE);
                break;
        }
    }
    options.recycle(); // 勿忘
    

    onDraw()

    onDraw() 方法的參數是一個Canvas對象,通過該對象即可實現自定義組件的內容繪製。

    invalidate()

    觸發重繪,onDraw。

    requestLayout()

    觸發整個繪製流程,onMeasure/onLayout/onDraw。

三 組合組件,模板化使用

這種自定義組件的方式並不創建新的組件,只是將已有組件進行組合,然後將組合好的整體當做模板來使用。

關鍵方法:

LayoutInflater.from(Context).inflate(自定義佈局id, 爲加載好的佈局指定父佈局);

通過以上關鍵代碼來將自定義佈局載入程序中,之後就可以通過findViewById來獲取自定義佈局文件中的組件了。

步驟如下:

  1. 根據需求組合組件

    根據需求對組件進行組合,構成模板。

    此處主要是實現一個XML佈局文件。

  2. 拓展Layout類

    實現Layout類的子類,在其具有兩個參數的構造方法中加載上一步實現的佈局文件,然後獲取其中定義的組件。

  3. 控制組件顯示,實現事件響應

    根據需求,操作上一步獲取的組件,控制組件顯示並實現具體的事件響應。

四 繼承View & ViewGroup

繼承View與繼承ViewGroup是不同的。繼承View是要自定義組件,而繼承ViewGroup則要自定義佈局

對於自定義組件而言,所需要考慮比較少,只有兩點需要注意:

  1. 對於Touch事件的處理

    根據事件處理機制,當重寫了onTouch方法後,要注意返回true之後會導致onTouchEvent方法不再執行,進而影響到click的執行與滑動的效果;同時,還要注意對ACTION的DOWN處理後返回false時,會導致後續的UP、MOVE事件無法獲取,也就是會阻止事件的層級傳遞。

  2. 重寫onDraw方法繪製需要的UI

    根據Android繪製原理,自定義組件只需要重寫onDraw方法即可,在其中使用所提供的Canvas畫布繪製所需要的效果。至於測量與佈局,自定義組件不需要考慮太多,這是由佈局組件來考慮的問題。

    實際上,View類中的measure方法是一個final方法,子類無法重寫;對於子類,只需要重寫真正進行測量的onMeasure方法即可。

    View類的onMeasure實現裏,只是去調用了setMeasuredDimension這個final方法去保存了組件的寬、高信息;直接繼承自View的TextView則重寫了onMeasure方法去根據padding等信息來計算寬、高數據。

    View類中的layout方法則直接在註釋中說明,子類不需要重寫該方法,具有子組件的子類,需要重寫onLayout方法,在其中調用子組件的layout方法

    代碼上看,View類中的onLayout方法是一個空方法,TextView中也並沒有重寫layout方法(因爲不需要),所重寫的onLayout方法也沒有進行重要的操作。

    所以,在自定義View組件時,爲了實現較精細的測量,一般需要重寫onMeasure方法,對於onLayout方法,一般不用實現

至於自定義佈局,就要考慮對於測量與佈局的處理問題

  1. 測量

    這一步根據Android繪製原理,需要重寫onMeasure方法,在其中獲取各個子組件的dimension,計算出佈局的dimension,然後調用setMeasuredDimension方法設置好佈局的尺寸。

    ViewGroup中沒有重寫onMeasure方法,子類必須自己進行實現。

  2. 佈局

    這一步需要重寫onLayout方法,在其中調用各個子組件的layout方法,讓子組件完成對自己的佈局。

    在ViewGroup中,onLayout方法是一個抽象方法,自定義的佈局必須實現該方法,在其中對子組件的layout方法進行調用。而ViewGroup中的layout方法是一個final方法,不能被重寫,其中則簡單的調用了View的layout方法(super.layout())。

    關於View的layout方法,一般在進行繼承View自定義組件時不需要重寫,至於View類中的onLayout方法,是一個空方法,不進行任何操作。

    至於繪製,則由ViewRootImpl類中的performTraversals方法調用performDraw進而調用組件的onDraw方法實現,不需要在ViewGroup中考慮。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章