一直有個問題就是,Android中是如何通過佈局文件,就能實現控件效果的不同呢?比如在佈局文件中,我設置了一個TextView,給它設置了textColor,它就能夠改變這個TextView的文本的顏色。這是如何做到的呢?我們分3個部分來看這個問題1.attrs.xml 2.styles.xml 3.看組件的源碼。
1.attrs.xml:
我們知道Android的源碼中有attrs.xml這個文件,這個文件實際上定義了所有的控件的屬性,就是我們在佈局文件中設置的各類屬性
你可以找到attrs.xml這個文件,打開它,全選,右鍵->Show In->OutLine。可以看到整個文件的解構
下面是兩個截圖:
我們大概可以看出裏面是Android中的各種屬性的聲明,比如textStyle這個屬性是這樣定義的:
- <!-- Default text typeface style. -->
- <attr name="textStyle">
- <flag name="normal" value="0" />
- <flag name="bold" value="1" />
- <flag name="italic" value="2" />
- </attr>
那麼現在你知道,我們在寫android:textStyle的時候爲什麼會出現normal,bold和italic這3個東西了吧,就是定義在這個地方。
再看看textColor:
- <!-- Color of text (usually same as colorForeground). -->
- <attr name="textColor" format="reference|color" />
format的意思是說:這個textColor可以以兩種方式設置,要麼是關聯一個值,要麼是直接設置一個顏色的RGB值,這個不難理解,因爲我們可以平時也這樣做過。
也就是說我們平時在佈局文件中所使用的各類控件的屬性都定義在這裏面,那麼這個文件,除了定義這些屬性外還定義了各種具體的組件,比如TextView,Button,SeekBar等所具有的各種特有的屬性
比如SeekBar:
- <declare-styleable name="SeekBar">
- <!-- Draws the thumb on a seekbar. -->
- <attr name="thumb" format="reference" />
- <!-- An offset for the thumb that allows it to extend out of the range of the track. -->
- <attr name="thumbOffset" format="dimension" />
- </declare-styleable>
也許你會問SeekBar的background,等屬性怎麼沒有看到?這是因爲Android中幾乎所有的組件都是從View中繼承下來的,SeekBar自然也不例外,而background這個屬性幾乎每個控件都有,因此被定義到了View中,你可以在declare-styleable:View中找到它。
總結下,也就是說attrs.xml這個文件定義了佈局文件中的各種屬性attr:***,以及每種控件特有的屬性declare-styleable:***
2.styles.xml:
剛纔的attrs.xml定義的是組件的屬性,現在要說的style則是針對這些屬性所設置的值,一些默認的值。
這個是SeekBar的樣式,我們可以看到,這裏面設置了一個SeekBar的默認的樣式,即爲attrs.xml文件中的各種屬性設置初始值
- <style name="Widget.SeekBar">
- <item name="android:indeterminateOnly">false</item>
- <item name="android:progressDrawable">@android:drawable/progress_horizontal</item>
- <item name="android:indeterminateDrawable">@android:drawable/progress_horizontal</item>
- <item name="android:minHeight">20dip</item>
- <item name="android:maxHeight">20dip</item>
- <item name="android:thumb">@android:drawable/seek_thumb</item>
- <item name="android:thumbOffset">8dip</item>
- <item name="android:focusable">true</item>
- </style>
這個是Button的樣式:
- <style name="Widget.Button">
- <item name="android:background">@android:drawable/btn_default</item>
- <item name="android:focusable">true</item>
- <item name="android:clickable">true</item>
- <item name="android:textAppearance">?android:attr/textAppearanceSmallInverse</item>
- <item name="android:textColor">@android:color/primary_text_light</item>
- <item name="android:gravity">center_vertical|center_horizontal</item>
- </style>
有了屬性和值,但是這些東西是如何關聯到一起的呢?它們如何被android的framework層所識別呢?
3.組件的源碼
我們看下TextView的源碼:
- public TextView(Context context) {
- this(context, null);
- }//這個構造器用來給用戶調用,比如new TextView(this);
- public TextView(Context context,
- AttributeSet attrs) {
- this(context, attrs, com.android.internal.R.attr.textViewStyle);
- }
- public TextView(Context context,
- AttributeSet attrs,
- int defStyle) {
- super(context, attrs, defStyle);//爲用戶自定義的TextView設置默認的style
- mText = "";
- //設置畫筆
- mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
- mTextPaint.density = getResources().getDisplayMetrics().density;
- mTextPaint.setCompatibilityScaling(
- getResources().getCompatibilityInfo().applicationScale);
- mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mHighlightPaint.setCompatibilityScaling(
- getResources().getCompatibilityInfo().applicationScale);
- mMovement = getDefaultMovementMethod();
- mTransformation = null;
- //attrs中包含了這個TextView控件在佈局文件中定義的屬性,比如android:background,android:layout_width等
- //com.android.internal.R.styleable.TextView中包含了TextView中的針對attrs中的屬性的默認的值
- //也就是說這個地方能夠將佈局文件中設置的屬性獲取出來,保存到一個TypeArray中,爲這個控件初始化各個屬性
- TypedArray a =
- context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.TextView, defStyle, 0);
- int textColorHighlight = 0;
- ColorStateList textColor = null;
- ColorStateList textColorHint = null;
- ColorStateList textColorLink = null;
- int textSize = 15;
- int typefaceIndex = -1;
- int styleIndex = -1;
- /*
- * Look the appearance up without checking first if it exists because
- * almost every TextView has one and it greatly simplifies the logic
- * to be able to parse the appearance first and then let specific tags
- * for this View override it.
- */
- TypedArray appearance = null;
- //TextView_textAppearance不太瞭解爲什麼要這樣做?難道是爲了設置TextView的一些默認的屬性?
- int ap = a.getResourceId(com.android.internal.R.styleable.TextView_textAppearance, -1);
- if (ap != -1) {
- appearance = context.obtainStyledAttributes(ap,
- com.android.internal.R.styleable.
- TextAppearance);
- }
- if (appearance != null) {
- int n = appearance.getIndexCount();
- for (int i = 0; i < n; i++) {
- int attr = appearance.getIndex(i);
- switch (attr) {
- case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
- textColorHighlight = appearance.getColor(attr, textColorHighlight);
- break;
- case com.android.internal.R.styleable.TextAppearance_textColor:
- textColor = appearance.getColorStateList(attr);
- break;
- case com.android.internal.R.styleable.TextAppearance_textColorHint:
- textColorHint = appearance.getColorStateList(attr);
- break;
- case com.android.internal.R.styleable.TextAppearance_textColorLink:
- textColorLink = appearance.getColorStateList(attr);
- break;
- case com.android.internal.R.styleable.TextAppearance_textSize:
- textSize = appearance.getDimensionPixelSize(attr, textSize);
- break;
- case com.android.internal.R.styleable.TextAppearance_typeface:
- typefaceIndex = appearance.getInt(attr, -1);
- break;
- case com.android.internal.R.styleable.TextAppearance_textStyle:
- styleIndex = appearance.getInt(attr, -1);
- break;
- }
- }
- appearance.recycle();
- }
- //各類屬性
- boolean editable = getDefaultEditable();
- CharSequence inputMethod = null;
- int numeric = 0;
- CharSequence digits = null;
- boolean phone = false;
- boolean autotext = false;
- int autocap = -1;
- int buffertype = 0;
- boolean selectallonfocus = false;
- Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
- drawableBottom = null;
- int drawablePadding = 0;
- int ellipsize = -1;
- boolean singleLine = false;
- int maxlength = -1;
- CharSequence text = "";
- CharSequence hint = null;
- int shadowcolor = 0;
- float dx = 0, dy = 0, r = 0;
- boolean password = false;
- int inputType = EditorInfo.TYPE_NULL;
- int n = a.getIndexCount();
- for (int i = 0; i < n; i++) {
- int attr = a.getIndex(i);
- //通過switch語句將用戶設置的,以及默認的屬性讀取出來並初始化
- switch (attr) {
- case com.android.internal.R.styleable.TextView_editable:
- editable = a.getBoolean(attr, editable);
- break;
- case com.android.internal.R.styleable.TextView_inputMethod:
- inputMethod = a.getText(attr);
- break;
- case com.android.internal.R.styleable.TextView_numeric:
- numeric = a.getInt(attr, numeric);
- break;
- //更多的case語句...
- case com.android.internal.R.styleable.TextView_textSize:
- textSize = a.getDimensionPixelSize(attr, textSize);//設置當前用戶所設置的字體大小
- break;
- case com.android.internal.R.styleable.TextView_typeface:
- typefaceIndex = a.getInt(attr, typefaceIndex);
- break;
- //更多的case語句...
- }
通過上面的代碼大概可以知道,每個組件基本都有3個構造器,其中只傳遞一個Context上下文的那個構造器一般用來在java代碼中實例化使用。
比如你可以
- TextView tv = new TextView(context);
來實例化一個組件。
最終調用的是第3個構造器
- public TextView(Context context,
- AttributeSet attrs,
- int defStyle)
在這個構造器中爲你設置了默認的屬性attrs和值styles。關鍵不在這裏,而是後面通過使用下面的代碼
- TypedArray a =
- context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.TextView, defStyle, 0);
來將屬性和值獲取出來,放到一個TypeArray中,然後再利用一個switch語句將裏面的值取出來。再利用這些值來初始化各個屬性。這個View最終利用這些屬性將這個控件繪製出來。
如果你在佈局文件中定義的一個View的話,那麼你定義的值,會被傳遞給構造器中的attrs和styles。也是利用同樣的方式來獲取出你定義的值,並根據你定義的值來繪製你想要的控件。
再比如其實Button和EditText都是繼承自TextView。看上去兩個控件似乎差異很大,其實不然。Button的源碼其實相比TextView變化的只是style而已:
- public class Button extends TextView {
- public Button(Context context) {
- this(context, null);
- }
- public Button(Context context, AttributeSet attrs) {
- this(context, attrs, com.android.internal.R.attr.buttonStyle);
- }
- public Button(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
- }
再看看EditText:
- public class EditText extends TextView {
- public EditText(Context context) {
- this(context, null);
- }
- public EditText(Context context, AttributeSet attrs) {
- this(context, attrs, com.android.internal.R.attr.editTextStyle);
- }
- public EditText(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
- @Override
- protected boolean getDefaultEditable() {
- return true;
- }
- @Override
- protected MovementMethod getDefaultMovementMethod() {
- return ArrowKeyMovementMethod.getInstance();
- }
- @Override
- public Editable getText() {
- return (Editable) super.getText();
- }
- @Override
- public void setText(CharSequence text, BufferType type) {
- super.setText(text, BufferType.EDITABLE);
- }
- /**
- * Convenience for {@link Selection#setSelection(Spannable, int, int)}.
- */
- public void setSelection(int start, int stop) {
- Selection.setSelection(getText(), start, stop);
- }
- /**
- * Convenience for {@link Selection#setSelection(Spannable, int)}.
- */
- public void setSelection(int index) {
- Selection.setSelection(getText(), index);
- }
- /**
- * Convenience for {@link Selection#selectAll}.
- */
- public void selectAll() {
- Selection.selectAll(getText());
- }
- /**
- * Convenience for {@link Selection#extendSelection}.
- */
- public void extendSelection(int index) {
- Selection.extendSelection(getText(), index);
- }
- @Override
- public void setEllipsize(TextUtils.TruncateAt ellipsis) {
- if (ellipsis == TextUtils.TruncateAt.MARQUEE) {
- throw new IllegalArgumentException("EditText cannot use the ellipsize mode "
- + "TextUtils.TruncateAt.MARQUEE");
- }
- super.setEllipsize(ellipsis);
- }
- }
不知道你是不是和我一樣感到意外呢?
不得不說這種方式非常的好。最大程度地利用了繼承,並且可以讓控件之間的屬性可以很方便的被開發者使用。也利用以後的擴展,實際上,不同的style就可以得到不同的UI,這也是MVC的一種體現。
比如用戶想自定義某個控件,只要覆蓋父類的style就可以很輕鬆的實現,可以參考我的一篇博文,就是使用style自定義ProgressBar 參考上一篇文章。
http://blog.csdn.net/hlglinglong/article/details/38662427
Android中的主題theme也是使用的style。當用戶在Activity中設置一個style的時候那麼會影響到整個Activity,如果爲Application設置style的話,則會影響所有的Activity,所以,如果你在開發一個應用的時候
可以考慮將應用的Activity的背景顏色等一類的屬性放到一個style中去,在Application中調用,這種做法會比較方便。
themes.xml:
- <!-- Variant of the default (dark) theme with no title bar -->
- <style name="Theme.NoTitleBar">
- <item name="android:windowNoTitle">true</item>
- </style>
- <!-- Variant of the default (dark) theme that has no title bar and
- fills the entire screen -->
- <style name="Theme.NoTitleBar.Fullscreen">
- <item name="android:windowFullscreen">true</item>
- <item name="android:windowContentOverlay">@null</item>
- </style>
我們平時使用的主題實際上就定義在這個文件中。也是一個style。