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

 

 本文由用戶 kaxc1088 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!