android 暗黑模式项目适配过程

2022-12-25 13:45:17

 在Android 10 开始安卓开始支持暗黑模式,个人感觉是仿照ios 来做的,不过android 的碎片化比较严重,各个厂商定制严重,没有办法去强制推广,ios 在系统升级13 并且必须适配暗黑模式,看看吧,人家是强制.但是我们也不能落后啊.

1.如何开启android  10 中的暗黑模式

不同厂商对于叫法不太相同,但是大概位置都会放到  设置---->>  显示和亮度  ----->>  暗黑或者深色模式

我的华为mate20是这样的,具体以个人手机为准,安卓原生在 ---->设置--->显示----> dark theme,大家一看就懂。

2.啥是暗黑模式

        所谓的暗黑就是将我们的应用的主题色调到趋向于黑或者灰色,总体给人的感觉就是比较柔和不像白色高亮那么刺眼,还有更重要的一点,省电,降低功耗,现在手机的屏幕是越来越大,厚度也在降低,相对于先前的一些外观尺寸相同的手机,明显续航能力弱了很多,电池也小了,所以为了续航为了更好的体验总的做点啥吧,并且现在主流设备使用的多是 LED 屏和 OLED 屏,他俩的发光原理不同.

  LED :屏幕的显示模式为屏幕背光模组照亮整个屏幕,即便是显示纯黑色时背光模组也会工作

 OLED :屏幕的发光特性,显示纯黑色时像素是完全不发光,OLED 屏幕如果长时间显示深色像素时便会比浅色像素更加省电。

所以我们所说的的暗黑模式主要有两个方面的优点:

1.暗黑模式可以帮助对光线较为敏感,或视力有问题的用户看清楚手机,也可让所有用户在低光环境下更舒适地看屏幕

2.在OLED 屏上大幅度的降低电量的消耗,延长设备使用时间

适配原理:

       适配原理其实类似于我们适配横竖屏一样,我们创建一个暗黑模式文夹一般都带night 关键字,然后将相同名字的资源文件放到不同的文件夹下,系统会主动根据当前设备的模式去相应的文件夹找资源。

适配主要的几个点

1.文字颜色,不同模式下相同的组件的颜色会有变化,比如暗黑模式你的文字必须是以白色为主的,白色或者亮色模式下是以黑色为主,否则就会看不清或者看不到,这里是最大的坑,有些颜色是根据业务在代码中动态设置的,很有可能你的背景是白色的,然后你的字体颜色也是白的,这个就比较坑了,需要注意,因为我们的app本来就是默认暗色的,现在要把暗色主题等的默认改成白色的主题,然后适配暗黑模式,之前文字图片大多是白色的或者透明的,在把主题改为白色后文字就看不到了,所以要关注每个空间xml中和代码中两个地方的文字颜色修改,否则就看不到,因为白色的底和白色的字,这个鬼才看得见,所以最好的办法就是同一个页面在两种模式切换下,进行对比,当然这样成本比较高,页面太多,当然如果你的团队操作比较规范比如颜色是有过约束的,没有用到硬编码,那这个就比较好改,只要从源头改掉基本就完事了。

2.图片适配和文字基本是一个道理,除非使用了其他比较鲜艳的颜色图片,无论是暗黑或者亮白都可以看的很清楚或者视觉效果都不错

3.状态栏和导航栏修改,因为我们大多使用的是自定义的暗黑模式适配,所以状态栏是不会随着系统设置去改变的,所以在白色主题下我们要将状态栏调为黑色,暗色时调节为白色,当然了一般导航栏是和主题颜色一样的,一般不用改,只要配置好主题

4.主题修改,因为app 页面太多了,所以只改了一级和主要二级页面,所以需要将适配的去修改主题

5.切换模式生命周期重新执行了,带来的数据丢失,比较明显的是tab 页切换时,当前内容页和当前的tab 页标签对不上,这里要做一个数据保存

下面我们进入正题,从头开始撸。

1.颜色的定义 color

  在项目的res 目录下新建values-night 文件夹,然后在里边新建colors文件,定义如下颜色

 

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--    这个用于暗黑模式主色调-->
    <color name="main_bg_1">@android:color/black</color>
    <!--    这个用于普通亮色模式辅助色调-->
    <color name="main_bg_2">#1B1818</color>
    <!--    主要文字颜色 用于主标题等-->
    <color name="main_text_1">@android:color/white</color>
    <!--    次要文字颜色 用于副标题-->
    <color name="main_text_2">#999999</color>

</resources>

然后我们在values下的colors 下 创建相同的颜色标签名字,把 后边的颜色值改下

<!--    这个用于普通亮色模式主色调-->

    <color name="main_bg_1">@android:color/white</color>
    <!--    这个用于辅助背景色调-->
    <color name="main_bg_2">#999999</color>
    <!--    主要文字颜色 用于主标题等-->
    <color name="main_text_1">@android:color/black</color>
    <!--    次要文字颜色 用于副标题-->
    <color name="main_text_2">#1B1818</color>

这个是普通模式下的。

2.主题定义

然后们定义一个主题,并且使用该颜色值

    <style name="DarkTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
        <item name="colorPrimary">@color/main_bg_1</item>
        <item name="colorPrimaryDark">@color/main_bg_1</item>
        <item name="colorAccent">@color/main_bg_1</item>
    </style>

 

然后在你需要适配的activity mainfist 中使用该主题,注意主题必须是DayNight 类型主题的子类

android:theme="@style/DarkTheme"

3.代码及xml 使用

在xml 中使用

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/main_bg_2"
        android:orientation="vertical"
        android:layout_margin="20dp"
        android:gravity="center"
        android:padding="20dp"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="我是一个标题"
            android:textSize="20sp"
            android:textColor="@color/main_text_1"
             />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="40dp"
            android:text="我是一个辅助标题"
            android:textSize="20sp"
            android:textColor="@color/main_text_1"
            />
    </LinearLayout>


