近的项目中需要添加换肤机制。期望达到的效果:
研究了一下目前的开源换肤方案的实现,大概是通过以下几种方式来实现:
下面说下整个机制的原理和过程。
所谓资源重定向,就是把系统默认的资源替换成我们想要更换的对应资源。这里就涉及到两部分,默认资源和对应资源。
举个例子,给TextView设置文字颜色android:textColor=”@color/white”,默认资源是R.color.white,重定向后,期望通过某种机制将R.color.white映射到R.color.black上,通过setTextColor(R.color.black)可修改TextView的文字颜色。
这个过程中,应该注意的对象有View(TextView)、属性(textColor)、属性值(@color/white)。换肤要实现的就是改变属性值。
这一步为换肤做准备,记下所有感兴趣的View及其属性和属性值。
我们从布局xml出发,找到一个记住View的时机。
Android布局xml中View的实例化过程可以参看我另一篇博文。
LayoutInflater完成了从layoutResID到View创建的过程。接下来祈祷能找到某个地方安插代码吧,好不用自行解析xml和遍历View。
LayoutInflater在递归inflate时,我们没机会侵入代码。直到LayoutInflater.createViewFromTag()。
Activtiy implements LayoutInflater.Factory2
,会调用到Activity的onCreateView()
第4种选择是系统默认实例化View的方式。到了这里,生米已成熟饭,记录感兴趣的View及其属性就没有希望了。
1和2可通过LayoutInflater.setFactory2()
和LayoutInflater.setFactory()
,实现自己的LayoutInflater Factory插入代码。法3的mPrivateFactory可以通过Activity的onCreateView()自行初始化View。
从这里看,1、2、3没有区别,demo里选取法2,截断系统默认createView()的过程。
前面也说了,我们通过Activity.getLayoutInflater().setFactory()
来更改原有factory,首先实现我们的factory,并实现onCreateView
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
private static final String[] sClassPrefixList = { "android.widget.", "android.webkit.", "android.app.", "android.view." }; public class SkinLayoutInflaterFactory implements LayoutInflater.Factory { @Override public View onCreateView(String name, Context context, AttributeSet attrs) { // 实例化View。自行预处理前缀,调用LayoutInflater.createView()实例化 for (String prefix : sClassPrefixList) { try { View view = mLayoutInflater.createView(name, prefix, attrs); if (view != null) { // 记录需要改变属性的View及其属性 addSkinViewIfNecessary(view, attrs); return view; } } catch (ClassNotFoundException e) { // In this case we want to let the base class take a crack // at it. } } return mLayoutInflater.createView(name, null, attrs); } } List<TextViewTextColorItem> textViewTextColorList = new ArrayList<>(); private void addSkinViewIfNecessary(View view, AttributeSet attrs) { if (view instanceof TextView) { int n = attrs.getAttributeCount(); for (int i = 0; i < n; i++) { String attrName = attrs.getAttributeName(i); if (attrName.equals("textColor")) { int id = 0; String attrValue = attrs.getAttributeValue(i); if (attrValue.startsWith("@")) { // 如"@2131427389" id = Integer.parseInt(attrValue.substring(1)); textViewTextColorList.add(new TextViewTextColorItem(view, id)); } } } } } |
对这里的onCreateView()实现有疑问,请继续深入博文中LayoutInflater.createViewFromTag()部分。
遍历记录感兴趣的View及其属性的数据结构,通过重定向资源更新属性值。为了减小项目修改,默认主题外的皮肤资源文件用命名后缀标识。
以替换TextView的textColor为例。
布局:
1 2 3 4 5 6 |
<TextView android:id="@+id/change_text_color" android:textColor="@color/textColor" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!"/>
|
更换主题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public void reSkin(String suffix) { for (TextViewTextColorItem skinItem : textViewTextColorList) { skinItem.view.setTextColor(getColor(skinItem.id, suffix)); } } // 用老的资源id获取新主题资源,需要一次华丽的转身:id -> name -> new name -> new id private int getColor(int oldResId, String suffix) { String oldResName = mContext.getResources().getResourceEntryName(oldResId); String newResName = oldResName + suffix; int newResId = mContext.getResources().getIdentifier(newResName, "color", mContext .getPackageName()); return mContext.getResources().getColor(newResId); } |
示例说明:
生成的R.java中textColor resource id:
1 2 3 4 5 |
public static final class color { ... public static final int textColor=0x7f0b003d; public static final int textColor_night=0x7f0b003e; } |
oldResId = 2131427389,转16进制为0x7f0b003d,即R.color.textColor
oldResName = textColor
newResName = textColor_night
newResId 2131427390,转16进制为0x7f0b003e,即R.color.textColor_night
使用新resource id通过Resource获取新色值,设置到view里,完成这个TextView的textColor属性值更改。
对,这就是我们想要的!
以上理论分析中我们只添加了一种关注的View和属性,接下来在换肤库中,我们将添加以下View和属性的支持,请持续关注项目eastmoneyandroid/Reskin。
- color
- drawable
- src
- View background(color/drawable)
- ListView divider(color/drawable)
- AbsListView listSelector(color/drawable)
- ImageView src(src)
- TextView textColor textHintColor drawableLeft
- ExpandableListView childDivider
- CheckBox button
- ProgressBar
- 自定义View
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责,本站只提供参考并不构成任何投资及应用建议。本站是一个个人学习交流的平台,网站上部分文章为转载,并不用于任何商业目的,我们已经尽可能的对作者和来源进行了通告,但是能力有限或疏忽,造成漏登,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。