Android主题主动适配方案
in Android with 0 comment

原生默认支持黑夜白天主题自适应,只要应用主题设置DayNight即可
若存在场景,需要做类似于换肤功能,需要自行获取资源,原生无法支持完全

基于原生night适配

Android主题机制

可自动改变样式的前提是使用DayNight主题
当系统主题切换时,会去自动去XXXX/XXXX-night目录寻找资源。
如果只使用LightDark主题,则无法进行适配,只会去对应资源目录找资源。

TODO:源码分析主题切换原理

Android原生获取资源代码

可以看到,我们常规用的获取资源方法,直接传入id
即使getDrawable(@DrawableRes int id)已经废弃,我们使用新方法,第二个参数也可以直接传入null进行获取资源。
我们可以关注这些2入参方法,第二个入参Theme

public final Drawable getDrawable(@DrawableRes int id) {
    return getResources().getDrawable(id, getTheme());
}
public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme) throws NotFoundException {
    return getDrawableForDensity(id, 0, theme);
}
public final int getColor(@ColorRes int id) {
    return getResources().getColor(id, getTheme());
}
public final ColorStateList getColorStateList(@ColorRes int id) {
    return getResources().getColorStateList(id, getTheme());
}

手动获取主题资源的方式

1. 设置主题,获取资源

基于上一步中的方法,默认重载方法的第二个参数传入getTheme(),即获取当前主题,然后去取当前主题的资源显示。
基于此我们可以先进行主题设置,再获取资源。

setTheme(androidx.appcompat.R.style.Base_Theme_AppCompat_Light);
getDrawable(R.drawable.bg_seek_bar);
setTheme(androidx.appcompat.R.style.Base_Theme_AppCompat_Dark);
getDrawable(R.drawable.bg_seek_bar);

设置为Light主题,将自动获取drawable目录下的资源
设置为Dark主题,自动获取drawable-night目录资源,若获取不到,仍然会返回drawable目录同名资源
缺陷:
必须要更换当前主题才可获取到对应的资源。

2. 构建主题对象,通过主题对象获取

因为允许接受其他Theme对象,不一定非要使用getTheme去做,可以根据主题手动构建对象,去获取资源。

Resources.Theme theme = getResources().newTheme();
theme.applyStyle(androidx.appcompat.R.style.Base_Theme_AppCompat_Light, true);
getDrawable(R.drawable.bg_seek_bar,theme);

通过getResources().newTheme()创建一个空的theme对象,之后应用主题style,便可以通过手动传入主题的方式获取对应主题的资源。


拼接资源后缀适配

原生night主题有着最大的限制就是无法适配2个以上的主题样式。如果需要适配多个主题,可使用该方案

Android资源包限制

虽然原生Android支持设置多个res目录,但是仍然无法重名
因为在打包时仍然会把多个res打包在一起,这就导致该方式无法像原生黑夜主题一样使用同名方式去适配。

拼接后缀获取资源

1. 定义多个res目录

虽然最终会打包在一起,但是为了方便维护与迭代,创建多个res目录还是有必要的
先创建一个资源包用于放置特定主题资源
image.png
gradle文件定义sourceSets

android {
    ...
    sourceSets {
        main.res.srcDirs += 'src/main/res_green'
    }
    ...
}

2. 资源拼接后缀

如常规drawable的按钮资源名叫switch_enable``switch_disable
res_green的文件名统一调整为switch_enable_g``switch_disable_g
所有的资源名按照XXX_g的方式命名

3. 获取资源

int id = R.drawable.bg_seek_bar;
// 获取文件名,getResourceName方法获取的名称包含包名和类型名,此处不适用
String name = getResources().getResourceEntryName(id);
if (isGreenTheme) {
    name+="_g";
}
// 第二个参数需要指定类型
int greenId = getResources().getIdentifier(name,"drawable", getPackageName());
if (greenId > 0) {
    id =greenId;
}

缺点:

  1. 资源需要改命名比较繁琐
  2. 资源id和名称多次转换,效率不高

三方主题包

三方有很多Skin相关的sdk,通过资源打包成文件的方式引入
后续补充三方资源
缺点:
依赖三方,额外引入资源

Responses