最近的项目中需要添加换肤机制。期望达到的效果:
代码修改小
皮肤资源与主程序资源辨识度高
效率高
可扩展(自定义View及自定义属性)
可选技术方案
研究了一下目前的开源换肤方案的实现,大概是通过以下几种方式来实现:
手动设置View属性。这种方案需要每个View的每个属性都至少要一行代码,且与具体Activity联系紧密。代码冗长,修改麻烦。
使用Activity Theme。问题是需要重启Activity。
资源重定向。替换View属性对应的资源id。
想要达到预期效果,在这三种方案中用资源重定向的方式是最理想的。自己通过资源重定向写了一个换肤库:
eastmoneyandroid/Reskin
下面说下整个机制的原理和过程。
资源重定向换肤方案的实现
资源重定向
所谓资源重定向,就是把系统默认的资源替换成我们想要更换的对应资源。这里就涉及到两部分,默认资源和对应资源。
举个例子,给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及其属性
这一步为换肤做准备,记下所有感兴趣的View及其属性和属性值。
我们从布局xml出发,找到一个记住View的时机。
Android布局xml中View的实例化过程可以参看我另一篇博文。
LayoutInflater完成了从layoutResID到View创建的过程。接下来祈祷能找到某个地方安插代码吧,最好不用自行解析xml和遍历View。
LayoutInflater在递归inflate时,我们没机会侵入代码。直到LayoutInflater.createViewFromTag()。
LayoutInflater构造View对象的4种选择
mFactory2.onCreateView()
mFactory.onCreateView()
mPrivateFactory.onCreateView(),由于
Activtiy implements LayoutInflater.Factory2
,会调用到Activity的onCreateView()LayoutInflater.createView(String name, String prefix, AttributeSet attrs)
第4种选择是系统默认实例化View的方式。到了这里,生米已成熟饭,记录感兴趣的View及其属性就没有希望了。
1和2可通过LayoutInflater.setFactory2()
和LayoutInflater.setFactory()
,实现自己的LayoutInflater Factory插入代码。法3的mPrivateFactory可以通过Activity的onCreateView()自行初始化View。
从这里看,1、2、3没有区别,demo里选取法2,截断系统默认createView()的过程。
如何自己实例化出View?仿制!
前面也说了,我们通过Activity.getLayoutInflater().setFactory()
来更改原有factory,首先实现我们的factory,并实现onCreateView
方法:
1 | privatestaticfinal String[] sClassPrefixList = { |
对这里的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!"/>
更换主题: