Android 白天黑夜模式切换换肤

简介

Android换肤操作,我的理解就是高级的setTextColor、setBackground等操作,设置可以通过getWindow().getDecorView()获取xml布局中所有的View然后一个个判断、设置。但是这样做的效率十分低下。我们知道android的一个个View都是通过LayoutInflater渲染出来的,那能不能在一开始的时候就给View设置监听,让他们在activity加载后能随着操作的改变而改变呢?这是可以的!

首先从源码onCreate进入,康康View怎么被从xml加载成java对象的。

父类AppCompatActivity的onCreate实现:
在这里插入图片描述
这里分为2部分,一部分是调用:

delegate.installViewFactory();

另一部分是继续调用父类的onCreate方法:

super.onCreate(savedInstanceState);

跟进delegate.installViewFactory();:
在这里插入图片描述
抽象类,看看实现,实现它的类是AppCompatDelegateImpl:
在这里插入图片描述
这里的内容就比较清晰了:先根据mContext获取LayoutInflater,然后通过LayoutInflaterCompat辅助类对LayoutInflater设置Factory2,这个Factory2是个接口,有它又继承了Factory接口,这种写法可以看出这是Factory的2.0版本:

Factory(Factory1.0):

Factory2(Factory2.0)

回到AppCompatDelegateImpl,跟进setFactory2:
在这里插入图片描述
在SDK版本小于21会做一些兼容设置,主要看inflater.setFactory2(factory):
在这里插入图片描述
可以发现当mFactorySet属性为true时被抛出异常,异常信息是:之前有一个Factory被设置到当前这个LayoutInflater了,所以不能再设置了,这也是很多博客中,再重新设置Factory时,会通过反射的方式将mFactorySet的值改为false,然后将自定义的Factory2给LayoutInflater进行设置。之后的FactoryMerger可以不会跟进了。
回到AppCompatDelegateImpl的installViewFactory方用,可以发现:
在这里插入图片描述
说明AppCompatDelegateImpl本身实现了Factory2接口,来看看它的实现方法:
在这里插入图片描述
可见它调用的是createView方法:

在这里插入图片描述
可以看出这里就是尝试着各种方法来生成mAppCompatViewInflater,这是个AppCompatViewInflater对象,然后调用它的createView方法:
在这里插入图片描述
createView方法,顾名思义就是创造View的方法,它是通过switch匹配名字,然后根据不同的名字创造不同的View控件对象:
在这里插入图片描述
到此delegate.installViewFactory()就结束了,接下来查看父类FragmentActivity的onCreate方法:
在这里插入图片描述
继续进入父类ComponentActivity的onCreate方法:
在这里插入图片描述
可见这里调用了setContentView:
在这里插入图片描述
这里调用了getWindow().setContentView(layoutResID)和initWindowDecorActionBar(),
initWindowDecorActionBar()从名字可见看出应该是一个actionbar的初始化方法,可以不用深究。而getWindow()得到的Window是个抽象类,它的唯一实现方法是PhoneWindow,因此这里调用的就是PhoneWindow的setContentView方法:
在这里插入图片描述
这里的有个重要的点是:
在这里插入图片描述
生成最外层的ViewGrop,接下来的View或ViewGrop都会是它的子类。它的生成有兴趣可以自行查看。

继续跟进重要的方法:

mLayoutInflater.inflate(layoutResID, mContentParent);

在这里插入图片描述
可以看出源码中会生成XmlResourceParser,然后使用这个工具来将xml文件中的节点控件一个个加载成java对象,进入inflate方法:(未贴全)
在这里插入图片描述
首先通过Xml解析器的getName方法获取控件的节点名字,然后根据名字进入不同的逻辑,这里进入的else的逻辑,如果是</merge/>节点才会走if逻辑。这里通过createViewFromTag方法(createViewFromTag方法稍后再进入)生成了一个temp的View,然后往下看:
在这里插入图片描述
它的英文注释已经很明显了:渲染所有temp下的子view,进入方法:
在这里插入图片描述
可以发现最下面它又调用了rInflateChildren,因此这是一个递归操作!它的View是怎么生成的?是调用接下来要进入的createViewFromTag方法。

