这篇总结也是拖了很久了,欠下的技术债必须得偿还啦~
Android换肤在使用场景上可以区分为静态换肤/动态换肤、应用内换肤/插件式换肤。不同的换肤方案,适用于不同的业务场景。
setTheme可以较好的支持静态换肤,但如果要动态换肤则需要解决的核心问题有:
- 外部资源的加载
- 定位到需要换肤的View
第一个资源加载的问题可以通过构造AssetManager,反射调用其addAssetPath就可以完成。
第二个问题,就可以利用在onCreateView中,根据view的属性来定位。应用内换肤一般以资源前缀或后缀来区分不同资源,辅助以tag或侵入式等来区分换肤的view;进而保存需要换肤的view引用。
在个人的应用场景中,需要支持动态实时换肤,且以应用内换肤为主。最终在开源框架ChangeSkin的基础上进行了改造来满足需求。
下面会介绍不同的换肤方案及优缺点。
切换Theme
首先,在attr.xml中定义支持换肤的属性。一般是textColor,background等。如果系统支持的字体颜色,背景色较多,则需要增加很多的自定义属性。
<resources>
<attr name="textColor" format="reference|color"/>
<attr name="background" format="reference|color"/>
</resources>
其次,在不同主题的style中定义属性值。
<style name="AppTheme_Light" >
<item name="textColor">@android:color/black</ item>
<item name="android:background">@android:color/white</ item>
</style>
<style name="AppTheme_Night">
<item name="textColor">@android:color/white</ item>
<item name="android:background">@android:color/black</ item>
</style>
再次,View上应用style(?attr/nbackground表示使用style内的此属性作为view 的background).
<TextView
android :id="@+id/textview"
android :layout_width="wrap_content"
android :layout_height="wrap_content"
android :text="Hello World!"
android :background="?attr/background"
android :textColor="?attr/textColor"/>
最后,在Java代码内根据情况设置主题并刷新界面。如果要支持动态换肤,则必须要设置换肤的监听,全部页面实现一个回调。原因是setTheme方法要在setContentView之前调用才能生效。
//设置换肤回调
@Override
protected void onSkinChange() {
TypedValue background = new TypedValue();
TypedValue textColor = new TypedValue();
Resources.Theme theme = getTheme();
theme.resolveAttribute(R.attr.background, background, true);
theme.resolveAttribute(R.attr.textColor, textColor, true);
mTextView .setTextColor(textColor.data);
mTextView.setBackgroundResource(background.resourceId);
...
}
方案优点:
- 非侵入式,兼容性好,接口完善。
方案缺点:
- 为了支持动态换肤,需要recreate页面或遍历view来实现。有性能消耗。
- 如果应用设计无颜色规范,导致应用内颜色等定义较多,则需要定义较多自定义属性。
换肤框架AndroidChangeSkin
项目地址:https://github.com/hongyangAndroid/AndroidChangeSkin。
该框架通过相同名称+不同皮肤后缀来区分不同皮肤,再通过View的Tag来标志夜间模式的Drawable/Color引用。
如实现黑白皮肤,文本颜色item_text_color有一套默认皮肤,一套黑色皮肤定义资源item_text_color,item_text_color_black。
方案优点:
- 非侵入式, Android支持度高
- 支持应用内换肤和插件换肤
- 目前支持src,background,textColor,支持扩展
方案缺点:
- 需要自定义Tag;部分View的Tag被其他逻辑占用
- 使用方式繁琐
<TextView
android :layout_width="wrap_content"
android :layout_height="wrap_content"
android :tag="skin:item_text_color:textColor"
android :text="@string/hello_world"
android :textColor="@color/item_text_color"/>
换肤框架ChangeSkin
项目地址:https://github.com/hongyangAndroid/ChangeSkin.
针对框架AndroidChangeSkin需要为每个支持换肤的view设置tag,鸿洋实现了侵入式换肤的方案。该框架也参考了AndroidSkinLoader。
该框架最主要的方法在于baseSkinactivity中,侵入了onCreateView方法,使用了视图兼容工厂。遍历反射创建的view,根据资源前缀来标志是否支持换肤,存储支持换肤的view引用,以实现动态换肤。
在应用的某些场景,需要动态获取当前皮肤某资源信息,如黑白皮肤的主题颜色,需要通过SkinManager获取ResourceManager对象,进而通过Resources的getColor方法获取颜色值。参数为资源名,为了便于开发,可通过getResourceName将resId转化为resName。
public static int getSkinColor(@ColorRes int resId){
if (SkinManager.getInstance().isUseSkinPlugin()) {
String resName = CoreApplication.getInstance().getResources().getResourceName(resId);
return SkinManager.getInstance().getResourceManager().getColor(resName);
}
int retColor = CoreApplication.getInstance().getResources().getColor(resId);
return retColor;
}
方案优点:
- 使用方便,对开发者无额外成本
- 支持应用内换肤和插件换肤
- 目前支持src,background,textColor,支持扩展
方案缺点:
- 侵入式换肤,可能存在兼容性问题
换肤框架AndroidSkinLoader
该方案为侵入式方案,通过为LayoutInfalter去设置自定义Factory,对加载的View进行分析和提取。
具体可参考项目地址:https://github.com/fengjundev/Android-Skin-Loader
参考文章:
android 换肤(1)——插件式无缝换肤(解析鸿洋大神的换肤流程)
android 换肤(2)——插件式无缝换肤(解析鸿洋大神的换肤流程)
Android换肤技术总结
Android夜间模式调研总结
网友评论