Android多主题颜色相关问题

kaxc1088 8年前
   <p>如果您通过以下的代码来获取定义的颜色值</p>    <pre>  <code class="language-java">context.getResources().getColor(R.color.some_color_resource_id);     </code></pre>    <p>在 Android Studio 中会有一个 lint 警告,提示您 Resources#getColor(int) 在 Marshmallow 中被废弃了,建议使用主题可知的 Resources#getColor(int, Theme) 函数。 为了避免该警告,则可以使用 ContextCompat:</p>    <pre>  <code class="language-java">ContextCompat.getColor(context, R.color.some_color_resource_id);     </code></pre>    <p>该函数的实现是这样的:</p>    <pre>  <code class="language-java">if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {    return context.getResources().getColor(id, context.getTheme());  } else {    return context.getResources().getColor(id);  }     </code></pre>    <p>看起来很简单。但是为什么会这样呢? 为什么会开始使用带主题的函数而废弃之前的函数呢?</p>    <p>Resources#getColor(int) & Resources#getColorStateList(int) 的问题</p>    <p>首先来看看这两个被废弃的函数是干啥的:</p>    <p>– Resources#getColor(int) 返回一个资源 id 对应的颜色值,如果该资源为 ColorStateList 则返回 ColorStateList 的默认颜色值</p>    <p>– Resources#getColorStateList(int) 返回对应的 ColorStateList</p>    <p>上面的代码在什么情况下会破坏我的代码呢?</p>    <p>要理解为何废弃这两个函数,来看个 ColorStateList 的例子。 当在 TextView 中使用自定义的 ColorStateList 的时候, TextView 不可用状态和可用状态的文字颜色分别使用 R.attr.colorAccent 和 R.attr.colorPrimary 表示。</p>    <pre>  <code class="language-xml"><selectorxmlns:android="http://schemas.android.com/apk/res/android">      <itemandroid:color="?attr/colorAccent" android:state_enabled="false"/>      <itemandroid:color="?attr/colorPrimary"/>  </selector>  </code></pre>    <p>现在如果您通过如下的代码来获取这个ColorStateList</p>    <pre>  <code class="language-java">ColorStateListcsl = context.getResources().getColorStateList(R.color.button_text_csl);     </code></pre>    <p>上面的代码会抛出一个异常(查看logcat 可以看到如下的信息)</p>    <pre>  <code class="language-java">W/Resources: ColorStateListcolor/button_text_cslhasunresolvedthemeattributes!              ConsiderusingResources.getColorStateList(int, Theme)              or Context.getColorStateList(int)          atandroid.content.res.Resources.getColorStateList(Resources.java:1011)          ...     </code></pre>    <p>哪里出错了呢?</p>    <p>问题的根源在于 Resources 对象并没有和一个 Theme 对象关联,当使用 R.attr.colorAccent 和 R.attr.colorPrimary 指代颜色的时候,在代码中通过上面的函数解析的时候没有指定对应的 Theme导致无法解析出结果。 所以在 Marshmallow 中添加了 ColorStateList 对 Theme 的支持并且添加了这两个新的函数:Resources#getColor(int, Theme) 和 Resources#getColorStateList(int, Theme),并使用 Theme 参数来解析里面的 attributes 属性。</p>    <p>在新版本的 Support 库中也有对应的实现,分别位于 ResourcesCompat 和 ContextCompat 类中。</p>    <p>如何解决该问题呢?</p>    <p>使用 AppCompat v24+ 版本可以很容易的解决该问题。</p>    <pre>  <code class="language-java">ColorStateListcsl = AppCompatResources.getColorStateList(context, R.color.button_text_csl);     </code></pre>    <p>在 23+ 版本上直接使用系统的函数,在之前的版本上 AppCompat 自己解析这些 xml 文件从里面提取 attr 属性指代的数值。 AppCompat 同时还支持 ColorStateList 新的 android:alpha 属性。</p>    <p>Resources#getDrawable(int) 的问题</p>    <p>Resources#getDrawable(int) 和前面的两个函数的问题是类似的。 在 Lollipop 之前的版本中无法支持 Theme attr 。</p>    <p>为啥我这样用也没有出现异常呢?</p>    <p>异常并不总是会出现。</p>    <p>VectorDrawableCompat 和 AnimatedVectorDrawableCompat 类中添加了和 AppCompatResources 类类似的功能。比如在 矢量图中你可以使用 ?attr/colorControlNormal 来设置矢量图的颜色,VectorDrawableCompat 会自动完成解析该 属性的工作:</p>    <pre>  <code class="language-xml"><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>  </code></pre>    <p>小测试</p>    <p>下面使用一个小测试来回顾一下前面介绍的内容。 假设有下面一个 ColorStateList:</p>    <pre>  <code class="language-xml"><!-- 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>  </code></pre>    <p>在应用中定义了如下的 Theme:</p>    <pre>  <code class="language-xml"><!-- 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>  </code></pre>    <p>在代码中有如下的函数用来解析颜色值并在代码中创建 ColorStateList:</p>    <pre>  <code class="language-java">@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.        });  }     </code></pre>    <p>看看是否能猜出在 API 19 和 API 23 版本上文字禁用状态和正常状态的颜色,实现代码如下(5和8的情况,在TextView xml 中指定了 android:theme=”@style/CustomButtonTheme” ):</p>    <pre>  <code class="language-java">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);     </code></pre>    <p>下面是对应的实现截图:</p>    <p><img src="https://simg.open-open.com/show/07ea70a07a22ac5c93b51bc870a18908.png"></p>    <p>示例项目代码位于 <a href="/misc/goto?guid=4959676772228167846" rel="nofollow,noindex">github</a> ,原文位于 <a href="/misc/goto?guid=4959676102402188774" rel="nofollow,noindex">androiddesignpatterns</a></p>    <p> </p>    <p>来自:http://blog.chengyunfeng.com/?p=1013</p>    <p> </p>