前言
产品大佬又提需求啦,要求app
里面的图表要实现白天黑夜模式的切换,以满足不同光线下都能保证足够的图表清晰度. 怎么办?可能解决的办法很多,你可以给图表view增加一个toggle
方法,参数String,day/nigh
t,然后切换之后postInvalidate
刷新重绘.
OK,可行,但是这种方式切换白天黑夜,只是单个View
中有效,那么如果哪天产品又要另一个View换肤,难道我要一个一个去写toggle
么?未免太low了.
那么能不能要实现一个全app内的一键换肤?一劳永逸~~~
正文大纲
什么是一键换肤
界面上哪些东西是可以换肤的
利用HOOK技术实现优雅的“一键换肤"
相关android源码一览
5. "全app一键换肤" Demo源码详解
1、什么是一键换肤
所谓"一键",就是通过"一个"接口的调用,就能实现全app范围内的所有资源文件的替换.包括 文本,颜色,图片等。
一些换肤实现方式的对比:
方案1: 自定义View中,要换肤,那如同引言中所述,toggle方法,invalidate重绘。弊端:换肤范围仅限于这个View.
方案2:给静态变量赋值,然后重启Activity. 如果一个Activity内用静态变量定义了两种色系,那么确实是可以通过关闭Activity,再启动的方式,实现 貌似换肤的效果(其实是重新启动了Activity)弊端:太low,而且很浪费资源
也许还有其他方案吧,View
重绘,重启Activity
,都能实现,但是仍然不是最优雅的方案,那么,有没有一种方案,能够实现全app内的换肤效果,又不会像重启 Activity
这样浪费资源呢?请看下图:
![7775f0abb826bcf67a57c3a80fddfe42.gif](https://img-blog.csdnimg.cn/img_convert/7775f0abb826bcf67a57c3a80fddfe42.gif)
这个动态图中,首先看到的是Activity1
,点击换肤,可直接更换界面上的background
,图片的src
,还有textView
的textColor
,跳转Activity2
之后的textView
颜色,在我换肤之前,和换肤之后,是不同的。换肤的过程我并没有启动另外的Activity
,界面也没有闪烁。我在Activity1
里面换肤,直接影响了Activity2
的textView
字体颜色。
既然给出了效果,那么肯定要给出Demo,不然太没诚意,嘿嘿嘿。
github地址奉上:https://github.com/18598925736/HookSkinDemoFromHank
2、界面上哪些东西是可以换肤的
上面的换肤动态图,我换了ImageView
,换了background
,换了TextView
的字体颜色,那么到底哪些东西可以换?
答案其实就一句话: 我们项目代码里面 res目录下的所有东西,几乎都可以被替换。
(为什么说几乎?因为一些犄角旮旯的东西我没有时间一个一个去试验….囧)
具体而言就是如下这些:
动画
背景图片
字体
字体颜色
字体大小
音频
视频
3、 利用HOOK技术实现优雅的“一键换肤"
什么是hook?
如题,我是用hook实现一键换肤。那么什么是hook?
hook,钩子. 安卓中的hook技术,其实是一个抽象概念:对系统源码的代码逻辑进行"劫持",插入自己的逻辑,然后放行。注意:hook可能频繁使用java反射机制···
"一键换肤"中的hook思路
1 、"劫持"系统创建View
的过程,我们自己来创建View
系统原本自己存在创建View
的逻辑,我们要了解这部分代码,以便为我所用。
2、收集我们需要换肤的View
(用自定义view
属性来标记一个view
是否支持一键换肤),保存到变量中,劫持了系统创建view
的逻辑之后,我们要把支持换肤的这些view保存起来。
3、加载外部资源包,调用接口进行换肤,外部资源包是.apk
后缀的一个文件,是通过gradle
打包形成的。里面包含需要换肤的资源文件,但是必须保证,要换的资源文件,和原工程里面的文件名完全相同
。
4、 相关android源码一览
1、Activity 的 setContentView(R.layout.XXX) 到底在做什么?
回顾我们写app的习惯,创建Activity
,写xxx.xml
,在Activity
里面setContentView(R.layout.xxx)
。 我们写的是xml
,最终呈现出来的是一个一个的界面上的UI控件,那么setContentView
到底做了什么事,使得XML
里面的内容,变成了UI控件呢?
请看下图:
源码索引:setContentView(R.layout.activity_main);
->getDelegate().setContentView(layoutResID);
OK,这里暴露出了两个方法,getDelegate()
和setContentView()
。
先看getDelegate
:
这里返回了一个AppCompatDelegate
对象,跟踪到AppCompatDelegate
内部,阅读源码,可以得出一个结论:AppCompatDelegate
是替Activity
生成View
对象的委托类,它提供了一系列setContentView
方法,在Activity
中加入UI控件。
2、那它的AppCompatDelegate
的setContentView
方法又做了什么?
找到setContentView的具体过程:
那么就进入下一个环节:LayoutInflater
又做了什么?
LayoutInflater
这个类是怎么把layout.xml
的 变成
TextView
对象的?
我们知道,我们传入的是int
,是xxx.xml
这个布局文件,在R文件里面的对应int值。LayoutInflater
拿到了这个int
之后,又干了什么事呢?
一路索引进去:会发现这个方法:
发现一个关键方法:CreateViewFromTag
,tag
是指的什么?其实就是 xml里面 的标签头:里的TextView
。