思考:
我們使用Android組件的時候,大多數都是在xml中定義View,然後設置它的屬性,那麼的它的屬性又是怎麼被解析應用的呢?
我們又該怎樣去自定義屬性呢?
activity_main.xml 佈局文件代碼如下:
看到沒attrs 其實就是咱在定義xml中定義的屬性.通過觀察還可以發現,是不是 id獲取的爲@XXX,沒有得到正確的數值,這是因爲id爲引用類型,需要經過兩次處理,或者用TypedArray來處理(後面還會重點提到).
那麼com.android.internal.R.styleable.View 是什麼鬼呢?其實知道自定義屬性的幾個步驟的,就知道其中有一步是在attrs.xml中申明 自定義屬性:
比如說 name="mytv" 通過declare-styleable來申明的,會在R.java文件中,定義如下類屬性:
看到了系統自定義屬性,我們再回過頭來看看 View的構造方法中這樣一行代碼:
我們使用Android組件的時候,大多數都是在xml中定義View,然後設置它的屬性,那麼的它的屬性又是怎麼被解析應用的呢?
我們又該怎樣去自定義屬性呢?
View屬性解析過程認識
我們首先要知道,在解析XML的屬性並賦值的過程都是在該View的構造方法中體現的,比如就拿我們的View類來說,它的構造方法部分代碼如下: public View(Context context, AttributeSet attrs, int defStyleAttr) {
this(context);
TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View,
defStyleAttr, 0);
Drawable background = null;
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
switch (attr) {
case com.android.internal.R.styleable.View_background:
background = a.getDrawable(attr);
break;
case com.android.internal.R.styleable.View_padding:
padding = a.getDimensionPixelSize(attr, -1);
mUserPaddingLeftInitial = padding;
mUserPaddingRightInitial = padding;
leftPaddingDefined = true;
rightPaddingDefined = true;
break;
case com.android.internal.R.styleable.View_paddingLeft:
leftPadding = a.getDimensionPixelSize(attr, -1);
mUserPaddingLeftInitial = leftPadding;
leftPaddingDefined = true;
break;
case com.android.internal.R.styleable.View_paddingTop:
topPadding = a.getDimensionPixelSize(attr, -1);
break;
case com.android.internal.R.styleable.View_id:
mID = a.getResourceId(attr, NO_ID);
}
}
在這裏,從構造方法中用到了兩個東西:(1)參數 AttributeSet attrs (2)屬性com.android.internal.R.styleable.View 然後就是解析判斷賦值了。那麼我們先看第一 構造方法參數 attrs是什麼東西呢?下面我們通過一個MyTextView來說明:public class MyTextView extends TextView {
public MyTextView(Context context) {
this(context, null);
}
public MyTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
// TODO 自動生成的構造函數存根
}
public MyTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
for (int i = 0; i < attrs.getAttributeCount(); i++) {
String name = attrs.getAttributeName(i);
String value = attrs.getAttributeValue(i);
Log.i("name-value", name + " :" + value);
}
}
}
activity_main.xml 佈局文件代碼如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xiaotang="http://schemas.android.com/apk/res/com.example.test"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.example.test.MyTextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dip"
xiaotang:mabc="abc"
xiaotang:mcolor="@string/hello_world"
xiaotang:mtext="this is test" />
</RelativeLayout>
我們看到輸出結果爲:看到沒attrs 其實就是咱在定義xml中定義的屬性.通過觀察還可以發現,是不是 id獲取的爲@XXX,沒有得到正確的數值,這是因爲id爲引用類型,需要經過兩次處理,或者用TypedArray來處理(後面還會重點提到).
那麼com.android.internal.R.styleable.View 是什麼鬼呢?其實知道自定義屬性的幾個步驟的,就知道其中有一步是在attrs.xml中申明 自定義屬性:
<resources>
<declare-styleable name="mytv">
<attr name="android:text"></attr>
<attr name="mtext" format="string" />
<attr name="mcolor" format="reference" />
</declare-styleable>
<attr name="mabc" format="string"></attr>
</resources>
比如說 name="mytv" 通過declare-styleable來申明的,會在R.java文件中,定義如下類屬性:
public static final class styleable {
public static final int[] mytv = {
0x0101014f, 0x7f010000, 0x7f010001
};
public static final int mytv_android_text = 0;
public static final int mytv_mcolor = 2;
public static final int mytv_mtext = 1;
};
可以看到R.java中定義靜態屬性的特徵,定義一個 mytv數組。如果沒有通過 declare-styleable來申明,則需要自己來處理成這種數組形式了。那麼com.android.internal.R.styleable.View引用的就是一個名字爲 View的數組唄,所以一定在某個attrs.xml文件中定義了 <declare-styleable name="View">以及在Android的某個位置有R.java中包含 R.styleable.View,這些都是系統定義好的東西,
在sdk中 \sdk\platforms\android-19\data\res\values 我這是SDK 19,裏面就有attrs.xml文件,通過查看可以看到如下定義: <declare-styleable name="View">
<!-- Supply an identifier name for this view, to later retrieve it
with {@link android.view.View#findViewById View.findViewById()} or
{@link android.app.Activity#findViewById Activity.findViewById()}.
This must be a
resource reference; typically you set this using the
<code>@+</code> syntax to create a new ID resources.
For example: <code>android:id="@+id/my_id"</code> which
allows you to later retrieve the view
with <code>findViewById(R.id.my_id)</code>. -->
<attr name="id" format="reference" />
<!-- Supply a tag for this view containing a String, to be retrieved
later with {@link android.view.View#getTag View.getTag()} or
searched for with {@link android.view.View#findViewWithTag
View.findViewWithTag()}. It is generally preferable to use
IDs (through the android:id attribute) instead of tags because
they are faster and allow for compile-time type checking. -->
<attr name="tag" format="string" />
<!-- The initial horizontal scroll offset, in pixels.-->
<attr name="scrollX" format="dimension" />
<!-- The initial vertical scroll offset, in pixels. -->
<attr name="scrollY" format="dimension" />
<!-- A drawable to use as the background. This can be either a reference
to a full drawable resource (such as a PNG image, 9-patch,
XML state list description, etc), or a solid color such as "#ff000000"
(black). -->
<attr name="background" format="reference|color" />
<!-- Sets the padding, in pixels, of all four edges. Padding is defined as
space between the edges of the view and the view's content. A views size
will include it's padding. If a {@link android.R.attr#background}
is provided, the padding will initially be set to that (0 if the
drawable does not have padding). Explicitly setting a padding value
will override the corresponding padding found in the background. -->
<attr name="padding" format="dimension" />
<!-- Sets the padding, in pixels, of the left edge; see {@link android.R.attr#padding}. -->
<attr name="paddingLeft" format="dimension" />
<!-- Sets the padding, in pixels, of the top edge; see {@link android.R.attr#padding}. -->
<attr name="paddingTop" format="dimension" />
<!-- Sets the padding, in pixels, of the right edge; see {@link android.R.attr#padding}. -->
<attr name="paddingRight" format="dimension" />
<!-- Sets the padding, in pixels, of the bottom edge; see {@link android.R.attr#padding}. -->
<attr name="paddingBottom" format="dimension" />
<!-- Sets the padding, in pixels, of the start edge; see {@link android.R.attr#padding}. -->
<attr name="paddingStart" format="dimension" />
<!-- Sets the padding, in pixels, of the end edge; see {@link android.R.attr#padding}. -->
<attr name="paddingEnd" format="dimension" />
此處只是粘貼了小部分View的定義,是不是看到了裏面關於View組件的系統屬性定義了!哈哈,看到了系統屬性定義,那麼自定義屬性的操作也就差不多羅!看到了系統自定義屬性,我們再回過頭來看看 View的構造方法中這樣一行代碼:
TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View,
defStyleAttr, 0);
傳遞了attrs(我們在xml中使用到的屬性),com.android.internal.R.styleable.View 系統給View定義的屬性,那麼這個操作時幹嘛呢?其實猜也猜的出來了,無非就是將attrs中的屬性值整合到系統屬性中,如果沒有,則取默認值!自定義屬性
步驟:
(1)編寫values/attrs.xml文件,在裏面編寫styleable和item元素標籤
(2)在佈局文件中,定義 命名空間和使用自定義屬性
(3)在自定義View的構造方法中使用TypedArray解析自定義屬性
下面我們一一講解這些步驟的用法,對於步驟(1),使用代碼如下:
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<declare-styleable name="mytext">
<attr name="mtext" format="string" />
<attr name="mColor" format="reference" />
<attr name="android:text"/></attr>
</declare-styleable>
</resources>
在values下面新建 attrs.xml文件,添加declare-styleable申明,可以添加自定義屬性,也可以是系統屬性賦值於新的含義,通過看有沒有format屬性判斷。上面也提到過,申明瞭declare-styleable會在R.java文件中生成如下代碼:public static final class styleable {
public static final int[] mytext = {
0x0101014f, 0x7f010000, 0x7f010001
};
public static final int mytext_android_text = 0;
public static final int mytext_mColor = 2;
public static final int mytext_mtext = 1;
};
public MyText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray typeArray = context.obtainStyledAttributes(attrs,
R.styleable.mytext);
String text = typeArray.getString(R.styleable.mytext_mtext);
int color = typeArray.getColor(R.styleable.mytext_mColor, 0xf00);
this.setText(text);
this.setBackgroundColor(color);
}
通過 R.styleable.mytext,R.styleable.mytext_mtext來引用自定義的屬性.當然如果我們沒有在attrs.xml中使用declare-styleable申明時,而是如下:<?xml version="1.0" encoding="UTF-8"?>
<resources>
<attr name="mtitle" format="string"/>
<attr name="mage" format="integer"/>
</resources>
那麼這樣時,我們就需要在自定義View中自己定義數組了,因爲context.obtainStyledAttributes()參數中需要傳遞數組。代碼如下:public class MyText extends TextView {
private int attrsId[] = {R.attr.mage,R.attr.mtitle};
public MyText(Context context) {
this(context, null);
}
public MyText(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray typeArray = context.obtainStyledAttributes(attrs,
attrsId);
int age=typeArray.getInt(0, 0); //0 代表 attrsId索引的值
String title = typeArray.getString(1);
}
這裏還需要提高一個東西就是 TypedArray和AttributeSet的區別:前面也已經提高過,AttributeSet能夠獲取佈局文件中所有的參數和值的,但是引用類型的內容時獲取不了的,比如:
android:text="@string/text" text引用strings.xml文件中的內容是不能一步獲取到的,就需要使用TypedArray了,具體操作如上。
我們再來看看佈局文件中使用自定義屬性的用法吧:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xiaotang="http://schemas.android.com/apk/res/com.example.test"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.example.test.MyText
android:id="@+id/mytext"
android:layout_width="200dip"
android:layout_height="100dip"
xiaotang:mColor="@color/mycolor"
xiaotang:mtext="fdsafdsas" />
</LinearLayout>
在佈局文件中申明命名空間 xmlns:xiaotang=http://schemas.android.com/apk/res/包名,然後就可以調用了自定義屬性啦!
到這裏,我們自定義屬性就討論完了。歡迎大家和我討論學習,一起提高哦!