Android实现夜间模式的方法(二)

该文章接上篇 Android实现夜间模式的方法(一)

三.夜间模式的实现方案——单纯夜间模式

1.通过切换主题实现

这是通过创建一套夜间模式的主题,然后在设置时进行切换。在Activity中有一个方法叫setTheme(),可以设置Activity的Theme,当然Application类中也有相同的方法,也可以在Application中设置当前应用的Theme。

首先在attrs.xml中,为需要随theme变化的内容设置自定义属性

<resources>
    <attr name="textColor" format="color|reference" />
    <attr name="mainBackground" format="color|reference" />
</resources>

然后定义自己的夜间模式的风格,示例如下:

<!-- 夜间 -->
    <style name="ThemeNight" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="mainBackground">#000000</item>
        <item name="textColor">#ffffff</item>
    </style>

接下来只要在布局文件中使用对应的值,通过?attr/属性名的方法就可以实现根据风格设置不同的属性的目的。

这种方法是是Android官方推荐的方式,较为简单常用但这种方式有一些不足,规模较大的应用,需要随theme变化的属性会很多,都需要逐一定义。

2.通过修改资源id映射方式实现

这种方法的思路是通过id获取资源时,先将其转换为夜间模式对应id,再通过Resources来获取对应的资源。

public static Drawable getDrawable(Context context, int id) {
    return context.getResources().getDrawable(getResId(id));
}

public static int getResId(int defaultResId) {
    if (!isNightMode()) {
        return defaultResId;
    }
    if (sResourceMap == null) {
        buildResourceMap();
    }
    int themedResId = sResourceMap.get(defaultResId);
    return themedResId == 0 ? defaultResId : themedResId;
}

可以通过HashMap将白天模式的resId和夜间模式的resId来一一对应起来

private static void buildResourceMap() {
    sResourceMap = new SparseIntArray();
    sResourceMap.put(R.drawable.common_background, R.drawable.common_background_night);
    // ...
}

3.通过使用系统提供的夜间模式主题实现

在Support Library 23.2.0添加了一条新的变化 AppCompat 现在有个新的主题:Theme.AppCompat.DayNight. 这个主题可以根据系统时间切换 Theme.AppCompat(暗色) 和 Theme.AppCompat.Light(亮色) 两种主题。这将会对应用的用户特别有用,特别是阅读类应用。需要注意的是,这个特性只支持 API v14 及以上的 Android 设备,在 API v14 以下的设备则会默认使用亮色的主题。而且activity必须继承要AppCompatActivity。

首先得先在你的 style.xml 文件里,让主题继承 DayNight 主题,例如:

<style name="AppTheme" parent="Theme.AppCompat.DayNight.DarkActionBar">

然后在 Application 添加一个静态代码块来进行初始化全局设置:

static {
    AppCompatDelegate.setDefaultNightMode(
        AppCompatDelegate.MODE_NIGHT_AUTO); 
}
然后使用自定义资源,只需要在 res 目录下创建对应的 values-night 文件夹并创建对应的 themes.xml 文件,重新定义夜间模式的主题风格,例如:
res/values-night/colors.xml
<color name="colorPrimary">#201D45</color>
<color name="colorPrimaryDark">#201D45</color>

同理其他资源你只需要在文件的末尾添加 -night 系统就会自动加载对应的文件了。
实现的效果如下示例:

效果示例.png

4.通过使用开源框架实现——Android-Skin-Loader

Android-Skin-Loader是一个通过动态加载本地皮肤包进行换肤的皮肤框架
使用方法:

  1. 在Application中进行初始化
public class SkinApplication extends Application {
   public void onCreate() {
        super.onCreate();
        // Must call init first 
        SkinManager.getInstance().init(this);
        SkinManager.getInstance().load();
    }
}
  1. 在布局文件中标识需要换肤的View
    xmlns:skin="http://schemas.android.com/android/skin"
  2. 继承BaseActivity或者BaseFragmentActivity作为BaseActivity进行开发
  3. 从.skin文件中设置皮肤
SkinManager.getInstance().load(skin.getAbsolutePath(),
                new ILoaderListener() {
//重写该类下的方法
});

皮肤包(后缀名为.skin)的本质是一个apk文件,该apk文件不包含代码,只包含资源文件

在皮肤包工程中(示例工程为skin/BlackFantacy)添加需要换肤的同名的资源文件,直接编译生成apk文件,再更改后缀名为.skin即可(防止用户点击安装)

使用gradle的同学,buildandroid-skin-loader-skin工程后即可在skin-package目录下取皮肤包(修改脚本中def skinName = "BlackFantacy.skin"换成自己想要的皮肤名)

Load包下的两个类
SkinInflaterFactory: 搜集需要的换肤的控件,并创建相应的换肤控件,并把需要换肤的空间及其相应支持的换肤属性存储起来。

SkinManager: 其内部通过反射调用AssetManager.addAssetPath()把外部的皮肤资源加载到AssetManager中,并通过该AssetManager创建相应的Resource。当执行换肤操作的时候,就可以设置需要换肤View的相关属性为Resource中相应的资源。

5.通过使用开源框架实现——NightModel

NightModel是个方便切换夜间模式的库,利用官方夜间模式,同时不用重启Activity
使用方法
1.在appication中初始化
2.只需要在需要刷新的activity中调用attach、detach方法。其它activity不需要调用此方法
3.切换时调用appleyDayModel\appleyNightModel进行夜间模式切换

使用要求
官方包support appcompat 23.2.0 或以上版本
activity 需要继承自 AppCompatActivity
应用按照官方的夜间模式实现

6.通过使用开源框架实现——MultipleTheme

MultipleTheme是一个支持无缝换肤/夜间模式的Android框架,配合theme和换肤控件框架可以做到无缝切换换肤(无需重启应用和当前页面)。需要在换肤/切换夜间模式的界面只需要使用框架里的自封装控件,其他界面的控件使用原生android控件即可。

第一步:在项目的attr.xml声明自定义属性(各种模式都会用到的属性)
第二步:在项目的style.xml指定各种模式主题下的自定义属性值
第三步:在页面布局文件里使用自定义属性值
第四步:在基类的onCreate方法里添加切换主题模式的逻辑代码
第五步:调用工具类方法切换主题模式
第六步:针对切换主题模式时需要立即更新页面ui的页面,需要使用框架里的封装控件

MultipleTheme的内部代码1.png
MultipleTheme的内部代码2.png

推荐阅读更多精彩内容