Android 换肤方案详解(一)
引言
在我们的开发中,也许有些项目会有换肤的需求,这个时候会比较头疼怎么做才能做到一键换肤呢?大家肯定是希望只要一行代码就能调用最好。下面我们先分析一下换肤的本质是什么?
原理
换肤,其本质无非就是更换页面元素(view或viewGroup)的属性值,这些属性值都是可以用资源文件表示的,换句话说换肤其实就是替换掉资源文件。比如换个背景,换个文字颜色等。
先看一组QQ换肤:
分析上面的QQ换肤其中的一个页面,白天和夜间风格只有背景颜色、文字颜色、小图标改变了。
再看一组换肤:
分析上面的平板应用换肤其中的一个页面,绿色和蓝色风格只有背景图片、控件颜色改变了。
方案
先看一张控制流程图了解大概思路:
上图大致讲解了换肤的原理,即通过对页面下的所有view重新设置一遍资源文件,而这些资源文件我们可以把它制作成皮肤包(即apk)。
也许通过上面这张流程图你还是不能完全看懂每一个工作流程,下面配合代码详细介绍一下:
遍历页面下所有元素及其属性集合
通过在页面(Activity、FragmentActivity)中设置Factory,该Factory能拿到页面下所有view和attrsActivity / FragmentActivity
/*** SkinInflaterFactory是自定义的Factory,实现了android.view.LayoutInflater.Factory* 创建view的事情委托给自定义工厂*/@Overrideprotected 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的属性*/@Overridepublic View onCreateView(String name, Context context, AttributeSet attrs) {// 根据name中包含的字段从下列3个包中选择构建出viewView 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 {// 资源IDint 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);
代码思想:
- 封装一个lib,统计页面上所有view和view下attr集
- 读取指定apk(皮肤包)的R文件
- 将读取到的R文件设置view的每一个attr
以上就是护肤的所有思想啦,如果看到这还没有理解,没关系,我们还可以通过阅读代码来弄清楚它的原理,文末会附上源代码地址。代码整体结构清晰,配合本文阅读代码,相信很快就能理顺。
代码结构
简单介绍下以上皮肤方案的代码结构:app、lib、制作好的皮肤包:
app模块------我们的app应用----------------------------------------------com.android.application(主工程)
lib模块--------封装换肤功能------------------------------------------------com.android.library(为App模块服务)
皮肤包------放一些需要替换的res文件----------------------------------com.android.application(主工程,打包后放在服务器或其他路径供app取用)
感言
之前我也做过换肤需求,当时也是琢磨了好久,因为需要考虑代码耦合度、扩展性,虽然最终解决了代码结构问题,但还是没能想到 打包成皮肤包 这种方案。这种方案的好处在于只需改动res下部分资源文件,大大节省了时间成本。希望本篇文章能帮助大家快速理解护肤思想,同时也希望大家多分享一些优秀的文章。码字不易,点个赞支持一下吧 hhh~~~
附上开源库链接:Android-Skin-Loader (github)
码云转存链接(解决拉代码慢问题):Android-Skin-Loader (gitee)
后续
如果你使用此方案在实践中遇到问题,可以参考以下这篇文章
Android 换肤方案详解(二)
Android 换肤方案详解(一)相关推荐
- Android可更换布局的换肤方案
换肤,顾名思义,就是对应用中的视觉元素进行更新,呈现新的显示效果.一般来说,换肤的时候只是更新UI上使用的资源,如颜色,图片,字体等等.本文介绍一种笔者自己使用的基于布局的Android换肤方案,不仅 ...
- 换肤方案,换肤策略,App插件式换肤实现方案
UI换皮肤或白天黑夜模式,从产品上来看,是两种不同产品设计模式:白天黑夜模式只有两种模式:而换皮肤可以有多套,可以进行商业化,并盈利. 换肤的本质就是去替换资源文件.我们知道,Android应用程序由 ...
- Android 主题切换/换肤方案 研究(四) - qq和qq空间
4. qq和qq空间 (独立app) 分析时用的是: 1. 夜神android模拟器(因为用android studio自带的模拟器运行x86架构的镜像提示不能安装qq空间,安装arm架构的镜像运行又 ...
- Android 应用换肤方案的总结
虽然现在已经有很多不错的换肤方案,但是这些方案或多或少都存在自己的问题.在这篇文章中,我将对 Android 现有的一些动态换肤方案进行梳理,对其底层实现原理进行分析,然后对开发一个新的换肤方案的可能 ...
- 对 Android 应用换肤方案的总结
作者:me 虽然现在已经有很多不错的换肤方案,但是这些方案或多或少都存在自己的问题.在这篇文章中,我将对 Android 现有的一些动态换肤方案进行梳理,对其底层实现原理进行分析,然后对开发一个新的换 ...
- Android面试Hash原理详解二
Hash系列目录 Android面试Hash原理详解一 Android面试Hash原理详解二 Android面试Hash常见算法 Android面试Hash算法案例 Android面试Hash原理详解 ...
- Android 源码编译详解【合集篇】
Android 源码编译详解[一]:服务器硬件配置及机型推荐 做 Android系统开发多年,开发环境都是入职就搭建好了,入职时拿个账号密码就直接开始搞开发了,年初换了新公司,所有的项目都是刚起步,一 ...
- 美团多渠道打包方案详解,速度快到白驹过隙
美团多渠道打包方案详解,速度快到白驹过隙 Andorid渠道市场有多分散呢?分散到比Android碎片化还严重,你还在为多渠道打包而头疼吗?美团提供了速度快到白驹过隙的多渠道打包方案.说的有点夸张,对 ...
- android ------- 开发者的 RxJava 详解
在正文开始之前的最后,放上 GitHub 链接和引入依赖的 gradle 代码: Github: https://github.com/ReactiveX/RxJava https://githu ...
最新文章
- 一种在注入进程中使用WTL创建无焦点不在任务栏出现“吸附”窗口的方法和思路
- [转]查看事物码相关的数据对象
- windows清理图标缓存并重新加载
- Linux网络/firewalld和netfilter/netfilter/iptables语法
- C#使用iTextSharp操作PDF文件
- Sourcetail 一款代码编辑神器,让看源码如丝般顺滑
- collector list 多个分组_【S01E07】groupby方法、GroupBy对象、groupby方法的分组键
- javascript中实例对象和构造函数关系、原型语法、原型链、call实现继承、apply调用函数、bind拷贝函数、拷贝继承、class类、super、严格模式、高阶函数、闭包、递归、es6简介
- (操作系统题目题型总结)第四章:存储管理
- 设计模式之:深入浅出 java 单例模式(Singleton)
- “碰瓷”特斯拉翻船,卡车界明星创企Nikola身陷“骗局”危机
- node.js中实现同步操作的3种实现方法
- viewpager初始化fragment没有绘制_NDK OpenGL ES渲染系列 之 绘制三角形
- Qt总结之十五:QByteArray详解
- 我大意了,刚一放出来就上了牛客网头条了
- 23种设计模式(二十)数据结构之迭代器
- 关于visual studio和vc版本之间的对应关系(更新至2020.07)
- C语言中期报告格式,本科论文中期报告范文_本科毕业论文中期报告模板(2)
- 【Oracle】ora-00932:数据类型不一致:应为 -,但却获得BLOB
- 美元汇率【贪心算法练习题】