赞
踩
在我们的开发中,也许有些项目会有换肤的需求,这个时候会比较头疼怎么做才能做到一键换肤呢?大家肯定希望越简单越好。下面我们基于Github上比较受欢迎的Android-Skin-Loader框架分析一下换肤的本质是什么?
换肤,其本质无非就是更换页面元素(view或viewGroup)的属性值,这些属性值都是可以用资源文件表示的,换句话说换肤其实就是替换掉资源文件。比如换个背景,换个文字颜色等。
先看一组换肤:
分析上面的平板应用换肤其中的一个页面,绿色和蓝色风格只有背景图片、控件颜色改变了。
再看一张控制流程图了解大概思路:
上图大致讲解了换肤的原理,即通过对页面下的所有view重新设置一遍资源文件,而这些资源文件我们可以把它制作成皮肤包(即apk)。
也许通过上面这张流程图你还是不能完全看懂每一个工作流程,下面配合代码详细介绍一下:
遍历页面下所有元素及其属性集合
通过在页面(Activity、FragmentActivity)中设置Factory,该Factory能拿到页面下所有view和attrs
Activity / FragmentActivity
/**
* SkinInflaterFactory是自定义的Factory,实现了android.view.LayoutInflater.Factory
* 创建view的事情委托给自定义工厂
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SkinInflaterFactory mSkinInflaterFactory = new SkinInflaterFactory();
getLayoutInflater().setFactory(mSkinInflaterFactory);
}
SkinInflaterFactory
/** * SkinInflaterFactory在onCreateView()方法完成了view的创建 * 我们可以在该方法中获取view和view的属性集AttributeSet * * @param name view的类名全称 例如:1.android.widget.TextView 2.android.view.View等 * @param context 上下文 * @param attrs xml中该view的属性 */ @Override public View onCreateView(String name, Context context, AttributeSet attrs) { // 根据name中包含的字段从下列3个包中选择构建出view View view = null; if (-1 == name.indexOf('.')){ if ("View".equals(name)) { view = LayoutInflater.from(context).createView(name, "android.view.", attrs); } if (view == null) { view = LayoutInflater.from(context).createView(name, "android.widget.", attrs); } if (view == null) { view = LayoutInflater.from(context).createView(name, "android.webkit.", attrs); } } else { view = LayoutInflater.from(context).createView(name, null, attrs); } // 解析每个元素下的所有属性(背景、文本颜色、图标等) parseSkinAttr(context, attrs, view) }
解析每个元素下的所有属性(背景、文本颜色、图标等)
/** * 为方便理解,我拿xml中view的背景属性举例 例如:android:background="@color/color_app_bg" * * 属性名称:background * 属性值:@color/color_app_bg * 资源ID:@color/color_app_bg的ID值,int类型,通过.arsc文件的一套关系映射规则生成 * 资源名称:color_app_bg * 资源类型:background */ private void parseSkinAttr(Context context, AttributeSet attrs, View view) { for (int i = 0; i < attrs.getAttributeCount(); i++){ // 属性名称 String attrName = attrs.getAttributeName(i); // 属性值 String attrValue = attrs.getAttributeValue(i); //带@的资源文件 if(attrValue.startsWith("@")){ try { // 资源ID int id = Integer.parseInt(attrValue.substring(1)); // 资源名称 String entryName = context.getResources().getResourceEntryName(id); // 资源类型 String typeName = context.getResources().getResourceTypeName(id); } catch (NumberFormatException e) { e.printStackTrace(); } catch (NotFoundException e) { e.printStackTrace(); } } } }
解析每一个attr并将attr的属性名称、资源ID、资源名称、资源类型和对应的view用实体保存起来,以便收到换肤指令时对所有view换肤
从皮肤包(实际上是另外一个apk)中读取属性值对应的资源文件
/**
* 以下代码可以获取到一个资源ID
* 该方法可以获取其他安装包的资源ID,换肤就是利用该方法获取皮肤包的资源文件实现的
*
* @param resName String类型的资源名称
* @param defType String类型的资源类型
* @param skinPackageName String类型的包名
*
* @return 返回资源ID
*/
int resId = mResources.getIdentifier(resName, defType, skinPackageName);
为页面上每一个元素重新设置一遍皮肤包的资源文件
// 设置背景资源ID(皮肤资源)
view.setBackgroundColor(resId);
// 设置文本颜色资源ID(皮肤资源)
view.setTextColor(resId);
// 设置图标资源ID(皮肤资源)
view.setImageResource(resId);
代码思想:
以上就是护肤的所有思想啦,如果看到这还没有理解,没关系,我们还可以通过阅读代码来弄清楚它的原理,文末会附上源代码地址。代码整体结构清晰,配合本文阅读代码,相信很快就能理顺。
简单介绍下以上皮肤方案的代码结构:app、lib、制作好的皮肤包:
app模块------我们的app应用----------------------------------------------com.android.application(主工程)
lib模块--------封装换肤功能------------------------------------------------com.android.library(为App模块服务)
皮肤包------放一些需要替换的res文件----------------------------------com.android.application(主工程,打包后放在服务器或其他路径供app取用)
该框架的优点是只需导入皮肤包就可换肤,但也存在很多局限,例如不能处理动态添加的组件、不能支持自定义view…。可借鉴
Android 换肤方案详解(二)中的解决思想。
附上开源库链接:Android-Skin-Loader (github)
码云转存链接(解决拉代码慢问题):Android-Skin-Loader (gitee)
如果你正好使用该框架进行换肤,并在实践中遇到了问题,或许下面这篇文章可以给你带来一些思路
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。