一直有個問題就是,Android中是如何經過佈局文件,就能實現控件效果的不一樣呢?好比在佈局文件中,我設置了一個TextView,給它設置了textColor,它就可以改變這個TextView的文本的顏色。這是如何作到的呢?咱們分3個部分來看這個問題1.attrs.xml 2.styles.xml 3.看組件的源碼。 java
1.attrs.xml: android
咱們知道Android的源碼中有attrs.xml這個文件,這個文件實際上定義了全部的控件的屬性,就是咱們在佈局文件中設置的各種屬性 git
你能夠找到attrs.xml這個文件,打開它,全選,右鍵->Show In->OutLine。能夠看到整個文件的解構 app
下面是兩個截圖: ide
咱們大概能夠看出裏面是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: this
<!-- Color of text (usually same as colorForeground). --> <attr name="textColor" format="reference|color" />
format的意思是說:這個textColor能夠以兩種方式設置,要麼是關聯一個值,要麼是直接設置一個顏色的RGB值,這個不難理解,由於咱們能夠平時也這樣作過。 spa
也就是說咱們平時在佈局文件中所使用的各種控件的屬性都定義在這裏面,那麼這個文件,除了定義這些屬性外還定義了各類具體的組件,好比TextView,Button,SeekBar等所具備的各類特有的屬性 .net
好比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。
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。