Android學(xué)習(xí)筆記---自定義View#01

最近發(fā)現(xiàn)自己對Android的學(xué)習(xí)只在表面,并沒有深入的理解,我不喜歡這種感覺,而且沒有自己的理解,學(xué)習(xí)到的內(nèi)容也很難為我所用.

所以從本次開始,我要寫點(diǎn)自己理解的東西,但要對知識有自己的理解,那就必須深入了解它的原理.而我覺得Android的自定義View是一個很好的入口.

學(xué)會如何自定義View,能夠了解Android系統(tǒng)中View使如何創(chuàng)建和維護(hù)的.這有助于我們學(xué)習(xí)Android的View的基本機(jī)制,也能解決我們?nèi)粘i_發(fā)的需求.

接下來我們一起來對自定義View中的各個部分做詳細(xì)的研究,現(xiàn)在我們先從View的構(gòu)造函數(shù)入手.

自定義View的構(gòu)造函數(shù)

自定義View最基礎(chǔ)的方式就是創(chuàng)建一個繼承于View或其子類的類,并且最少重寫父類的一個構(gòu)造函數(shù).

/**
 */
public class MyView extends View {
    /**
     * 在代碼中使用new關(guān)鍵字創(chuàng)建View時會調(diào)用
     * @param context
     */
    public MyView(Context context) {
        super(context);
    }

    /**
     * 在layout文件聲明View時會調(diào)用,只有實(shí)現(xiàn)了這個構(gòu)造函數(shù)才能在布局文件中聲明此自定義View
     * @param context
     * @param attrs 存有View在xml布局文件中的自定義的屬性
     */
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * 與第二個構(gòu)造函數(shù)不同的地方在于,此構(gòu)造函數(shù)可以指定View的默認(rèn)樣式
     * @param context
     * @param attrs
     * @param defStyleAttr 是當(dāng)前theme中包含的指向View的樣式資源的屬性(0代表此參數(shù)無效)
     */
    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}

第一個構(gòu)造函數(shù)比較簡單,我們從第二個構(gòu)造函數(shù)開始研究.
當(dāng)我們需要在xml布局文件中使用我們的View時,我們就必須實(shí)現(xiàn)第二個構(gòu)造函數(shù).第二個構(gòu)造函數(shù)的參數(shù)列表為MyView(Context context, AttributeSet attrs),這里的context不用多說,就是View所在的上下文,而第二個參數(shù)就是AttributeSet類型的一個Set集合.它是一個保存了View的自定義屬性的集合,即我們在xml布局文件中為View所設(shè)置的屬性可以通過這個參數(shù)獲取.
通常我們會在res/values/文件下創(chuàng)建一個attrs.xml文件來聲明我們的自定義屬性.該文件是一個資源文件.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyView">
        <attr name="myview_text" format="string"/>
    </declare-styleable>
</resources>

上面我們在attrs.xml中定義了我們的自定義屬性,其中<declare-styleable>標(biāo)簽表示一組自定義的屬性,對應(yīng)著一個View的自定義屬性.其中的name可以是任何值,但為了方便和規(guī)范,建議name與對應(yīng)的View同名.
<attr>標(biāo)簽就是一個具體的屬性了,它的name代表該屬性的名字.format代表該屬性的值的格式.者兩個是必須要有的.<attr>標(biāo)簽支持的formatstring,enum,boolean,dimension,color,float...等多種格式.

有了自定義的屬性后,在xml布局文件中使用View時就能使用我們的自定義屬性了.但我們需要在使用自定義屬性前,指定自定義屬性的命名空間,這樣系統(tǒng)才能準(zhǔn)確的找到你的自定義屬性.
而它們的命名空間為http://schemas.android.com/apk/res/[your package name],這里不同的View可能會有不同的package name,這樣一來就比較麻煩.但是在Android Studio中我們只需指定一個統(tǒng)一的命名空間即可http://schemas.android.com/apk/res-auto.剩下的AS會幫我們完成.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <com.source.kevin.costomviewlib.costomview.MyView
        app:myview_text="Hello"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</RelativeLayout>

為了驗(yàn)證第二個參數(shù)AttributeSet能否得到我們的自定義屬性,我們在第二個構(gòu)造函數(shù)中測試一下.

    /**
     * 在layout文件聲明View時會調(diào)用,只有實(shí)現(xiàn)了這個構(gòu)造函數(shù)才能在布局文件中聲明此自定義View
     * @param context
     * @param attrs 存有View在xml布局文件中的自定義的屬性
     */
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView);
        try{
            String string = a.getString(R.styleable.MyView_myview_text);
            Log.e("RESULT",string);
        }finally {
            a.recycle();//回收資源
        }
    }

先運(yùn)行一下應(yīng)用,然后在控制臺的Log中我們看到了輸出


我們很簡單的就得到了自定義的屬性.代碼中我們使用了context.obtainStyledAttributes(AttributeSet set, @StyleableRes int[] attrs)這個函數(shù)獲取我們的自定義屬性集合.返回的是一個TypeArray類型的對象,這個對象包含了View的自定義屬性.第1個參數(shù)便是要解析的AttributeSet,第2個參數(shù)int[] attrs就是我們attrs.xml文件中定義的一組屬性資源.其實(shí)我們可以打開自動生成的R.java文件,在文件中搜索MyView關(guān)鍵字,定位到相應(yīng)的行數(shù),我們可以看到下面的代碼:

....
public static final class attr {
....
    public static final int myview_text=0x7f0100a7;
....
}
....
public static final class styleable {
....
    public static final int[] MyView = {
            0x7f0100a7
    };
    public static final int MyView_myview_text = 0;
....
}

