Android多主題顏色相關問題
如果您通過以下的代碼來獲取定義的顏色值
context.getResources().getColor(R.color.some_color_resource_id);
在 Android Studio 中會有一個 lint 警告,提示您 Resources#getColor(int) 在 Marshmallow 中被廢棄了,建議使用主題可知的 Resources#getColor(int, Theme) 函數。 為了避免該警告,則可以使用 ContextCompat:
ContextCompat.getColor(context, R.color.some_color_resource_id);
該函數的實現是這樣的:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return context.getResources().getColor(id, context.getTheme());
} else {
return context.getResources().getColor(id);
}
看起來很簡單。但是為什么會這樣呢? 為什么會開始使用帶主題的函數而廢棄之前的函數呢?
Resources#getColor(int) & Resources#getColorStateList(int) 的問題
首先來看看這兩個被廢棄的函數是干啥的:
– Resources#getColor(int) 返回一個資源 id 對應的顏色值,如果該資源為 ColorStateList 則返回 ColorStateList 的默認顏色值
– Resources#getColorStateList(int) 返回對應的 ColorStateList
上面的代碼在什么情況下會破壞我的代碼呢?
要理解為何廢棄這兩個函數,來看個 ColorStateList 的例子。 當在 TextView 中使用自定義的 ColorStateList 的時候, TextView 不可用狀態和可用狀態的文字顏色分別使用 R.attr.colorAccent 和 R.attr.colorPrimary 表示。
<selectorxmlns:android="http://schemas.android.com/apk/res/android">
<itemandroid:color="?attr/colorAccent" android:state_enabled="false"/>
<itemandroid:color="?attr/colorPrimary"/>
</selector>
現在如果您通過如下的代碼來獲取這個ColorStateList
ColorStateListcsl = context.getResources().getColorStateList(R.color.button_text_csl);
上面的代碼會拋出一個異常(查看logcat 可以看到如下的信息)
W/Resources: ColorStateListcolor/button_text_cslhasunresolvedthemeattributes!
ConsiderusingResources.getColorStateList(int, Theme)
or Context.getColorStateList(int)
atandroid.content.res.Resources.getColorStateList(Resources.java:1011)
...
哪里出錯了呢?
問題的根源在于 Resources 對象并沒有和一個 Theme 對象關聯,當使用 R.attr.colorAccent 和 R.attr.colorPrimary 指代顏色的時候,在代碼中通過上面的函數解析的時候沒有指定對應的 Theme導致無法解析出結果。 所以在 Marshmallow 中添加了 ColorStateList 對 Theme 的支持并且添加了這兩個新的函數:Resources#getColor(int, Theme) 和 Resources#getColorStateList(int, Theme),并使用 Theme 參數來解析里面的 attributes 屬性。
在新版本的 Support 庫中也有對應的實現,分別位于 ResourcesCompat 和 ContextCompat 類中。
如何解決該問題呢?
使用 AppCompat v24+ 版本可以很容易的解決該問題。
ColorStateListcsl = AppCompatResources.getColorStateList(context, R.color.button_text_csl);
在 23+ 版本上直接使用系統的函數,在之前的版本上 AppCompat 自己解析這些 xml 文件從里面提取 attr 屬性指代的數值。 AppCompat 同時還支持 ColorStateList 新的 android:alpha 屬性。
Resources#getDrawable(int) 的問題
Resources#getDrawable(int) 和前面的兩個函數的問題是類似的。 在 Lollipop 之前的版本中無法支持 Theme attr 。
為啥我這樣用也沒有出現異常呢?
異常并不總是會出現。
VectorDrawableCompat 和 AnimatedVectorDrawableCompat 類中添加了和 AppCompatResources 類類似的功能。比如在 矢量圖中你可以使用 ?attr/colorControlNormal 來設置矢量圖的顏色,VectorDrawableCompat 會自動完成解析該 屬性的工作:
<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>
小測試
下面使用一個小測試來回顧一下前面介紹的內容。 假設有下面一個 ColorStateList:
<!-- res/colors/button_text_csl.xml -->
<selectorxmlns:android="http://schemas.android.com/apk/res/android">
<itemandroid:color="?attr/colorAccent" android:state_enabled="false"/>
<itemandroid:color="?attr/colorPrimary"/>
</selector>
在應用中定義了如下的 Theme:
<!-- res/values/themes.xml -->
<stylename="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<itemname="colorPrimary">@color/vanillared500</item>
<itemname="colorPrimaryDark">@color/vanillared700</item>
<itemname="colorAccent">@color/googgreen500</item>
</style>
<stylename="CustomButtonTheme" parent="ThemeOverlay.AppCompat.Light">
<itemname="colorPrimary">@color/brown500</item>
<itemname="colorAccent">@color/yellow900</item>
</style>
在代碼中有如下的函數用來解析顏色值并在代碼中創建 ColorStateList:
@ColorInt
private static int getThemeAttrColor(Contextcontext, @AttrRes int colorAttr) {
TypedArrayarray = context.obtainStyledAttributes(null, new int[]{colorAttr});
try {
return array.getColor(0, 0);
} finally {
array.recycle();
}
}
private static ColorStateListcreateColorStateList(Contextcontext) {
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.
});
}
看看是否能猜出在 API 19 和 API 23 版本上文字禁用狀態和正常狀態的顏色,實現代碼如下(5和8的情況,在TextView xml 中指定了 android:theme=”@style/CustomButtonTheme” ):
Resourcesres = ctx.getResources();
// (1)
int deprecatedTextColor = res.getColor(R.color.button_text_csl);
button1.setTextColor(deprecatedTextColor);
// (2)
ColorStateListdeprecatedTextCsl = 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)
ColorStateListtextCslXml = AppCompatResources.getColorStateList(ctx, R.color.button_text_csl);
button4.setTextColor(textCslXml);
// (5)
ContextthemedCtx = button5.getContext();
ColorStateListtextCslXmlWithCustomTheme =
AppCompatResources.getColorStateList(themedCtx, R.color.button_text_csl);
button5.setTextColor(textCslXmlWithCustomTheme);
// (6)
int textColorJava = getThemeAttrColor(ctx, R.attr.colorPrimary);
button6.setTextColor(textColorJava);
// (7)
ColorStateListtextCslJava = createColorStateList(ctx);
button7.setTextColor(textCslJava);
// (8)
ContextthemedCtx = button8.getContext();
ColorStateListtextCslJavaWithCustomTheme = createColorStateList(themedCtx);
button8.setTextColor(textCslJavaWithCustomTheme);
下面是對應的實現截圖:
示例項目代碼位于 github ,原文位于 androiddesignpatterns
來自:http://blog.chengyunfeng.com/?p=1013