ColorStateList 使用详解
1. 是什么?
ColorStateList(顏色狀態(tài)列表)是一個可以定義在 XML 布局文件中,并最終根據(jù) ColorStateList 應(yīng)用的 View 的狀態(tài)顯示不同顏色的對象。
A ColorStateList is an object you can define in XML that you can apply as a color, but will actually change colors, depending on the state of the View object to which it is applied.
最終效果如下:
界面中兩按鈕文字的顏色隨著按鈕的狀態(tài)而改變。
2. 怎么用?
從 ColorStateList 的定義可以知道,創(chuàng)建 ColorStateList 的方式應(yīng)該不止有一種。接下來,我們就嘗試從兩方面創(chuàng)建 ColorStateList:
2.1 如何在 XML 中定義 ColorStateList
2.1.1 文件位置
res/color/filename.xml 復(fù)制代碼2.1.2 編譯之后的數(shù)據(jù)類型
ColorStateList 復(fù)制代碼2.1.3 應(yīng)用方式
2.1.4 語法
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android" ><itemandroid:color="hex_color"android:state_pressed=["true" | "false"]android:state_focused=["true" | "false"]android:state_selected=["true" | "false"]android:state_checkable=["true" | "false"]android:state_checked=["true" | "false"]android:state_enabled=["true" | "false"]android:state_window_focused=["true" | "false"] /> </selector> 復(fù)制代碼2.1.5 屬性解析
| color | 不同狀態(tài)的顏色值 | 十六進(jìn)制的顏色值。 可以是如下格式: #RGB #ARGB #RRGGBB #AARRGGBB |
| state_pressed | View 按下的狀態(tài) | true,false。 true,按下; false,默認(rèn)狀態(tài),即沒有按下之前的狀態(tài)。 |
| state_selected | View 選中的狀態(tài) | true,false。 true,選中; false,未選中。 |
其他的屬性類似,在此就不做贅述了。想要了解更多關(guān)于 state_xxx 的內(nèi)容,請查看Color state list resource。
2.1.6 示例
//1. text_color_state_list.xml <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:color="@color/green_700" android:state_pressed="true" /><item android:color="@color/grey_700" android:state_pressed="false" /><!--默認(rèn)項(xiàng)--><item android:color="@color/grey_700" /> </selector> 復(fù)制代碼//2. 在 XML 布局文件中應(yīng)用 text_color_state_list <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:orientation="vertical"tools:context=".MainActivity"><Buttonandroid:id="@+id/alphabet_a"android:layout_width="@dimen/avatar_size"android:layout_height="@dimen/padding_seventy_two"android:text="@string/alphabet_a"android:textColor="@color/text_color_state_list"android:textSize="@dimen/font_thirty_two" /></LinearLayout> 復(fù)制代碼最終效果如下:
//3. 在 Java 代碼中使用 text_color_state_list public class MainActivity extends AppCompatActivity {private Button mAlphaB;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();}private void initView(){mAlphaB = findViewById(R.id.alphabet_b);Resources resources = getResources();ColorStateList colorStateList = resources.getColorStateList(R.color.text_color_state_list);mAlphaB.setTextColor(colorStateList);}} 復(fù)制代碼在 Java 中使用在 XML 中定義的 ColorStateList 的效果與在 XML 中使用在 XML 中定義的 ColorStateList 的效果一樣,所以就不贅述了。
2.1.7 注意事項(xiàng)
2.1.7.1 ColorStateList 中定義的默認(rèn) Item 一定要放在最下面
ColorStateList 中定義的默認(rèn) Item 一定要放在最下面,否則后面的 Item 將被忽略,Android Framework 在此處選擇資源的時候,并不是按照“最優(yōu)選項(xiàng)”選擇的,而是按照從上到下選擇第一個匹配的。
Remember that the first item in the state list that matches the current state of the object will be applied. So if the first item in the list contains none of the state attributes above, then it will be applied every time, which is why your default value should always be last, as demonstrated in the following example.
舉個例子:
最終效果如下:
最終效果如下:
由上面的運(yùn)行效果可知:當(dāng)默認(rèn)的 Item 在最上面的時候,Button 的文字顏色并不會隨著 Button 狀態(tài)的改變而改變。因此在后面定義 ColorStateList 的時候,如果想要應(yīng)用 ColorStateList 的 View 內(nèi)容(字體或者其他)的顏色隨著 View 的狀態(tài)而改變,就需要把 ColorStateList 中默認(rèn)的 Item 定義在最下面。
2.1.7.2 ColorStateList 是不能用于 View 的 Background
//1. View 部分源碼 public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {...public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {this(context);final TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);...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;...}...}...}...} 復(fù)制代碼由 View 源碼可知:View 的 Background 最終是通過 TypedArray 的 GetDrawable 方法獲取的。
//2. TypedArray 部分源碼 public class TypedArray {.../*** Retrieve the Drawable for the attribute at <var>index</var>.* <p>* This method will throw an exception if the attribute is defined but is* not a color or drawable resource.** @param index Index of attribute to retrieve.** @return Drawable for the attribute, or {@code null} if not defined.* @throws RuntimeException if the TypedArray has already been recycled.* @throws UnsupportedOperationException if the attribute is defined but is* not a color or drawable resource.*/@Nullablepublic Drawable getDrawable(@StyleableRes int index) {return getDrawableForDensity(index, 0);}...} 復(fù)制代碼由 TypedArray 源碼可知,在 TypedArray 的 GetDrawable 中只能接收純 Color 或者 Drawable Resource,而 ColorStateList 并未在此范圍內(nèi),因此 ColorStateList 是不能用于 View 的 Background(如果在 View 的 Background 中引用 ColorStateList,應(yīng)用程序?qū)?Crash)。
throws UnsupportedOperationException if the attribute is defined but is not a color or drawable resource.
2.1.7.2 StateListDrawable 是不能用于 TextView 系的 TextColor
//1. TextView 部分源碼 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {...public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {...readTextAppearance(context, a, attributes, true /* styleArray */);...}... } 復(fù)制代碼//2. readTextAppearance 方法 private void readTextAppearance(Context context, TypedArray appearance, TextAppearanceAttributes attributes, boolean styleArray) {...for (int i = 0; i < n; i++) {...switch (index) {case com.android.internal.R.styleable.TextAppearance_textColorHighlight:attributes.mTextColorHighlight = appearance.getColor(attr, attributes.mTextColorHighlight);break;...}...}...} 復(fù)制代碼通過 TextView 源碼可知,TextView 的 TextColor 最終是通過 TypedArray 的 GetColor 方法獲取的。
//3. TypedArray 部分源碼 public class TypedArray {.../*** Retrieve the color value for the attribute at <var>index</var>. If* the attribute references a color resource holding a complex* {@link android.content.res.ColorStateList}, then the default color from* the set is returned.* <p>* This method will throw an exception if the attribute is defined but is* not an integer color or color state list.** @param index Index of attribute to retrieve.* @param defValue Value to return if the attribute is not defined or* not a resource.** @return Attribute color value, or defValue if not defined.* @throws RuntimeException if the TypedArray has already been recycled.* @throws UnsupportedOperationException if the attribute is defined but is* not an integer color or color state list.*/@ColorIntpublic int getColor(@StyleableRes int index, @ColorInt int defValue) {if (mRecycled) {throw new RuntimeException("Cannot make calls to a recycled instance!");}final int attrIndex = index;index *= STYLE_NUM_ENTRIES;final int[] data = mData;final int type = data[index + STYLE_TYPE];if (type == TypedValue.TYPE_NULL) {return defValue;} else if (type >= TypedValue.TYPE_FIRST_INT&& type <= TypedValue.TYPE_LAST_INT) {return data[index + STYLE_DATA];} else if (type == TypedValue.TYPE_STRING) {final TypedValue value = mValue;if (getValueAt(index, value)) {final ColorStateList csl = mResources.loadColorStateList(value, value.resourceId, mTheme);return csl.getDefaultColor();}return defValue;} else if (type == TypedValue.TYPE_ATTRIBUTE) {final TypedValue value = mValue;getValueAt(index, value);throw new UnsupportedOperationException("Failed to resolve attribute at index " + attrIndex + ": " + value);}throw new UnsupportedOperationException("Can't convert value at index " + attrIndex+ " to color: type=0x" + Integer.toHexString(type));}...}復(fù)制代碼由 TypedArray 源碼可知,在 TypedArray 的 getColor 中只能接收純 Color 或者 Color State List,而 StateListDrawable 并未在此范圍內(nèi),因此 StateListDrawable 是不能用于 TextView 系的 TextColor(如果在 TextView 的 TextColor 中引用 StateListDrawable 程序?qū)?Bug,但是不會 Crash)。
throws UnsupportedOperationException if the attribute is defined but is not an integer color or color state list.
2.2 如何在代碼中定義 ColorStateList
2.2.1 ColorStateList 源碼解析
ColorStateList 部分源碼如下:
public class ColorStateList extends ComplexColor implements Parcelable {.../*** Creates a ColorStateList that returns the specified mapping from* states to colors.*/public ColorStateList(int[][] states, @ColorInt int[] colors) {mStateSpecs = states;mColors = colors;onColorsChanged();}...} 復(fù)制代碼由上面的源碼可知,在創(chuàng)建 ColorStateList 的時候,需要傳入兩個數(shù)組,第一個數(shù)組是存儲狀態(tài)值的,第二個數(shù)組是存儲狀態(tài)對應(yīng)顏色值的。
簡單對比一下 XML 中定義 ColorStateList 的語法,其實(shí)很容易就明白為什么在 ColorStateList 構(gòu)造方法中存儲狀態(tài)值的數(shù)組是二維數(shù)組。
因?yàn)樵诿恳粋€ Item 中可以有很多個狀態(tài)(state_xxx),每一個 Item 中的所有這些狀態(tài)只對應(yīng)一個顏色值。也就是說,ColorStateList 構(gòu)造方法中的存儲狀態(tài)的數(shù)組的第一層數(shù)組的 Size 只要和存儲狀態(tài)對應(yīng)顏色值的數(shù)組的 Size 一致就好了。
舉個例子(偽代碼):
//狀態(tài)值(states 第一層 size 為 2) int[][] states = new int[2][]; states[0] = new int[] {android.R.attr.state_xxx}; states[1] = new int[] {}; //不同狀態(tài)對應(yīng)的顏色值(colors size 為 2) int[] colors = new int[] { R.color.pressed, R.color.normal}; ColorStateList colorList = new ColorStateList(states, colors); 復(fù)制代碼2.2.2 示例
public class MainActivity extends AppCompatActivity {private Button mAlphaB;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();}private void initView(){mAlphaB = findViewById(R.id.alphabet_b);ColorStateList colorStateList = createColorStateList(getResources().getColor(R.color.green_700), getResources().getColor(R.color.grey_700));mAlphaB.setTextColor(colorStateList);}private ColorStateList createColorStateList(int pressed, int normal) {//狀態(tài)int[][] states = new int[2][];//按下states[0] = new int[] {android.R.attr.state_pressed};//默認(rèn)states[1] = new int[] {};//狀態(tài)對應(yīng)顏色值(按下,默認(rèn))int[] colors = new int[] { pressed, normal};ColorStateList colorList = new ColorStateList(states, colors);return colorList;}} 復(fù)制代碼最終效果如下:
2.2.3 自定義 ColorStateList
除了上面的方式之外,還可以繼承 ColorStateList 實(shí)現(xiàn)自定義 ColorStateList,但由于 ColorStateList 可更改的屬性太少,所以自定義 ColorStateList 并沒有什么意義。
簡單示例:
public class CustomColorStateList extends ColorStateList {public CustomColorStateList(int[][] states, int[] colors) {super(states, colors);}} 復(fù)制代碼具體使用方法同《2.2.2 示例》一樣,所以再次不做贅述。
3. 工作原理
下面是在代碼中使用在 XML 布局文件中創(chuàng)建的 ColorStateList 的方法:
//1. text_color_state_list.xml <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:color="@color/green_700" android:state_pressed="true" /><item android:color="@color/grey_700" android:state_pressed="false" /><!--默認(rèn)項(xiàng)--><item android:color="@color/grey_700" /> </selector> 復(fù)制代碼//2. 在 Java 代碼中使用 text_color_state_list public class MainActivity extends AppCompatActivity {private Button mAlphaB;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();}private void initView(){mAlphaB = findViewById(R.id.alphabet_b);Resources resources = getResources();ColorStateList colorStateList = resources.getColorStateList(R.color.text_color_state_list);mAlphaB.setTextColor(colorStateList);}} 復(fù)制代碼既然是通過 Button 的 SetTextColor 方法將 ColorStateList 應(yīng)用到 Button 的字體顏色上的,那接下來就進(jìn)到 Button 的 SetTextColor 方法一看究竟。
//3. 進(jìn)入 Button 的 setTextColor 方法 public class TextView{...@android.view.RemotableViewMethodpublic void setTextColor(ColorStateList colors) {if (colors == null) {throw new NullPointerException();}mTextColor = colors;updateTextColors();}...} 復(fù)制代碼因?yàn)?Button 繼承至 TextView,Button 的 SetTextColor 方法繼承至 TextView,且未做任何更改,因此直接進(jìn)入了 TextView 類中。
在 TextView 類的 SetTextColor 方法中調(diào)用了 UpdateTextColors 方法。
//4. 進(jìn)入 updateTextColors 方法 public class TextView{...private void updateTextColors() {boolean inval = false;final int[] drawableState = getDrawableState();int color = mTextColor.getColorForState(drawableState, 0);if (color != mCurTextColor) {mCurTextColor = color;inval = true;}if (mLinkTextColor != null) {color = mLinkTextColor.getColorForState(drawableState, 0);if (color != mTextPaint.linkColor) {mTextPaint.linkColor = color;inval = true;}}if (mHintTextColor != null) {color = mHintTextColor.getColorForState(drawableState, 0);if (color != mCurHintTextColor) {mCurHintTextColor = color;if (mText.length() == 0) {inval = true;}}}if (inval) {// Text needs to be redrawn with the new colorif (mEditor != null) mEditor.invalidateTextDisplayList();invalidate();}}...} 復(fù)制代碼接著看下在 TextView 類中,哪里都調(diào)用了 TextView 的 UpdateTextColors 方法。
最終找到了 TextView 的 DrawableStateChanged 方法,即在 TextView 的 DrawableStateChanged 方法中調(diào)用了 TextView 的 UpdateTextColors 方法。
//5. 進(jìn)入 drawableStateChanged 方法 public class TextView{...@Overrideprotected void drawableStateChanged() {super.drawableStateChanged();if (mTextColor != null && mTextColor.isStateful()|| (mHintTextColor != null && mHintTextColor.isStateful())|| (mLinkTextColor != null && mLinkTextColor.isStateful())) {updateTextColors();}if (mDrawables != null) {final int[] state = getDrawableState();for (Drawable dr : mDrawables.mShowing) {if (dr != null && dr.isStateful() && dr.setState(state)) {invalidateDrawable(dr);}}}}...} 復(fù)制代碼在 TextView 類的 DrawableStateChanged 方法中調(diào)用了父類的 DrawableStateChanged 方法,進(jìn)入 TextView 的父類(View)中看下哪里都調(diào)用了 DrawableStateChanged 方法。
最終找到了 View 的 RefreshDrawableState 方法,即在 View 的 RefreshDrawableState 方法中調(diào)用了 DrawableStateChanged 方法。
//6. 進(jìn)入 refreshDrawableState 方法 public class View{.../*** Call this to force a view to update its drawable state. This will cause* drawableStateChanged to be called on this view. Views that are interested* in the new state should call getDrawableState.** @see #drawableStateChanged* @see #getDrawableState*/public void refreshDrawableState() {mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;drawableStateChanged();ViewParent parent = mParent;if (parent != null) {parent.childDrawableStateChanged(this);}}... } 復(fù)制代碼在 View 類中看下哪里都調(diào)用了 RefreshDrawableState 方法。
在 View 類中,發(fā)現(xiàn)有多個方法都調(diào)用了 RefreshDrawableState 方法,如:
- setEnabled(boolean enabled)
- setPressed(boolean pressed)
- onWindowFocusChanged(boolean hasWindowFocus)
- setHovered(boolean hovered)
- setSelected(boolean selected)
- setActivated(boolean activated)
是不是有一種似曾相識的感覺:
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android" ><itemandroid:color="hex_color"android:state_pressed=["true" | "false"]android:state_focused=["true" | "false"]android:state_selected=["true" | "false"]android:state_checkable=["true" | "false"]android:state_checked=["true" | "false"]android:state_enabled=["true" | "false"]android:state_window_focused=["true" | "false"] /> </selector> 復(fù)制代碼接下來,我們隨便挑一個方法來分析——SetPressed 方法。
//7. 進(jìn)入 setPressed 方法 public class View{.../*** Sets the pressed state for this view.** @see #isClickable()* @see #setClickable(boolean)** @param pressed Pass true to set the View's internal state to "pressed", or false to reverts* the View's internal state from a previously set "pressed" state.*/public void setPressed(boolean pressed) {final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);if (pressed) {mPrivateFlags |= PFLAG_PRESSED;} else {mPrivateFlags &= ~PFLAG_PRESSED;}if (needsRefresh) {refreshDrawableState();}dispatchSetPressed(pressed);}... } 復(fù)制代碼接下來看下,在 View 類中哪里都調(diào)用了 SetPressed 方法。
在 View 類中,發(fā)現(xiàn)有多個方法都調(diào)用了 SetPressed 方法,如:
- removeUnsetPressCallback
- onFocusChanged
- resetPressedState
- dispatchGenericMotionEventInternal
- onKeyDown
- onKeyUp
- onTouchEvent
在上面的這些方法中,有一個方法引起了我們注意——onTouchEvent 處理觸屏事件的方法。
Implement this method to handle touch screen motion events.
public class View{...public boolean onTouchEvent(MotionEvent event) {...if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {switch (action) {case MotionEvent.ACTION_UP:...break;case MotionEvent.ACTION_DOWN:...if (isInScrollingContainer) {...} else {// Not inside a scrolling container, so show the feedback right away///// //// 只看這里就好啦 //// /////setPressed(true, x, y);checkForLongClick(0, x, y);}break;case MotionEvent.ACTION_CANCEL:...break;case MotionEvent.ACTION_MOVE:...break;}return true;}return false;}...} 復(fù)制代碼到這里,我們不難發(fā)現(xiàn)最終 ColorStateList 是如何起作用的:
在 View 的 OnTouchEvent 中根據(jù)用戶操作確定當(dāng)前 View 的狀態(tài),選擇與該狀態(tài)對應(yīng)的顏色值并將其設(shè)置到 View 的 Paint上,進(jìn)而在刷新界面的時候應(yīng)用新的顏色。在 TextView 系控件中表現(xiàn)為:根據(jù) TextView 系控件的狀態(tài)將與該狀態(tài)對應(yīng)的顏色值設(shè)置到當(dāng)前控件的 TextPaint 上,進(jìn)而在刷新界面的時候應(yīng)用新的顏色。
4. ColorStateList 與 StateListDrawable 之間的關(guān)系
ColorStateList 與 StateListDrawable 其實(shí)并沒有什么關(guān)系。
ColorStateList 繼承至 Object,而 StateListDrawable 間接繼承至 Drawable。
如果非要從它們兩個中間找到共同點(diǎn),那就是它們都能根據(jù)當(dāng)前 View 的狀態(tài)改變自己的顯示內(nèi)容(ColorStateList 根據(jù) View 狀態(tài)顯示不同的 Color,StateListDrawable 根據(jù) View 狀態(tài)顯示不同的 Drawable)。
5. 參考文獻(xiàn)
轉(zhuǎn)載于:https://juejin.im/post/5cbc5baf6fb9a0687015d3a1
總結(jié)
以上是生活随笔為你收集整理的ColorStateList 使用详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 单用户登陆demo-后者挤到前者,类似Q
- 下一篇: MFC 随机矩形