Android夜间模式
Android夜间模式
Android 夜间模式
夜间模式需求:
- 改动少,侵入性小
- 不需要重启 Activity
1、UiModeManager(android 官方)
弊端:要开启车载模式
2、Change Theme(需 restart Activity)
通过定义两套 Theme,在切换模式的时候,保存 Theme Id,重建 Activity。在 Activity 中,在 setContentView() 之前,读取保存的 Theme Id,再 setTheme(Theme id) 一下即可。
缺点:
需要重启 Activity,界面会闪烁
可参考
MultipleTheme
开源库,不需要重启页面即可实现夜间模式的切换
3、NightModeHelper
知乎简书实现方案
- 定义两套 Theme,color 等
- 在布局中引入属性 (?attr/) 的值
- 代码中重新设置对应模式的值;其他页面根据 setTheme() 判断。
资源 id 映射的方式
通过 id 获取资源时,先将其转换为夜间模式对应 id,再通过 Resources 来获取对应的资源。
缺点:
每次添加资源都需要建立映射关系
Material Design 实现夜间模式
1
2
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGTH_NO);
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGTH_YES);
自定义 LayoutInflaterFactory
自定义 Factory2 应用 – 动态换肤
注意
androidP(28) 以上的,如何实现 Factory2 的多次设置?反射修改 mFactorySet
值实现了
多个 Factory2 怎么实现兼容?如何兼容 AppCompatDelete 设置的 Factory2 的功能?
换肤基础
Resources
- String getResourceEntryName(@AnyRes int resid) 通过 resId 获取资源的 name,对应 apk 中的
resources.arsc
的 name - String getResourceTypeName(@AnyRes int resid) 通过 resId 获取资源的 type
- int getIdentifier(String name, String defType, String defPackage) 通过
package:type/entry
获取资源 id
AssetManager
- int addAssetPath(String path) 添加 path 资源到 AssetManager,过时,不能反射
- void setApkAssets(@NonNull ApkAssets[] apkAssets, boolean invalidateCaches) 替代 addAssetPath
自定义 Factory2
用于收集要换肤的 view
系统 createView 流程:
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
33
34
35
36
37
38
// LayoutInflater
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
// ...
try {
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
// ...
} catch (Exception e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
}
}
ActivityLifecycleCallbacks
资源加载流程
主要流程:
- 注册
ActivityLifecycleCallbacks
监听 Activity 的创建,在每个 Activity 创建时,创建一个 Factory2 并记录 - 自定义
Factory2
,收集需要换肤的 View,属性(Factory2 可能需要多次应用,需要反射修改 Factory2 的判断值,默认一个 Activity 只能设置一次,注意 AndroidP(28) 不能反射修改mFactorySet
值了),里面保存了所有要换肤的 View 和属性 - 换肤操作时替换掉系统的 Resource,替换为换肤的 Resource,从而实现换肤时调用资源是皮肤包的
- 应用观察者模式,SkinManager 为 Observable(被观察者),Factory2 为观察者,换肤操作时,通知各个观察者应用换肤
Ref
https://github.com/fengjundev/Android-Skin-Loader
糗百用
本文由作者按照 CC BY 4.0 进行授权