不销毁activity实现白天黑夜主题切换
Android activity 加载布局文件流程
一.onCreate初始化
AppCompatActivity.onCreate 先调用getDelegate() 创建 AppCompatDelegateImplN(最终继承AppCompatDelegateImplV9->AppCompatDelegateImplBase)对象 delegate,然后调用 delegate.installViewFactory()
AppCompatDelegateImplV9 实现接口LayoutInflater.Factory2
->AppCompatDelegateImplV9.installViewFactory() 先调用 layoutInflater = LayoutInflater.from(mContext) 获取LAYOUT_INFLATER_SERVICE 服务,然后调用LayoutInflaterCompat.setFactory2(layoutInflater, this)。
ps:LAYOUT_INFLATER_SERVICE 服务 是ContextImpl类加载时调用 SystemServiceRegistry.createServiceCache() new PhoneLayoutInflater(ctx.getOuterContext())创建的,PhoneLayoutInflater继承 LayoutInflater。
->LayoutInflaterCompat.setFactory2 调用IMPL.setFactory2(inflater, factory), 这里的IMPL 为LayoutInflaterCompatApi21Impl.
->LayoutInflaterCompatApi21Impl.setFactory2(inflater, factory) 方法只是调用inflater.setFactory2(factory) 这里inflater为服务端的 PhoneLayoutInflater对象,factory为AppCompatDelegateImplV9对象.调用服务端接口:
->LayoutInflater.setFactory2(factory) 将客户端的 AppCompatDelegateImplV9对象 赋值给服务端变量 mFactory2
二.setContentView 加载布局文件
AppCompatActivity.setContentView(int layoutResID) 执行2步
步骤1 :调用ensureSubDecor() 创建SubDecor,并将SubDecor添加到了DecorView
步骤2: 调用getDelegate().setContentView(layoutResID) 这里getDelegate() 获取的是上面创建的 AppCompatDelegateImplN
->AppCompatDelegateImplV9.setContentView(int resId) 调用LayoutInflater.from(mContext).inflate(resId, contentParent) 执行服务端 inflate方法:
->LayoutInflater.inflate( int resource, ViewGroup root) 调用 inflate(parser, root, root != null)
->LayoutInflater.inflate( int resource, ViewGroup root, boolean attachToRoot) 基于布局资源id resource获取 parser
->LayoutInflater.inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) 执行2步
1. 调用createViewFromTag(root, name, inflaterContext, attrs,false) 实例化根节点view
2.调用rInflateChildren(parser, temp, attrs, true)该方法遍历xml布局控件 具体流程如下:
->LayoutInflater.rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate)
->LayoutInflater.rInflate(XmlPullParser parser, View parent, Context context,AttributeSet attrs, boolean finishInflate) 该方法遍历布局中 控件节点,调用createViewFromTag(parent, name, context, attrs) ,name为控件名称如:Button
->LayoutInflater.createViewFromTag(View parent, String name, Context context, AttributeSet attrs,boolean ignoreThemeAttr)
该方法调用view = mFactory2.onCreateView(parent, name, context, attrs) mFactory2 为客户端 对象AppCompatDelegateImplV9,调用客户端mFactory2接口:
->AppCompatDelegateImplV9.onCreateView(View parent, String name, Context context, AttributeSet attrs) 调用createView(parent, name, context, attrs)
->AppCompatDelegateImplV9.createView(View parent, String name, Context context, AttributeSet attrs) 调用new AppCompatViewInflater() 创建mAppCompatViewInflater,
然后调用mAppCompatViewInflater.createView(parent, name, context, attrs...)
->AppCompatViewInflater.createView(parent, name, context, attrs...)
基于控件名称name 创建AppCompat控件 并作为方法返回值返回,例如:name 为 Button 调用new AppCompatButton(context, attrs)创建AppCompatButton ,
不销毁activity实现白天黑夜主题切换思路:
既然LAYOUT_INFLATER_SERVICE 服务端加载布局文件遍历控件时候会调用客户端传递的 Factory2接口对象的onCreateView方法,
那我们可以通过参考源码自定义Factory2 接口实现类,并将该实现类设置到 服务端,这样就可以获取到所有的布局文件中的控件对象了,我们可以先缓存下来这些控件。
当收到白天黑夜模式切换的回调时,重新加载所有控件的背景,字体颜色等就可以完成换肤操作。
具体操作步骤
一、创建自定义类:
1.参考AppCompatDelegateImplV9 创建 CustomSkinLayoutInflaterFactory 继承 LayoutInflater.Factory2, 实现 Factory2 接口方法,
2.参考LayoutInflaterCompat 创建 LayoutInflaterCompat , 封装 LayoutInflater服务端接口调用 。
3.创建所有AppCompat控件子类,例如:创建类 CustomSkinAppCompatButton 继承 AppCompatButton 实现 自定义接口uiModeChangeListener.applyCustomSkin方法。
控件收到回调后,控件中调用 setBackground,setPadding,setTextColor,setHintTextColor等设置资源的颜色的操作,实现换肤,
4.参考 AppCompatViewInflater 创建自定义类 CustomSkinAppCompatViewInflater ,系统遍历布局中的所有控件会通过Factory2调用该类createView方法, 这里可以考虑扩展自定义View的Inflater 也是要有 createView方法
5.创建CustomSkinCompatResources 用来缓存所有的自定义的 AppCompatViewInflater
二、应用初始化时候操作:
1.注册所有 activity监听
2.向 CustomSkinCompatResources 缓存所有CustomSkinLayoutInflater 例如: CustomSkinAppCompatViewInflater (转换常规控件android.widget) 等
三、Activity onCreate初始化
通过一注册的acitivity 监听,当onActivityCreated 时候 为当前activity 创建 CustomSkinLayoutInflaterFactory 并缓存到map中,调用inflater.setFactory2(CustomSkinLayoutInflaterFactory) 向服务端设置 自定义的 Factory2
布局加载时候回遍历所有的view控件 调用 Factory2.onCreateView
CustomSkinLayoutInflaterFactory.onCreateView
调用 getInflaters 获取缓存所有CustomSkinLayoutInflater 调用 inflater.createView,例如:
->CustomSkinAppCompatViewInflater.createView 最终 返回创建的CustomSkinCompat控件对象,例如:CustomSkinCompatButton
CustomSkinLayoutInflaterFactory.onCreateView 就可以缓存布局中所有的CustomSkinCompat控件。
四、白天黑夜切换
保证白天黑夜切换activity不销毁,需要在AndroidManifest.xml android:configChanges="uiMode",同时在acvitivity 重载onConfigurationChanged 方法。白天黑夜切换时候会回调
CustomSkinActivity.onConfigurationChanged 调用CustomSkinManager.getInstance().uiModeChange(this)
CustomSkinLayoutInflaterFactory.applyCustomSkin() 遍历前面缓存布局中所有的CustomSkinCompat控件 调用applyCustomSkin。例如:
CustomSkinCompatButton.applyCustomSkin 控件中调用 setBackground,setPadding,setTextColor,setHintTextColor,setCompoundDrawablesWithIntrinsicBounds
这样就完成了不销毁activity实现 白天黑夜主题资源的切换。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术