</androidx.constraintlayout.widget.ConstraintLayout>

首先这个页面我没有设置背景色,他的背景色是跟随主题的,如果你硬要给他一个,那么在系统切换模式的时候就会失效,其次这里使用的颜色是我们之前定义过的色值,他会根据不同的设置模式去自动选择使用那种颜色,如,night 文件夹中的就是暗黑模式下的色值。

关于字体及背景色简单总结:

这是文字及背景色的适配,基本就这些,但是正式运用到项目中就不会这么简单,因为你可能会有很多种颜色,每种颜色大多分为白色背景下的和黑色背景下的,比较难管理,通过我在整个项目的实践,我的建议是,一种模式下主要字体颜色最多定义三种,就比如说我上边使用到的,我只用了两种,主标题和副标题颜色以及主背景和副背景色,这样比较好管理,至于其他比较鲜艳的颜色黑白都能用的,就可以定义在普模式的colors 文件夹下,两种模式公用。

4.关于图片的适配

图片的适配和颜色是一个道理 比如说 我们的在drawable 文件下有一张图需要适配两种模式,那就新建一个文件夹darwable-night

,将需要在暗黑模式中需要使用的图片放到darwable-night下,需要注意的是,文件名一定要相同,还有就是在night相关文件定义了资源,那么在普通模式文件夹下一定要有相同名的资源,不然会报错。

反之就不用了。

5.状态栏的修改

    如果你按照上面我说的去做了,那么你会发现一个很操蛋的问题,在暗黑模式下没有啥问题,但是在白色模式下状态栏看不到了,

 

 

 

这个我上面说过,我们要手动的设置状态栏颜色,看不见的原因是系统默认是白色的状态栏,遇到白色背景肯定就看不到了,所以我们要坚获取到当前是否是暗黑模式然后修改状态栏颜色。

 private void setAndroidNativeLightStatusBar(Activity activity, boolean dark) {
        Log.d(TAG, "setAndroidNativeLightStatusBar: " + dark);
        View decor = activity.getWindow().getDecorView();
        if (dark) { //暗黑 设置状态栏为白色
            decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
        } else {//设置状态栏为黑色
            decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
        }
    }

这是谷歌给出的方法.

  那我咋知道当前是不是暗黑模式呢? 这里有两种方式

    方式一:

             在mainfist 中activity 添加

<activity android:name=".MainActivity"
            android:configChanges="uiMode">

     android:configChanges="uiMode" 这样在用户切换暗黑模式后进入到app 时

 @Override
    public void onConfigurationChanged(@NonNull Configuration newConfig) {
when (newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
    Configuration.UI_MODE_NIGHT_YES -> {
        // 暗黑模式已开启
    }
    Configuration.UI_MODE_NIGHT_NO -> {
        // 暗黑模式已关闭
    }
    }

方法就会被调用,但是这时系统设置暗黑主题后,在app是无效的,他的oncreate 没有重新执行,也就是app 当前页还没刷新过来,新开的页面就是好的,所以需要oncreate 重新执行才可以,要手动控制,还有就是我们可以根据这个去切换两个不同的主题,并且还是在setContentView 前,通过setTheme()设置主题,这样我们需要定义两套主题,我个人觉得不可取,所以我也没有使用这种方式,这个也是我之前遇到的一个坑,当设置了  android:configChanges="uiMode"  切换系统主题,app 主题一直没有变换.

    方式二:

        直接在当前activity 判断当前activity 是否是暗黑模式,如果是就设置状态栏为白色,不是就设置为黑色

//检查当前系统是否已开启暗黑模式
    public static boolean getDarkModeStatus(Context context) {
        int mode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
        return mode == Configuration.UI_MODE_NIGHT_YES;
    }

然后调用 setAndroidNativeLightStatusBar(this,isDark)方法设置状态栏,这样基本就完美,当然这个只要我们把它放在父类中就好了,比较简单。

看下MainActivity

public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState!=null) {
            String tag = (String) savedInstanceState.get("tag");
        }
        
        setContentView(R.layout.activity_main);
        setAndroidNativeLightStatusBar(this, getDarkModeStatus(this));
        Log.d(TAG, "onCreate: ");
    }

    private void setAndroidNativeLightStatusBar(Activity activity, boolean dark) {
        Log.d(TAG, "setAndroidNativeLightStatusBar: " + dark);
        View decor = activity.getWindow().getDecorView();
        if (dark) { //暗黑 设置状态栏为白色
            decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
        } else {//设置状态栏为黑色
            decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
        }
    }

    @Override
    public void onConfigurationChanged(@NonNull Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        Log.d(TAG, "onConfigurationChanged: ");
    }

    public static boolean getDarkModeStatus(Context context) {
        int mode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
        return mode == Configuration.UI_MODE_NIGHT_YES;
    }

    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString("tag","");
    }
}

6.最后一坑,tab标签页选项和内容fragmeng 不匹配

  上面我们说了,在系统切换主题时当前activity 的生命周期会重新执行,目的就是刷行刚刚设置的主题,但是oncreate 重新执行带来了数据丢失,出现tab 标签和tab 标签页的fragment 内容不符.

这时就要保存数据了,通过重写

  @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString("tag","");
    }

将数据保存在outState Bundle 中。在oncreate 中取出即可

 

 if (savedInstanceState!=null) {
            String tag = (String) savedInstanceState.get("tag");
        }

 

到此为止本次适配过程中遇到的所有问题都解决了

 

  • 作者:豌豆琪琪
  • 原文链接:https://blog.csdn.net/u013179982/article/details/105837859
    更新时间:2022-12-25 13:45:17