可以看出R.styleable.MyView是一個int型數(shù)組,里面的內(nèi)容是與R.attr.myview_text相對應(yīng)的.這就表明了R.style.MyView是一個存放了attrs.xml文件中聲明的一組自定義屬性的id集合.而R.styleable.MyView_myview_text則是一個屬性在數(shù)組中對應(yīng)的下標(biāo)索引.
如果還是存在疑惑,可以在attrs.xmlMyView屬性節(jié)點(diǎn)下多添加幾個自定義的屬性,然后重新編譯代碼,按照我上面的方法查看相應(yīng)的代碼,我相信你能夠明白其中的關(guān)系.

我們可以點(diǎn)到context.obtainStyledAttributes(AttributeSet set, @StyleableRes int[] attrs)這個函數(shù)的源碼中去,發(fā)現(xiàn)這個函數(shù)調(diào)用的是context.getTheme().obtainStyledAttributes(set, attrs, 0, 0)這個函數(shù).可以看到這是一個4個參數(shù)的函數(shù),它的函數(shù)原型如下:

/**
*
* @param set 需要解析的屬性集合
* @param attrs 屬性集合對應(yīng)的id資源數(shù)組
* @param defStyleAttr 當(dāng)前Theme中包含的一個指向style樣式的引用.
*                     當(dāng)我們沒有設(shè)置自定義屬性時,默認(rèn)會從該集合中查找布局文件的屬性配置值(0代表不向defStyleAttr查找屬性默認(rèn)值)
* @param defStyleRes 也是一個指向Style的資源ID
*                    當(dāng)defStyleAttr==0 或 defStyleAttr!=0 但Theme中沒有為defStyleAttr賦值,該參數(shù)才起作用.
* @return 
*/
public TypedArray obtainStyledAttributes(AttributeSet set,@StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes)

代碼中的注釋已經(jīng)說的很清楚,其中第3個參數(shù)和第4個參數(shù)分別與View的3參數(shù)的構(gòu)造函數(shù)中的第3個參數(shù)和4參數(shù)構(gòu)造函數(shù)中的第4個參數(shù)的意義是一樣的.最后當(dāng)我們使用完了TypeArray,我們需要將它回收,因?yàn)樗且粋€共享的資源.
由上面的函數(shù)就可以看出屬性可以在很多的地方進(jìn)行賦值,包括: XML布局文件中、decalare-styleable、theme中等,它們之間是有優(yōu)先級次序的,按照優(yōu)先級從高到低排序如下:

在布局xml中直接定義 > 在布局xml中通過style定義 > 自定義View所在的Activity的Theme中指定style引用 > 構(gòu)造函數(shù)中defStyleRes指定的默認(rèn)值

下面我們來嘗試一下如何從第三個參數(shù)獲取屬性值.首先我們在attr.xml文件中添加一個單獨(dú)的屬性MyViewDefStyleAttr,格式為reference,就是資源引用類型.并在MyView屬性組下添加一個屬性.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyView">
        <attr name="myview_text" format="string"/>
        <attr name="myview_attr" format="string"/>
    </declare-styleable>
    <attr name="MyViewDefStyleAttr" format="reference"/>
</resources>

接下來我們修改style.xml文件,添加一個自定義的style,作為MyViewDefStyleAttr的實(shí)現(xiàn),并在AppTheme style下引用它.

<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>
        <item name="MyViewDefStyleAttr">@style/MyViewDefStyleaAttrImpl</item>
    </style>

    <style name="MyViewDefStyleaAttrImpl">
        <item name="myview_attr">attr</item>
    </style>

</resources>

然后我們就可以在第三個構(gòu)造函數(shù)中獲取自定義屬性.

    /**
     * 在layout文件聲明View時會調(diào)用,只有實(shí)現(xiàn)了這個構(gòu)造函數(shù)才能在布局文件中聲明此自定義View
     * @param context
     * @param attrs 存有View在xml布局文件中的自定義的屬性
     */
    public MyView(Context context, AttributeSet attrs) {
        this(context, attrs,R.attr.MyViewDefStyleAttr);

    }

    /**
     * 與第二個構(gòu)造函數(shù)不同的地方在于,此構(gòu)造函數(shù)可以指定View的默認(rèn)樣式
     * @param context
     * @param attrs
     * @param defStyleAttr 是當(dāng)前theme中包含的指向View的樣式資源的屬性(0代表此參數(shù)無效)
     */
    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyView, defStyleAttr, 0);
        try {
            String s = a.getString(R.styleable.MyView_myview_attr);
            Log.e("RESULT",s);
        }finally {
            a.recycle();
        }

    }

這里我們通過兩個參數(shù)的構(gòu)造函數(shù)調(diào)用3參數(shù)的構(gòu)造函數(shù),并傳入R.attr.MyViewDefStyleAttr作為默認(rèn)樣式屬性值資源,在第三個構(gòu)造函數(shù)中,若要獲取第3個參數(shù)的默認(rèn)屬性值,必須通過顯式的調(diào)用context.getTheme().obtainStyledAttributes(attrs,R.styleable.MyView,defStyleAttr, 0)這個4個參數(shù)的obtainStyledAttributes()函數(shù).我們運(yùn)行代碼后得到的相應(yīng)的結(jié)果:

通過對View的構(gòu)造函數(shù)的研究,基本了解了View在創(chuàng)建時是通過什么方式獲取自定義屬性的,并且也知道了該如何實(shí)現(xiàn)View的自定義屬性.

參考

Android View構(gòu)造函數(shù)詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容