參考鏈接:
========================================================
特別注意:經(jīng)過(guò)我們?cè)趯?shí)際開(kāi)發(fā)中的不斷實(shí)驗(yàn)和完善天通,只用鏈接2中的方法就可以完美做到APP整體的深色模式辩越,無(wú)需參考鏈接1的方法漩仙;當(dāng)然如有需要也可以研究一下傀蚌。
========================================================
最近公司想做夜間模式拯钻,于是在網(wǎng)上進(jìn)行了搜索,發(fā)現(xiàn)一些三方庫(kù)已經(jīng)很久不更新了,于是轉(zhuǎn)而參考上面的兩個(gè)鏈接,利用系統(tǒng)特性研究出一套夜間模式的方法市埋,這套方法代碼量比較小,適用性強(qiáng)恕刘,能夠很好的滿足我司當(dāng)前的需求缤谎;
開(kāi)發(fā)思路:我最開(kāi)始看到的是鏈接1,然后根據(jù)鏈接1開(kāi)搞的過(guò)程中發(fā)現(xiàn)褐着,里面的示例代碼不全坷澡,在xml中設(shè)置的字體顏色無(wú)法修改,本人能力有限含蓉,百度之后也無(wú)法解決這個(gè)問(wèn)題洋访,于是轉(zhuǎn)而看網(wǎng)上的其他方法并找到了鏈接2,通過(guò)實(shí)踐發(fā)現(xiàn)鏈接2可以很好的修改xml中設(shè)置的背景色和字體色以及自定義drawable背景中的顏色谴餐,完美彌補(bǔ)了鏈接1的不足,但是鏈接2不能對(duì)代碼中動(dòng)態(tài)設(shè)置的字體顏色和背景色進(jìn)行修改呆抑,而鏈接1又很好的完成了這一點(diǎn)岂嗓;于是二者結(jié)合,成功解決夜間模式問(wèn)題
總結(jié):
鏈接1:負(fù)責(zé)修改代碼中動(dòng)態(tài)設(shè)置的字體顏色和背景色鹊碍;
鏈接2:負(fù)責(zé)修改 xml 和 drawable 中設(shè)置的字體顏色和背景色厌殉;
鏈接1的實(shí)現(xiàn)思路:
1:創(chuàng)建res-night文件夾食绿,注意一定要和 res 文件夾是同級(jí):
2:在build.gradle中 android{} 下添加以下代碼:
//適配暗黑模式
sourceSets {
main {
java {
srcDir 'src/main/java'
}
res.srcDirs += 'src/main/res'
res.srcDirs += 'src/main/res-night'
}
}
3:在Application類中添加以下代碼:
private Resources mSkinResources = null;
@Override
public Resources getResources() {
if (mSkinResources == null) {
mSkinResources = new SkinResources(this,super.getResources());
}
return mSkinResources;
}
4:SkinResources 類源碼:
class SkinResources(context: Context, res: Resources) : Resources(res.assets, res.displayMetrics, res.configuration) {
val contextRef: WeakReference<Context> = WeakReference(context)
override fun getDrawableForDensity(id: Int, density: Int, theme: Theme?): Drawable? {
return super.getDrawableForDensity(resetResIdIfNeed(contextRef.get(), id), density, theme)
}
override fun getColor(id: Int, theme: Theme?): Int {
return super.getColor(resetResIdIfNeed(contextRef.get(), id), theme)
}
override fun getColorStateList(id: Int, theme: Theme?): ColorStateList {
return super.getColorStateList(resetResIdIfNeed(contextRef.get(), id), theme)
}
private fun resetResIdIfNeed(context: Context?, resId: Int): Int {
// 非暗黑無(wú)需替換資源 ID
val boolean = SPUtils.getBoolean(context, "mode", false) //默認(rèn)為白天模式
if (context == null || !boolean)
{
return resId
}
var newResId = resId
val res = context.resources
try {
val resPkg = res.getResourcePackageName(resId)
// 非本包資源無(wú)需替換
if (context.packageName != resPkg) return newResId
val resName = res.getResourceEntryName(resId)
val resType = res.getResourceTypeName(resId)
// 獲取對(duì)應(yīng)暗黑皮膚的資源 id
val id = res.getIdentifier("${resName}_night", resType, resPkg)
if (id != 0) newResId = id
} finally {
return newResId
}
}
}
5:給 res-night 文件夾中的colors文件添加顏色,具體要映射多少顏色公罕,需要根據(jù)項(xiàng)目情況來(lái)定器紧,這里舉例如下:
res 文件夾中colors中的白色:
<color name="white">#ffffff</color>
在這里應(yīng)該是:
<color name="white_night">#000000</color>
到此為止鏈接1中需要做的事情已經(jīng)做完了
================================ 分割線 ================================
鏈接2中的步驟:
1:在Application類的onCreate()中添加以下代碼:
boolean isNight = SPUtils.getBoolean(this, "mode", false); //默認(rèn)為白天模式
if (isNight)
{
//這個(gè)方法一設(shè)置就會(huì)全局生效
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
}else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
2:在res文件夾下創(chuàng)建values-night文件夾,如下:
3:colors文件中顏色對(duì)應(yīng)如下:
<color name="white">#ffffff</color>
在這里應(yīng)該是:
<color name="white">#000000</color>
到此為止鏈接2中需要做的事情已經(jīng)做完了楼眷,下面是暗黑模式的開(kāi)關(guān)部分:
val isNight = SPUtils.getBoolean(this, "mode",false)
SPUtils.put(this,"mode",!isNight)
if (!isNight)
{
//這個(gè)方法一設(shè)置就會(huì)全局生效
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
}else{
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
}
6:深色模式適配注意點(diǎn):
a:所有用到的色值铲汪,盡量使用colors中定義好的,如果colors中沒(méi)有罐柳,則需要添加到colors中
b:在對(duì)colors中添加色值的時(shí)候掌腰,需要先看一下colors中是否已經(jīng)有這個(gè)色值漓骚,如果有就復(fù)用惧笛,不要再新起一個(gè)名字
c:不要在代碼或者xml中直接使用16進(jìn)制的色值,比如使用:#ffffff 這種的
d:如果設(shè)計(jì)圖中的色值在colors沒(méi)有定義叠纷,但是和colors中的某個(gè)色值很接近肮蛹,比如:設(shè)計(jì)圖中是#f6f6f6勺择,而color中 #f5f5f5 ,這時(shí)要找設(shè)計(jì)確認(rèn)是否改成 #f5f5f5伦忠,這里涉及到一個(gè)風(fēng)格統(tǒng)一的問(wèn)題
e:圖標(biāo)盡量使用iconfont省核,比如返回按鈕,iconfont的色值使用colors中定義好的
f:如果控件的顏色在深色和淺色兩種模式下需要保持一致缓苛,比如Textview的字體顏色芳撒,在深色和淺色下都需要是白色,則使用colors中的white_for_night未桥;如果colors中沒(méi)有這個(gè)色值笔刹,則增加一個(gè): 色值_for_night
g:深色模式對(duì)應(yīng)的色值合集為:values-night/colors 和 res-night/values/colors,在最終上線的時(shí)候冬耿,這兩個(gè)文件里面的色值盡量保持一致
h:頂部侵入式狀態(tài)欄:有的頁(yè)面在設(shè)置深色模式之后舌菜,狀態(tài)欄的背景色和字體顏色會(huì)有問(wèn)題,需要注意一下
i:深色模式的開(kāi)關(guān)在設(shè)置頁(yè)面”文字大小“按鈕的下面亦镶,如果是隱藏狀態(tài)日月,需要改成visibility
j:深色模式做完必須得讓設(shè)計(jì)驗(yàn)收
k:代碼中動(dòng)態(tài)設(shè)置顏色的時(shí)候,必須要傳入當(dāng)前頁(yè)面的上下文缤骨,否則系統(tǒng)設(shè)置了深色模式爱咬,但是APP沒(méi)開(kāi)啟深色模式的情況下,也會(huì)走res中的res-night下的colors绊起,從而導(dǎo)致顯示深色模式的情況精拟,比如:
ContextCompat.getColor(BaseApplication.mTopActivity, color_id);
中,必須傳入?yún)?shù)一的上下文
7:部分機(jī)型跟隨系統(tǒng)的適配問(wèn)題:
a:在實(shí)際開(kāi)發(fā)的時(shí)候,我們?cè)黾恿烁S系統(tǒng)的功能蜂绎,但是在魅族手機(jī)和榮耀手機(jī)上更改系統(tǒng)為深色或者淺色模式的時(shí)候栅表,APP中檢測(cè)不到系統(tǒng)的更改,拿到的始終是淺色模式师枣;為此我們?cè)黾恿艘韵路椒▉?lái)修復(fù)這個(gè)問(wèn)題:
b:在清單文件的application標(biāo)簽下增加:android:configChanges="uiMode"
c:在application的繼承類中增加以下代碼:
@Override
public void onConfigurationChanged(@NonNull android.content.res.Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (DarkModeUtil.INSTANCE.isInit() && DarkModeUtil.INSTANCE.isFollowSystem()) {
int uiMode = newConfig.uiMode & android.content.res.Configuration.UI_MODE_NIGHT_MASK;
//這里需要先判斷當(dāng)前是否是跟隨系統(tǒng)怪瓶,如果是跟隨系統(tǒng),并且跟系統(tǒng)的樣式不一致践美,再進(jìn)行改變洗贰,并且修改深色模式的SP
//中的布爾值;這里就先省略了拨脉,只保留核心代碼
switch (uiMode) {
case android.content.res.Configuration.UI_MODE_NIGHT_YES: // 深色模式
if (BaseApplication.mTopActivity != null) {
BaseApplication.mTopActivity.recreate();
}
break;
case android.content.res.Configuration.UI_MODE_NIGHT_NO: // 淺色模式
if (BaseApplication.mTopActivity != null) {
BaseApplication.mTopActivity.recreate();
}
break;
default: // 未知模式
break;
}
}
}