[译]使用Android Theme属性进行个性化
2016-08-10 23:20
495 查看
原文地址——Styling Colors & Drawables w/ Theme Attributes。
你也许注意到
AndroidStudio会提示
你也许知道最简单的处理方法是调用:
这个方法其实是下面方法的简单写法
很简单,但它的内部原理是什么,为什么这个方法会过时,为什么
- Resources#getColor(int)
返回一个于颜色ID对应的颜色值。如果这个ID指向一个
- Resources#getColorStateList(int)
返回一个ID指向的
代码什么时候会报错呢
为了明白方法过时的原因,考虑一下
假设现在在代码中获取
出人意料的是,错误出现了!
哪里出错?
问题在于
-Resources#getColor(int, Theme)
返回ID指定的色值,如果ID指向的是
- Resources#getColorStateList(int, Theme)
返回一个ID指向的
更方便的方法可以通过support library中的ResourcesCompat
和 ContextCompat
来获取。
如何更优雅地解决问题
在 v24.0的
来解决这个问题
在Api23+,AppCompat 会委托对应的框架来实现,更早的版本会手动解析xml文件,解析所有的主题属性。如果还不够,
已经可以由低版本使用了(之前只能在API 23+上使用)
与
我不相信你,真的没有例外?
总是有例外的。
与
和AnimatedVectorDrawableCompat
可以解析主题属性,例如你想把
原理在于,在
,很酷吧。
假设声明主题如下:
最后假设你使用上述方法解析主题属性动态构造
试试预测在API 23和API 19下按钮
答案
注意在截屏中的粉色并不奇怪,而是当未指定主题时,默认加载默认主题的效果。
与往常一样,感谢阅读。如果有任何问题请评论,Github源码地址。
你也许注意到
context.getResources().getColor(R.color.some_color_resource_id);
AndroidStudio会提示
Resources#getColor(int)方法在
Marshmallow版本已经过时了,可以使用
Resources#getColor(int, Theme)来代替。
你也许知道最简单的处理方法是调用:
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); }
很简单,但它的内部原理是什么,为什么这个方法会过时,为什么
Theme参数之前不需要?
Resources#getColor(int) & Resources#getColorStateList(int) 的问题
首先,让我们来研究老方法内部做了什么。- Resources#getColor(int)
返回一个于颜色ID对应的颜色值。如果这个ID指向一个
ColorStateList,方法会返回一个默认色值;
- Resources#getColorStateList(int)
返回一个ID指向的
ColorStateList。
代码什么时候会报错呢
为了明白方法过时的原因,考虑一下
ColorStateList是使用以下
xml文件来声明的。当这个
xml应用到
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>
假设现在在代码中获取
ColorStateList
ColorStateList csl = context.getResources().getColorStateList(R.color.button_text_csl);
出人意料的是,错误出现了!
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并不与应用中特定的
Theme绑定,所以像
R.attr.colorAccent和
R.attr.colorPrimary就无法从
Theme中获取到指定的颜色。事实上,直到API 23,才支持在
ColorStateList中指定主题属性,使用下面两个方法可以实现:
-Resources#getColor(int, Theme)
返回ID指定的色值,如果ID指向的是
ColorStateList,方法会返回一个默认色值,任何主题属性都会从主题参数中获取;
- Resources#getColorStateList(int, Theme)
返回一个ID指向的
ColorStateList,任何主题属性都会从主题参数中获取;
更方便的方法可以通过support library中的ResourcesCompat
和 ContextCompat
来获取。
如何更优雅地解决问题
在 v24.0的
AppCompt Support Library,可以通过使用 AppCompatResources
来解决这个问题
ColorStateList csl = AppCompatResources.getColorStateList(context, R.color.button_text_csl);
在Api23+,AppCompat 会委托对应的框架来实现,更早的版本会手动解析xml文件,解析所有的主题属性。如果还不够,
ColorStateList中的 android:alpha
已经可以由低版本使用了(之前只能在API 23+上使用)
Resources#getDrawable(int)的问题
你猜对了。已过时的方法Resources#getDrawable(int)与
Resources#getColor(int)和
Resources#getColorStateList(int)一样有同样的问题——直到API 21+在xml文件中才支持主题属性。所以,如果你想支持Lolipop之前的版本,需要避免主题属性或者动态构造一个
Drawable对象。
我不相信你,真的没有例外?
总是有例外的。
与
AppCompatResources类似, VectorDrawableCompat
和AnimatedVectorDrawableCompat
可以解析主题属性,例如你想把
VectorDrawableCompat变成标准的灰色,可以使用
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)
,很酷吧。
突击考试
我们用上面的知识做个简单的测试,有如下的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>
假设声明主题如下:
<!-- 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>
最后假设你使用上述方法解析主题属性动态构造
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. }); }
试试预测在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);
答案
注意在截屏中的粉色并不奇怪,而是当未指定主题时,默认加载默认主题的效果。
与往常一样,感谢阅读。如果有任何问题请评论,Github源码地址。
相关文章推荐
- xUtils2和xUtils3的使用及区别
- Android开发之Android内存管理原理
- 实习杂记(32):怎么查看android真机的各种包和权限列表
- Android 通过代码创建界面
- android adb 显示error
- Android Studio Logcat 酷炫配色
- Android如何缩减APK包大小
- 实习杂记(31):android多dex方案四
- 从源代码分析Android-Universal-Image-Loader的缓存处理机制
- RecyclerView通用adapter以及item点击事件的实现
- android内存自动清理机制和android垃圾回收器
- Android的四大组建Service 简单、易懂的解析
- Android 使用PopupWindow实现下拉列表
- Android 蓝牙开发基本流程
- 触摸精灵之keepScreen
- Android dex加载过程分析
- 应用里的用户头像是怎样更改的?
- Android----Intent,运用由android系统帮助匹配实现打电话、发送短信、打开网页、播放音乐、打开视频、打开图片、安装APK、通知栏消息、拍照上传头像等功能
- 了解隐示Intent跳转方法
- 我的Android之旅(九)---Android读取电话,短信,网页,音乐等