android官方夜间模式,Android夜间模式实践
前言
由于項(xiàng)目需要,近段時(shí)間開發(fā)的夜間模式功能。主流的方案如下:
1、通過(guò)切換theme實(shí)現(xiàn)
2、通過(guò)resource id映射實(shí)現(xiàn)
3、通過(guò)Android Support Library的實(shí)現(xiàn)
方案選擇
切換theme實(shí)現(xiàn)夜間模式
采用這種實(shí)現(xiàn)方式的代表是簡(jiǎn)書和知乎~實(shí)現(xiàn)策略如下:
1)在xml中定義兩套theme,差別僅僅是顏色不同
@color/colorPrimary
@color/colorPrimaryDark
@color/colorAccent
@android:color/white
@android:color/black
@color/color3F3F3F
@color/color3A3A3A
@color/color868686
@color/color3F3F3F
@color/color8A9599
自定義顏色:
在layout布局文件中,如 TextView 里的 android:textColor="?attr/clockTextColor" 是讓其字體顏色跟隨所設(shè)置的 Theme。
2)Java代碼相關(guān)實(shí)現(xiàn):
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
initData();
initTheme();
setContentView(R.layout.activity_day_night);
initView();
}
在每次setContentView之前必須調(diào)用initTheme方法,因?yàn)楫?dāng) View 創(chuàng)建成功后 ,再去 setTheme 是無(wú)法對(duì) View 的 UI 效果產(chǎn)生影響的。
/**
* 刷新UI界面
*/
private void refreshUI() {
TypedValue background = new TypedValue();//背景色
TypedValue textColor = new TypedValue();//字體顏色
Resources.Theme theme = getTheme();
theme.resolveAttribute(R.attr.clockBackground, background, true);
theme.resolveAttribute(R.attr.clockTextColor, textColor, true);
mHeaderLayout.setBackgroundResource(background.resourceId);
for (RelativeLayout layout : mLayoutList) {
layout.setBackgroundResource(background.resourceId);
}
for (CheckBox checkBox : mCheckBoxList) {
checkBox.setBackgroundResource(background.resourceId);
}
for (TextView textView : mTextViewList) {
textView.setBackgroundResource(background.resourceId);
}
Resources resources = getResources();
for (TextView textView : mTextViewList) {
textView.setTextColor(resources.getColor(textColor.resourceId));
}
int childCount = mRecyclerView.getChildCount();
for (int childIndex = 0; childIndex < childCount; childIndex++) {
ViewGroup childView = (ViewGroup) mRecyclerView.getChildAt(childIndex);
childView.setBackgroundResource(background.resourceId);
View infoLayout = childView.findViewById(R.id.info_layout);
infoLayout.setBackgroundResource(background.resourceId);
TextView nickName = (TextView) childView.findViewById(R.id.tv_nickname);
nickName.setBackgroundResource(background.resourceId);
nickName.setTextColor(resources.getColor(textColor.resourceId));
TextView motto = (TextView) childView.findViewById(R.id.tv_motto);
motto.setBackgroundResource(background.resourceId);
motto.setTextColor(resources.getColor(textColor.resourceId));
}
//讓 RecyclerView 緩存在 Pool 中的 Item 失效
//那么,如果是ListView,要怎么做呢?這里的思路是通過(guò)反射拿到 AbsListView 類中的 RecycleBin 對(duì)象,然后同樣再用反射去調(diào)用 clear 方法
Class recyclerViewClass = RecyclerView.class;
try {
Field declaredField = recyclerViewClass.getDeclaredField("mRecycler");
declaredField.setAccessible(true);
Method declaredMethod = Class.forName(RecyclerView.Recycler.class.getName()).getDeclaredMethod("clear", (Class>[]) new Class[0]);
declaredMethod.setAccessible(true);
declaredMethod.invoke(declaredField.get(mRecyclerView), new Object[0]);
RecyclerView.RecycledViewPool recycledViewPool = mRecyclerView.getRecycledViewPool();
recycledViewPool.clear();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
refreshStatusBar();
}
/**
* 刷新 StatusBar
*/
private void refreshStatusBar() {
if (Build.VERSION.SDK_INT >= 21) {
TypedValue typedValue = new TypedValue();
Resources.Theme theme = getTheme();
theme.resolveAttribute(R.attr.colorPrimary, typedValue, true);
getWindow().setStatusBarColor(getResources().getColor(typedValue.resourceId));
}
}
refreshUI函數(shù)起到模式切換的作用。通過(guò) TypedValue 和 Theme.resolveAttribute 在代碼中獲取 Theme 中設(shè)置的顏色,來(lái)重新設(shè)置控件的背景色或者字體顏色等等。refreshStatusBar刷新狀態(tài)欄。
/**
* 展示一個(gè)切換動(dòng)畫
*/
private void showAnimation() {
final View decorView = getWindow().getDecorView();
Bitmap cacheBitmap = getCacheBitmapFromView(decorView);
if (decorView instanceof ViewGroup && cacheBitmap != null) {
final View view = new View(this);
view.setBackgroundDrawable(new BitmapDrawable(getResources(), cacheBitmap));
ViewGroup.LayoutParams layoutParam = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
((ViewGroup) decorView).addView(view, layoutParam);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f);
objectAnimator.setDuration(300);
objectAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
((ViewGroup) decorView).removeView(view);
}
});
objectAnimator.start();
}
}
/**
* 獲取一個(gè) View 的緩存視圖
*
* @param view
* @return
*/
private Bitmap getCacheBitmapFromView(View view) {
final boolean drawingCacheEnabled = true;
view.setDrawingCacheEnabled(drawingCacheEnabled);
view.buildDrawingCache(drawingCacheEnabled);
final Bitmap drawingCache = view.getDrawingCache();
Bitmap bitmap;
if (drawingCache != null) {
bitmap = Bitmap.createBitmap(drawingCache);
view.setDrawingCacheEnabled(false);
} else {
bitmap = null;
}
return bitmap;
}
showAnimation 是用于展示一個(gè)漸隱效果的屬性動(dòng)畫,這個(gè)屬性作用在哪個(gè)對(duì)象上呢?是一個(gè) View ,一個(gè)在代碼中動(dòng)態(tài)填充到 DecorView 中的 View。
知乎之所以在夜間模式切換過(guò)程中會(huì)有漸隱效果,是因?yàn)樵谇袚Q前進(jìn)行了截屏,同時(shí)將截屏拿到的 Bitmap 設(shè)置到動(dòng)態(tài)填充到 DecorView 中的 View 上,并對(duì)這個(gè) View 執(zhí)行一個(gè)漸隱的屬性動(dòng)畫,所以使得我們能夠看到一個(gè)漂亮的漸隱過(guò)渡的動(dòng)畫效果。而且在動(dòng)畫結(jié)束的時(shí)候再把這個(gè)動(dòng)態(tài)添加的 View 給 remove 了,避免了 Bitmap 造成內(nèi)存飆升問題。
resource id映射實(shí)現(xiàn)夜間模式
通過(guò)id獲取資源時(shí),先將其轉(zhuǎn)換為夜間模式對(duì)應(yīng)id,再通過(guò)Resources來(lái)獲取對(duì)應(yīng)的資源。
public static Drawable getDrawable(Context context, int id) {
return context.getResources().getDrawable(getResId(id));
}
public static int getResId(int defaultResId) { if (!isNightMode()) {
return defaultResId;
}
if (sResourceMap == null) {
buildResourceMap();
}
int themedResId = sResourceMap.get(defaultResId);
return themedResId == 0 ? defaultResId : themedResId;
}
private static void buildResourceMap() {
sResourceMap = new SparseIntArray();
sResourceMap.put(R.drawable.common_background, R.drawable.common_background_night);
// ...
}
這個(gè)方案簡(jiǎn)單粗暴,麻煩的地方和第一種方案一樣:每次添加資源都需要建立映射關(guān)系,刷新UI的方式也與第一種方案類似,貌似今日頭條,網(wǎng)易新聞客戶端等主流新聞閱讀應(yīng)用都是通過(guò)這種方式實(shí)現(xiàn)的夜間模式。
通過(guò)Android Support Library實(shí)現(xiàn)
1)在res目錄中為夜間模式配置專門的目錄,以-night為后綴
res目錄
2)在Application中設(shè)置夜間模式
Application全局設(shè)置夜間模式
3)夜間模式切換
夜間模式切換
夜間模式實(shí)現(xiàn)
三種方案比較,第二種太暴力,不適合項(xiàng)目后期開發(fā);第一種方法需要做配置的地方比第三種方法多。總體來(lái)說(shuō),第三種方法最簡(jiǎn)單,類似整個(gè)app內(nèi)有一個(gè)夜間模式的總開關(guān),切換了以后就不用管了。最后采用第三種方案!
通過(guò)Android Support Library實(shí)現(xiàn)夜間模式雖然簡(jiǎn)單,但是當(dāng)中也碰到了一些坑。現(xiàn)做一下記錄:
1、 橫屏切換的時(shí)候,夜間模式混亂
基于Android Support Library的夜間模式,相當(dāng)于是support庫(kù)來(lái)幫忙關(guān)鍵相關(guān)的資源,有時(shí)候會(huì)出現(xiàn)錯(cuò)誤的情況。比如說(shuō)app橫豎屏切換之后!!經(jīng)測(cè)試發(fā)現(xiàn),每次調(diào)起一個(gè)橫屏的Activity,然后退出,整個(gè)app的夜間模式就亂了,部分的UI調(diào)用的是日間模式的資源~~~
這里認(rèn)為的加了一個(gè)多余的設(shè)定:
/**
* 刷新UI_MODE模式
*/
public void refreshResources(Activity activity) {
if (Prop.isNightMode.getBoolean()) {
updateConfig(activity, Configuration.UI_MODE_NIGHT_YES);
} else {
updateConfig(activity, Configuration.UI_MODE_NIGHT_NO);
}
}
/** * google官方bug,暫時(shí)解決方案 * 手機(jī)切屏后重新設(shè)置UI_MODE
模式(因?yàn)樵贒ayNight主題下,切換橫屏后UI_MODE會(huì)出錯(cuò),會(huì)導(dǎo)致
資源獲取出錯(cuò),需要重新設(shè)置回來(lái))
*/
private void updateConfig(Activity activity, int uiNightMode) {
Configuration newConfig = new
Configuration(activity.getResources().getConfiguration());
newConfig.uiMode &= ~Configuration.UI_MODE_NIGHT_MASK;
newConfig.uiMode |= uiNightMode;
activity.getResources().updateConfiguration(newConfig, null);
}
在每次退出橫屏的時(shí)候,調(diào)用這個(gè)方法,強(qiáng)制刷新一次config
2、 drawable xml文件中部分顏色值 日間/夜間 弄反了
Android Support Library實(shí)現(xiàn)的夜間模式,資源的獲取碰到了一些坑。我們經(jīng)常會(huì)在drawable文件夾中定義一些xml來(lái)做背景形狀、背景顏色。
按照Android Support Library的介紹,需要為夜間模式創(chuàng)建一個(gè)為night借我的目錄,那么這里就可以有兩種理解:
1)在values/color.xml 和values-night/color.xml分別為app_textbook_bg_color定義不同的色值
2)在values/color.xml 和values-night/color.xml分別為app_textbook_bg_color定義不同的色值;此外,需要分別定義drawable/bg_textbook.xml和drawable-night/bg_textbook.xml,兩個(gè)文件的內(nèi)容可以一樣。
這里碰到了一些坑。原先采用的是第一種方法,這樣代碼改動(dòng)少,看起來(lái)一目了然。但是,,不同廠商的手機(jī)會(huì)有不一樣的表現(xiàn)!!部分手機(jī),在夜間模式的時(shí)候還是用的日間的資源;殺了app重進(jìn)才會(huì)好。
我的理解是Android會(huì)對(duì)資源做緩存~ 緩存的時(shí)候會(huì)將app_textbook_color解析出來(lái)并緩存;假設(shè)日間模式app_textbook_color為#FFFFFF,我們?cè)O(shè)置夜間模式切換,這時(shí)候不同手機(jī)廠商的策略不一樣,有些廠商會(huì)把緩存清除,所以切成夜間模式的時(shí)候app_textbook_color的色值會(huì)改變,夜間模式正常;但是有些廠商應(yīng)該不會(huì)清理緩存,夜間模式切換之后,拿的是日間模式緩存下的色值,也就是#FFFFFF,這樣就出問題了~~
以上為個(gè)人見解,建議碰到這種情況,多在drawable下寫一個(gè)xml防止個(gè)別手機(jī)出錯(cuò)。
3、切換夜間模式需要restartActivity,會(huì)閃一下
這也是一個(gè)比較坑的地方。夜間模式切換以后,需要重新獲取一遍資源,最簡(jiǎn)單的方法是restart一下。現(xiàn)在我采用的就是這種簡(jiǎn)單粗暴的方法,用戶體驗(yàn)比較不友好,后期需要參考知乎的實(shí)現(xiàn),改進(jìn)實(shí)現(xiàn)。
參考鏈接
總結(jié)
以上是生活随笔為你收集整理的android官方夜间模式,Android夜间模式实践的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android 得到毫秒时间戳,andr
- 下一篇: android 粘性service,An