好了,回归正题,说回夜间模式。在网上看到很多童鞋都说用什么什么框架来实现这个功能,然后仔细去看一下各个推荐的框架,发现其实都是动态换肤的,动态换肤可比夜间模式要复杂多了,未免大材小用了。说实话,我一直没用什么好思路,虽然网上有童鞋提供了一种思路是通过 setTheme 然后再 recreate Activity 的方式,但是这样带来的问题是非常多的,看起来就相当不科学(为什么不科学,后文会说)。于是,直接想到了去逆向分析那些夜间模式做得好的应用的源代码,学习他们的实现套路。所以,本文的实现思路来自于编写这些应用的夜间模式功能的童鞋,先在这里向他们表示感谢。我的手机里面使用高频的应用不少,其中简书和知乎是属于夜间模式做得相当 nice 的。先给两个效果图大家对比感受下
简书 | 知乎 |
---|---|
如果大家仔细观察,肯定会发现,知乎的切换效果更漂亮些,因为它有一个渐变的效果。那么它们的夜间模式到底是如何实现的呢?别急接着往下看,你也可以。
这里先展示一下我的实现效果吧
简书实现效果 | 知乎实现效果 |
---|---|
此处分为两个部分,一部分是 xml 文件中要干的活,一部分是 Java 代码要实现的活,先说 xml 吧。
首先,先写一套UI界面出来,上方左边是两个 TextView,右边是两个 CheckBox,下方是一个 RecyclerView ,实现很简单,这里我不贴代码了。
接着,在 styles 文件中添加两个 Theme,一个是日间主题,一个是夜间主题。它们的属性都是一样的,唯一区别在于颜色效果不同。
<!--白天主题-->
<style name="DayTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="clockBackground">@android:color/white</item>
<item name="clockTextColor">@android:color/black</item>
</style>
<!--夜间主题-->
<style name="NightTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/color3F3F3F</item>
<item name="colorPrimaryDark">@color/color3A3A3A</item>
<item name="colorAccent">@color/color868686</item>
<item name="clockBackground">@color/color3F3F3F</item>
<item name="clockTextColor">@color/color8A9599</item>
</style>
需要注意的是,上面的 clockTextColor 和 clockBackground 是我自定义的 color 类型属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="clockBackground" format="color" />
<attr name="clockTextColor" format="color" />
</resources>
然后再到所有需要实现夜间模式功能的 xml 布局文件中,加入类似下面设置,比如我在 RecyclerView 的 Item 布局文件中做了如下设置
稍稍解释下其作用,如 TextView 里的 android:textColor=”?attr/clockTextColor” 是让其字体颜色跟随所设置的 Theme。到这里,xml 需要做的配置全部完成,接下来是 Java 代码实现了。
大家可以先看下面的实现代码,看不懂的童鞋可以边结合我代码下方实现思路解说。
package com.clock.study.activity;
import ...
/**
* 夜间模式实现方案
*
* @author Clock
* @since 2016-08-11
*/
public class DayNightActivity
extends AppCompatActivity
implements CompoundButton.OnCheckedChangeListener {
private final static String TAG = DayNightActivity.class.getSimpleName();
/**用于将主题设置保存到SharePreferences的工具类**/
private DayNightHelper mDayNightHelper;
private RecyclerView mRecyclerView;
private LinearLayout mHeaderLayout;
private List<RelativeLayout> mLayoutList;
private List<TextView> mTextViewList;
private List<CheckBox> mCheckBoxList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
initData();
initTheme();
setContentView(R.layout.activity_day_night);
initView();
}
private void initView() {
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setAdapter(new SimpleAuthorAdapter());
mHeaderLayout = (LinearLayout) findViewById(R.id.header_layout);
mLayoutList = new ArrayList<>();
mLayoutList.add((RelativeLayout) findViewById(R.id.jianshu_layout));
mLayoutList.add((RelativeLayout) findViewById(R.id.zhihu_layout));
mTextViewList = new ArrayList<>();
mTextViewList.add((TextView) findViewById(R.id.tv_jianshu));
mTextViewList.add((TextView) findViewById(R.id.tv_zhihu));
mCheckBoxList = new ArrayList<>();
CheckBox ckbJianshu = (CheckBox) findViewById(R.id.ckb_jianshu);
ckbJianshu.setOnCheckedChangeListener(this);
mCheckBoxList.add(ckbJianshu);
CheckBox ckbZhihu = (CheckBox) findViewById(R.id.ckb_zhihu);
ckbZhihu.setOnCheckedChangeListener(this);
mCheckBoxList.add(ckbZhihu);
}
@Override
public void onCheckedChanged(
CompoundButton buttonView,
boolean isChecked) {
int viewId = buttonView.getId();
if (viewId == R.id.ckb_jianshu) {
changeThemeByJianShu();
} else if (viewId == R.id.ckb_zhihu) {
changeThemeByZhiHu();
}
}
private void initData() {
mDayNightHelper = new DayNightHelper(this);
}
private void initTheme() {
if (mDayNightHelper.isDay()) {
setTheme(R.style.DayTheme);
} else {
setTheme(R.style.NightTheme);
}
}
/**
* 切换主题设置
*/
private void toggleThemeSetting() {
if (mDayNightHelper.isDay()) {
mDayNightHelper.setMode(DayNight.NIGHT);
setTheme(R.style.NightTheme);
} else {
mDayNightHelper.setMode(DayNight.DAY);
setTheme(R.style.DayTheme);
}
}
/**
* 使用简书的实现套路来切换夜间主题
*/
private void changeThemeByJianShu() {
toggleThemeSetting();
refreshUI();
}
/**
* 使用知乎的实现套路来切换夜间主题
*/
private void changeThemeByZhiHu() {
showAnimation();
toggleThemeSetting();
refreshUI();
}
/**
* 刷新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);
n