前言
早在四年前就準(zhǔn)備做深色模式的姚糊,當(dāng)時用的三方的SDK蜘渣,但是SDK上還有bug,不能適配RecyclerView澜驮,用上后會很卡,然后就一直放著了刨疼,有些用戶一直催著要深色模式:
然后這段期間給整上泉唁,本以為現(xiàn)在深色模式應(yīng)用的挺廣泛的,在項(xiàng)目中實(shí)踐了一下還是躺了很多坑揩慕,梳理一下實(shí)踐過程及遇到的問題亭畜。
所有代碼實(shí)踐在云閱里可以看到:
- 下載App體驗(yàn),酷安:云閱
- 直接查看源碼迎卤,GitHub:CloudReader
項(xiàng)目實(shí)踐
1.選定原生Api實(shí)現(xiàn)
Android官方深色主題背景開發(fā)文檔(需科學(xué)上網(wǎng))拴鸵。
原生Api簡單穩(wěn)定但是就是要重啟App,不過看掘金以及微信都是這樣實(shí)現(xiàn)的蜗搔。
于是參考了微信和掘金的操作劲藐,總有三種狀態(tài),跟隨系統(tǒng)樟凄,普通模式聘芜,深色模式。
2.關(guān)鍵的工具類
public class NightModeUtil {
/**
* 當(dāng)前系統(tǒng)是否是深色模式
*/
public static boolean isNightMode(Context context) {
int uiMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
return uiMode == Configuration.UI_MODE_NIGHT_YES;
}
/**
* 獲取是否跟隨系統(tǒng)缝龄,默認(rèn)true
*/
public static boolean getSystemMode() {
return SPUtils.getBoolean(Constants.KEY_MODE_SYSTEM, true);
}
public static void setSystemMode(boolean nightMode) {
SPUtils.putBoolean(Constants.KEY_MODE_SYSTEM, nightMode);
}
/**
* 獲取是否設(shè)置深色模式汰现,默認(rèn)false
*/
public static boolean getNightMode() {
return SPUtils.getBoolean(Constants.KEY_MODE_NIGHT, false);
}
public static void setNightMode(boolean nightMode) {
SPUtils.putBoolean(Constants.KEY_MODE_NIGHT, nightMode);
}
public static void initNightMode() {
initNightMode(getSystemMode(), getNightMode());
}
/**
* 初始化App深色模式
*
* @param systemMode 是否是跟隨系統(tǒng)
* @param nightMode 是否是深色模式
*/
public static void initNightMode(boolean systemMode, boolean nightMode) {
if (systemMode) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
} else {
if (nightMode) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
}
}
/**
* 重啟App
*/
public static void restartApp(Activity activity) {
final Intent intent = App.getInstance().getPackageManager().getLaunchIntentForPackage(App.getInstance().getPackageName());
if (intent != null) {
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
activity.startActivity(intent);
android.os.Process.killProcess(android.os.Process.myPid());
}
}
}
3.在Application里初始化
NightModeUtil.initNightMode();
4.切換狀態(tài)后重啟App
NightModeUtil.initNightMode(dayNightSwitch.isChecked, ctvCheckNight.isChecked)
NightModeUtil.restartApp(activity)
其中還要保存是否跟隨系統(tǒng)或指定深色模式的狀態(tài)挂谍,具體邏輯細(xì)節(jié)可見:NavNightModeActivity.kt
5.Application下的主題設(shè)置
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<!--選中狀態(tài)icon的顏色和字體顏色-->
<item name="colorPrimary">@color/colorTheme</item>
<item name="colorPrimaryDark">@color/colorTheme</item>
<item name="colorAccent">@color/colorTheme</item>
<item name="android:windowAnimationStyle">@style/default_animation</item>
<item name="android:listDivider">@drawable/shape_line</item>
<!--默認(rèn)狀態(tài)下頁面的背景色-->
<item name="android:windowBackground">@color/color_page_bg</item>
</style>
同時還要注意如果單個Activity有自己的主題,也需要設(shè)置parent主題為Theme.AppCompat.DayNight.NoActionBar
瞎饲。
ToolBar也有自己的主題:
<androidx.appcompat.widget.Toolbar xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/tool_bar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorToolBar"
app:contentInsetStart="0.0dp"
app:contentInsetStartWithNavigation="0dp"
app:layout_scrollFlags="enterAlways|scroll"
app:popupTheme="@style/ThemeOverlay.AppCompat.ActionBar"
app:subtitleTextAppearance="@style/Toolbar.SubTitle"
app:theme="@style/ToolbarStyle"
app:titleMarginStart="0dp"
app:titleTextAppearance="@style/ToolBar.Title"
tools:layout_height="50dp"
tools:title="云閱" />
其中
-
theme
為:<style name="ToolbarStyle" parent="@style/ThemeOverlay.AppCompat.ActionBar"/>
口叙, -
popupTheme
也不能設(shè)置單個的Light
或Dark
主題,不然切換深色模式的時候也不會改變效果嗅战。
6.WebView的深色模式設(shè)置
引入implementation 'androidx.webkit:webkit:1.2.0'
后可輕易的實(shí)現(xiàn)WebView的深色模式妄田,不過有兼容問題,這和WebView的版本有關(guān)驮捍,WebView版本獨(dú)立于Android版本疟呐。(親測在系統(tǒng)6.0和7.1上無效。)
在有WebView的Activity的onCarete里加上如下代碼:
WebSettings webSetting = webView.getSettings();
// 檢查是否支持暗模式
if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
boolean isAppDarkMode;
if (NightModeUtil.getSystemMode()) {
// 是否是跟隨系統(tǒng)
isAppDarkMode = NightModeUtil.isNightMode(this);
} else {
isAppDarkMode = NightModeUtil.getNightMode();
}
if (isAppDarkMode) {
WebSettingsCompat.setForceDark(webSetting, WebSettingsCompat.FORCE_DARK_ON);
} else {
WebSettingsCompat.setForceDark(webSetting, WebSettingsCompat.FORCE_DARK_OFF);
}
}
7.配置項(xiàng)
1).接下來就是一些配色和部分深色模式下的圖片處理問題厌漂。
- 顏色:新建
values-night
文件夾萨醒,里面是深色模式下的colors.xml
文件 - 圖片:新建
drawable-night-xxhdpi
圖片文件夾
2).啟動頁我們經(jīng)常會放品牌圖,頁面的深色模式可以通過 改變普通/深色模式文件夾下的圖來實(shí)現(xiàn)
3).也可以自己處理配置變更苇倡,不重建Activity:
<activity
android:name=".NavNightModeActivity"
android:configChanges="uiMode" />
當(dāng)某個 Activity 聲明它會處理配置變更時富纸,系統(tǒng)會在出現(xiàn)主題背景變更時調(diào)用該 Activity 的 onConfigurationChanged()
方法。
val currentNightMode = configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
when (currentNightMode) {
Configuration.UI_MODE_NIGHT_NO -> {} // Night mode is not active, we're using the light theme
Configuration.UI_MODE_NIGHT_YES -> {} // Night mode is active, we're using dark theme
}
遇到的問題
1).獲取顏色ContextCompat.getColor(context, resId)
旨椒,需要加Activity
的context晓褪,如果是Application
的context會變不了色,這個和切換語言是一樣的综慎,獲取String也不能用全局的context涣仿。
2).之前使用了關(guān)閉應(yīng)用時殺掉進(jìn)程的代碼,導(dǎo)致不能重建Activity示惊,找了好長時間問題好港。殺掉進(jìn)程代碼:android.os.Process.killProcess(android.os.Process.myPid());
3).如果代碼需要單獨(dú)動態(tài)設(shè)置ToolBar的主題:
// 設(shè)置toolbar的dark模式,為了使"完成"文字顏色顯示白色
supportActionBar?.themedContext?.setTheme(R.style.ToolBarDarkActionBar)
4).可以使用系統(tǒng)自己的顏色值:
-
?android:attr/textColorPrimary
這是一種通用型文本顏色米罚。它在淺色主題背景下接近于黑色钧汹,在深色主題背景下接近于白色。 -
?android:attr/textColorSecondary
可作為第二文本顏色录择,相對于上面的顏色較淺拔莱。
5).在dialog打開后,再切換系統(tǒng)的深色模式隘竭,這時使用系統(tǒng)的顏色會不生效塘秦,需要使用自己的color文件里的顏色。具體出現(xiàn)在首次打開應(yīng)用時动看,彈出的隱私彈框尊剔。
6).需要使用png后綴的圖,最好別直接將jpg改為png菱皆,可以打開圖片后將圖片另存為png格式须误。我做時候debug模式下沒問題笔咽,打release包的時候就提示了這個問題。
總結(jié)
使用官方給出的深色模式Api霹期,實(shí)現(xiàn)起來比較簡單,但是也有一些注意項(xiàng)和優(yōu)化點(diǎn)拯田,如有需要可自取代碼 GitHub:CloudReader历造,如有其他問題,歡迎留言騷擾~