原文地址——Styling Colors & Drawables w/ Theme Attributes诚啃。
你也許注意到
context.getResources().getColor(R.color.some_color_resource_id);
AndroidStudio會提示Resources#getColor(int)
方法在Marshmallow
版本已經(jīng)過時了益缎,可以使用Resources#getColor(int, Theme)
來代替乎赴。
你也許知道最簡單的處理方法是調(diào)用:
ContextCompat.getColor(context, R.color.some_color_resource_id);
這個方法其實(shí)是下面方法的簡單寫法
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return context.getResources().getColor(id, context.getTheme());
} else {
return context.getResources().getColor(id);
}
很簡單,但它的內(nèi)部原理是什么入挣,為什么這個方法會過時递惋,為什么Theme
參數(shù)之前不需要访惜?
Resources#getColor(int) & Resources#getColorStateList(int) 的問題
首先,讓我們來研究老方法內(nèi)部做了什么宙址。
-
Resources#getColor(int)
返回一個于顏色I(xiàn)D對應(yīng)的顏色值轴脐。如果這個ID指向一個ColorStateList
,方法會返回一個默認(rèn)色值; -
Resources#getColorStateList(int)
返回一個ID指向的ColorStateList
豁辉。
代碼什么時候會報錯呢
為了明白方法過時的原因令野,考慮一下ColorStateList
是使用以下xml
文件來聲明的。當(dāng)這個xml
應(yīng)用到TextView
上時徽级,就使字體顏色指向了R.attr.colorAccent
和R.attr.colorPrimary
的主題色气破。
<!-- res/colors/button_text_csl.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/colorAccent" android:state_enabled="false"/>
<item android:color="?attr/colorPrimary"/>
</selector>
假設(shè)現(xiàn)在在代碼中獲取ColorStateList
ColorStateList csl = context.getResources().getColorStateList(R.color.button_text_csl);
出人意料的是,錯誤出現(xiàn)了餐抢!
W/Resources: ColorStateList color/button_text_csl has unresolved theme attributes!
Consider using Resources.getColorStateList(int, Theme)
or Context.getColorStateList(int)
at android.content.res.Resources.getColorStateList(Resources.java:1011)
...
哪里出錯现使?
問題在于Resource
并不與應(yīng)用中特定的Theme
綁定,所以像R.attr.colorAccent
和R.attr.colorPrimary
就無法從Theme
中獲取到指定的顏色旷痕。事實(shí)上碳锈,直到API 23,才支持在ColorStateList
中指定主題屬性欺抗,使用下面兩個方法可以實(shí)現(xiàn):
- [Resources#getColor(int, Theme)
](http://developer.android.com/reference/android/content/res/Resources.html#getColor(int, android.content.res.Resources.Theme))返回ID指定的色值售碳,如果ID指向的是ColorStateList
,方法會返回一個默認(rèn)色值绞呈,任何主題屬性都會從主題參數(shù)中獲让橙恕; - [Resources#getColorStateList(int, Theme)
](http://developer.android.com/reference/android/content/res/Resources.html#getColorStateList(int, android.content.res.Resources.Theme))返回一個ID指向的ColorStateList
佃声,任何主題屬性都會從主題參數(shù)中獲纫罩恰;
更方便的方法可以通過support library中的ResourcesCompat
和 ContextCompat
來獲取圾亏。
如何更優(yōu)雅地解決問題
在 v24.0的AppCompt Support Library
十拣,可以通過使用 AppCompatResources
來解決這個問題
ColorStateList csl = AppCompatResources.getColorStateList(context, R.color.button_text_csl);
在Api23+,AppCompat 會委托對應(yīng)的框架來實(shí)現(xiàn)志鹃,更早的版本會手動解析xml文件夭问,解析所有的主題屬性。如果還不夠曹铃,ColorStateList
中的 android:alpha
已經(jīng)可以由低版本使用了(之前只能在API 23+上使用)
Resources#getDrawable(int)的問題
你猜對了缰趋。已過時的方法Resources#getDrawable(int)
與Resources#getColor(int)
和Resources#getColorStateList(int)
一樣有同樣的問題——直到API 21+在xml文件中才支持主題屬性。所以铛只,如果你想支持Lolipop之前的版本埠胖,需要避免主題屬性或者動態(tài)構(gòu)造一個Drawable
對象。
我不相信你淳玩,真的沒有例外直撤?
總是有例外的。
與AppCompatResources
類似蜕着, VectorDrawableCompat
和AnimatedVectorDrawableCompat
可以解析主題屬性谋竖,例如你想把VectorDrawableCompat
變成標(biāo)準(zhǔn)的灰色红柱,可以使用android:tint=?attr/colorControlNormal
,在老版本上也可以使用蓖乘。
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="?attr/colorControlNormal">
<path
android:pathData="..."
android:fillColor="@android:color/white"/>
</vector>
原理在于锤悄,在support
庫中解析xml中的主題屬性是使用[Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
](http://developer.android.com/reference/android/content/res/Resources.Theme.html#obtainStyledAttributes(android.util.AttributeSet, int[], int, int)),很酷吧嘉抒。
突擊考試
我們用上面的知識做個簡單的測試零聚,有如下的ColorStateList
:
<!-- res/colors/button_text_csl.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/colorAccent" android:state_enabled="false"/>
<item android:color="?attr/colorPrimary"/>
</selector>
假設(shè)聲明主題如下:
<!-- res/values/themes.xml -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/vanillared500</item>
<item name="colorPrimaryDark">@color/vanillared700</item>
<item name="colorAccent">@color/googgreen500</item>
</style>
<style name="CustomButtonTheme" parent="ThemeOverlay.AppCompat.Light">
<item name="colorPrimary">@color/brown500</item>
<item name="colorAccent">@color/yellow900</item>
</style>
最后假設(shè)你使用上述方法解析主題屬性動態(tài)構(gòu)造ColorStateList
:
@ColorInt
private static int getThemeAttrColor(Context context, @AttrRes int colorAttr) {
TypedArray array = context.obtainStyledAttributes(null, new int[]{colorAttr});
try {
return array.getColor(0, 0);
} finally {
array.recycle();
}
}
private static ColorStateList createColorStateList(Context context) {
return new ColorStateList(
new int[][]{
new int[]{-android.R.attr.state_enabled}, // Disabled state.
StateSet.WILD_CARD, // Enabled state.
},
new int[]{
getThemeAttrColor(context, R.attr.colorAccent), // Disabled state.
getThemeAttrColor(context, R.attr.colorPrimary), // Enabled state.
});
}
試試預(yù)測在API 23和API 19下按鈕enable
和disable
的外觀(例如,在#5和#8中些侍,按鈕使用了android:theme="@style/CustomButtonTheme"
來獲取自定義主題)隶症。
Resources res = ctx.getResources();
// (1)
int deprecatedTextColor = res.getColor(R.color.button_text_csl);
button1.setTextColor(deprecatedTextColor);
// (2)
ColorStateList deprecatedTextCsl = res.getColorStateList(R.color.button_text_csl);
button2.setTextColor(deprecatedTextCsl);
// (3)
int textColorXml =
AppCompatResources.getColorStateList(ctx, R.color.button_text_csl).getDefaultColor();
button3.setTextColor(textColorXml);
// (4)
ColorStateList textCslXml = AppCompatResources.getColorStateList(ctx, R.color.button_text_csl);
button4.setTextColor(textCslXml);
// (5)
Context themedCtx = button5.getContext();
ColorStateList textCslXmlWithCustomTheme =
AppCompatResources.getColorStateList(themedCtx, R.color.button_text_csl);
button5.setTextColor(textCslXmlWithCustomTheme);
// (6)
int textColorJava = getThemeAttrColor(ctx, R.attr.colorPrimary);
button6.setTextColor(textColorJava);
// (7)
ColorStateList textCslJava = createColorStateList(ctx);
button7.setTextColor(textCslJava);
// (8)
Context themedCtx = button8.getContext();
ColorStateList textCslJavaWithCustomTheme = createColorStateList(themedCtx);
button8.setTextColor(textCslJavaWithCustomTheme);
答案
注意在截屏中的粉色并不奇怪,而是當(dāng)未指定主題時岗宣,默認(rèn)加載默認(rèn)主題的效果蚂会。
與往常一樣,感謝閱讀耗式。如果有任何問題請?jiān)u論胁住,Github源碼地址。