进入createViewFromTag方法:
这个方法十分重要!!
在这里插入图片描述
在这里插入图片描述
首先通过tryCreateView来生成view, 而tryCreateView是通过调用mFactory2的onCreateView方法来来生成view的,如果view为null的话,那么才会调用LayoutInflater的onCreateView方法或着createView方法

二 换肤操作

到这源码分析就可以告一段落了,最重要的是就是这个tryCreateView方法,一种做法是新建一个类去实现mFactory2方法,然后将LayoutInflater.setFacotry2设置为它,那么tryCreateView就不会返回空,也就不会执行LayoutInflater自己的onCreateView和createView方法了,如:

但是其实没有必要这么做,因为Acitivty本身就实现了Factory2接口,因此它可以自己实现Factory2接口中的所有方法:
在这里插入图片描述
在调用super.onCreate之前就将自己实现的mFactory2设置给layoutInflater:
在这里插入图片描述
这里的CustomLayoutInflater继承于AppCompatViewInflater,它模仿源码中的实现:
在这里插入图片描述
通过不同的控件name来生成并返回不同的自定义控件,如果view返回null怎么办?那么之前说的tryCreateView就会返回空,就会调用LayoutInflater自己实现的onCreateView方法了。

以自定义控件SkinLinearLayout为例:

在values中新建一个attrs文件,然后创建想要修改的值:
在这里插入图片描述

在这里插入图片描述
继承于LinearLayout,然后让他实现自定义接口ViewMatch,这个接口的用处是用来标记那些要换肤的控件。

在构造方法中就可以获取到属性名id和属性资源id,并将他们保存起来,通过obtainStyledAttributes方法的第3个参数可以选定想要的几个参数:第一个参数返回所有的属性,但是一些属性并不是需要的,比如layout_width,layout_height这种属性一般不会去修改它(当然如果你想要修改也行)
在这里插入图片描述
通过SparseIntArray将属性名id和属性资源值保存成key,value形式的数据。

当点击换肤按钮时:

在这里插入图片描述
首先获取顶层控件,然后调用applyForDayNightMode方法,这个方法是遍历所有的子view,如果是实现了自定义接口的话就调用各自的换肤方法skinChange()(从这里可以看出之前的自定义接口就是一个“标记”的作用),如果是ViewGrop,就递归地一层一层往下处理。
在这里插入图片描述

最后的设置方法:
在这里插入图片描述
根据之前保存的数据根据key去除相应的属性值设置就行了。

这个属性值存储在:

在这里插入图片描述

getDelegate().setLocalNightMode(nightMode);
通过调用系统APIsetLocalNightMode来设置白天、黑夜模式传入的参数为:

AppCompatDelegate.MODE_NIGHT_NO 白天模式
AppCompatDelegate.MODE_NIGHT_YES 黑夜模式

注意上面的colors.xml(night),当系统为night模式时,即 getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES),读取它里面的资源信息,当为白天模式,读取colors.xml里的资源信息。

总结

1、首先通过setFactory2将系统实现的Factory2接口替换为自己实现的接口,然后xml布局文件中不同的控件名称生成对应的自定义控件。
2、当换肤按钮按下时,先通过系统APIsetLocalNightMode设置白天黑夜模式,然后从DecorView循环往下遍历所有的子View或子子View或。。。判断是否实现了自定义的接口,如果实现了,那么调用各自实现接口的方法。
3、实现的方法中获取控件想要实现的属性值和资源ID然后进行设置,这个资源ID是从
values(白天模式访问这个文件下)或values-night(黑夜模式访问这个文件下)读取的。

但是这样就只要2种模式,有没有多姿多彩的各种绚丽的皮肤包能切换呢?可以参考:
https://editor.csdn.net/md/?articleId=106454713

demo地址:https://github.com/lyx19970504/Dynamic-Skin-Change

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哒哒呵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值