在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");
}
到此爲止本次適配過程中遇到的所有問題都解決了