本文基于两个前提
- 1.代码已经基本完成,并且不容易进行大规模的重构。
2.使用XML配置式完成模式切换,而不是采用大量的动态代码标记。
3.不希望重启Activity 就能动态切换主题
本文主要使用技术:
-
- Android skinloader
- Android res 加载原理
一、Android Skinloader 原理剖析
Android skin loader 是在 View 进行LayoutInflate 加载Xml布局文件,解析皮肤标记然后进行标记收集。
其中LayoutInflater 采用策略模式解析XML,所以可以在解析XML的文件的时候,采用相应的标记策略定义自己的解析策略,遍历并且保存所有带有换肤标记的View节点id属性,然后进行换肤。
源码如下:
private void parseSkinAttr(Context context, AttributeSet attrs, View view) {
List<SkinAttr> viewAttrs = new ArrayList<SkinAttr>();
for (int i = 0; i < attrs.getAttributeCount(); i++){
String attrName = attrs.getAttributeName(i);
String attrValue = attrs.getAttributeValue(i);
if(!AttrFactory.isSupportedAttr(attrName)){
continue;
}
if(attrValue.startsWith("@")){
try {
int id = Integer.parseInt(attrValue.substring(1));
String entryName = context.getResources().getResourceEntryName(id);
String typeName = context.getResources().getResourceTypeName(id);
SkinAttr mSkinAttr = AttrFactory.get(attrName, id, entryName, typeName);
if (mSkinAttr != null) {
viewAttrs.add(mSkinAttr);
}
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (NotFoundException e) {
e.printStackTrace();
}
}
}
if(!ListUtils.isEmpty(viewAttrs)){
SkinItem skinItem = new SkinItem();
skinItem.view = view;
skinItem.attrs = viewAttrs;
mSkinItems.add(skinItem);
if(SkinManager.getInstance().isExternalSkin()){
skinItem.apply();
}
}
}
public static SkinAttr get(String attrName, int attrValueRefId, String attrValueRefName, String typeName){
SkinAttr mSkinAttr = null;
if(BACKGROUND.equals(attrName)){
mSkinAttr = new BackgroundAttr();
}else if(TEXT_COLOR.equals(attrName)){
mSkinAttr = new TextColorAttr();
}else if(LIST_SELECTOR.equals(attrName)){
mSkinAttr = new ListSelectorAttr();
}else if(DIVIDER.equals(attrName)){
mSkinAttr = new DividerAttr();
}else{
return null;
}
mSkinAttr.attrName = attrName;
mSkinAttr.attrValueRefId = attrValueRefId;
mSkinAttr.attrValueRefName = attrValueRefName;
mSkinAttr.attrValueTypeName = typeName;
return mSkinAttr;
}**
二、Android主题策略已经资源加载原理
在Android studio 工程目录中定义了大量资源文件,其中有一个文件夹是关于夜间模式(values-night),这个是设置Android夜间模式资源位置。
其主要技术点为同名资源,不同资源目录的替换。
就是主动触发模式切换然后切换资源的加载路径,其源码如下:
final Resources resources = getContext().getApplicationContext().getResources();
final Configuration configuration = resources.getConfiguration();
configuration.uiMode = Configuration.UI_MODE_NIGHT_YES;
DisplayMetrics dm = resources.getDisplayMetrics();
resources.updateConfiguration(configuration, dm);
三、两者结合构建SkinLoader夜间模式
主要原理就是在需要修改颜色的控件上打上标记,并且在在vaules 和 values-night 定义重名的颜色,当模式切换 的时候调用遍历好的标记View,切换颜色,达到实时切换的效果。
主要代码如下:
定义 values
<color name="text_font_color">#ff77dd88</color>
定义values-night
<color name="text_font_color">#ff774488</color>
主要代码:
private void setTrigger(View view) {
view.findViewById(R.id.night_mode).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final Resources resources = getContext().getApplicationContext().getResources();
final Configuration configuration = resources.getConfiguration();
configuration.uiMode = Configuration.UI_MODE_NIGHT_YES;
DisplayMetrics dm = resources.getDisplayMetrics();
resources.updateConfiguration(configuration, dm);
mSkinInflaterFactory.applySkin();
}
});
}
资源下载地址: