android 暗黑模式項目適配過程

 在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");
        }

 

到此爲止本次適配過程中遇到的所有問題都解決了

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章