当前位置:   article > 正文

Android 换肤方案详解(一)

android 换肤

引言

在我们的开发中,也许有些项目会有换肤的需求,这个时候会比较头疼怎么做才能做到一键换肤呢?大家肯定希望越简单越好。下面我们基于Github上比较受欢迎的Android-Skin-Loader框架分析一下换肤的本质是什么?

原理

换肤,其本质无非就是更换页面元素(view或viewGroup)的属性值,这些属性值都是可以用资源文件表示的,换句话说换肤其实就是替换掉资源文件。比如换个背景,换个文字颜色等。

先看一组换肤:
在这里插入图片描述
分析上面的平板应用换肤其中的一个页面,绿色和蓝色风格只有背景图片、控件颜色改变了。

再看一张控制流程图了解大概思路:
在这里插入图片描述
上图大致讲解了换肤的原理,即通过对页面下的所有view重新设置一遍资源文件,而这些资源文件我们可以把它制作成皮肤包(即apk)。

也许通过上面这张流程图你还是不能完全看懂每一个工作流程,下面配合代码详细介绍一下:

  1. 遍历页面下所有元素及其属性集合
    通过在页面(Activity、FragmentActivity)中设置Factory,该Factory能拿到页面下所有viewattrs

    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);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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)
    	}
    
    • 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
  2. 解析每个元素下的所有属性(背景、文本颜色、图标等)

    	/**
    	 * 为方便理解,我拿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();
    				}
    		    }
    		}
    	}
    
    • 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
  3. 解析每一个attr并将attr的属性名称、资源ID、资源名称、资源类型和对应的view用实体保存起来,以便收到换肤指令时对所有view换肤

  4. 从皮肤包(实际上是另外一个apk)中读取属性值对应的资源文件

    	/**
    	 * 以下代码可以获取到一个资源ID
    	 * 该方法可以获取其他安装包的资源ID,换肤就是利用该方法获取皮肤包的资源文件实现的
    	 *
    	 * @param resName String类型的资源名称
    	 * @param defType String类型的资源类型
    	 * @param skinPackageName String类型的包名
    	 * 
    	 * @return 返回资源ID
    	 */
    	int resId = mResources.getIdentifier(resName, defType, skinPackageName);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
  5. 为页面上每一个元素重新设置一遍皮肤包的资源文件

    	// 设置背景资源ID(皮肤资源)
    	view.setBackgroundColor(resId);
    	// 设置文本颜色资源ID(皮肤资源)
    	view.setTextColor(resId);
    	// 设置图标资源ID(皮肤资源)
    	view.setImageResource(resId);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

代码思想:

  1. 封装一个lib,统计页面上所有view和view下attr集
  2. 读取指定apk(皮肤包)的R文件
  3. 将读取到的R文件设置view的每一个attr

以上就是护肤的所有思想啦,如果看到这还没有理解,没关系,我们还可以通过阅读代码来弄清楚它的原理,文末会附上源代码地址。代码整体结构清晰,配合本文阅读代码,相信很快就能理顺。

代码结构

简单介绍下以上皮肤方案的代码结构: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)

后续

如果你正好使用该框架进行换肤,并在实践中遇到了问题,或许下面这篇文章可以给你带来一些思路

Android 换肤方案详解(二)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/86889
推荐阅读
相关标签
  

闽ICP备14008679号