App实战:夜间模式实现方法一
大致上有三种实现方法:
- 通过更换主题,不需要重新创建Activity。
/**
* Set the base theme for this context. Note that this should be called
* before any views are instantiated in the Context (for example before
* calling {@link android.app.Activity#setContentView} or
* {@link android.view.LayoutInflater#inflate}).
*
* @param resid The style resource describing the theme.
*/
public abstract void setTheme(@StyleRes int resid);
通过自带sdk提供的api,比较简单,需要重新创建Activity。
/** * Sets the default night mode. This is used across all activities/dialogs but can be overridden * locally via {@link #setLocalNightMode(int)}. * * <p>This method only takes effect for those situations where {@link #applyDayNight()} works. * Defaults to {@link #MODE_NIGHT_NO}.</p> * * <p>This only takes effect for components which are created after the call. Any components * which are already open will not be updated.</p> * * @see #setLocalNightMode(int) * @see #getDefaultNightMode() */ public static void setDefaultNightMode(@NightMode int mode) { switch (mode) { case MODE_NIGHT_AUTO: case MODE_NIGHT_NO: case MODE_NIGHT_YES: case MODE_NIGHT_FOLLOW_SYSTEM: sDefaultNightMode = mode; break; default: Log.d(TAG, "setDefaultNightMode() called with an unknown mode"); break; } }
然后调用recreate(); 重新创建Activity。
自定义各种View继承自系统View,然后自定义方法设置夜间背景颜色,工作量巨大。
下面介绍第一种实现。
第一种开源方案来自何红辉Colorful。
利用context.setTheme新的主题后,然后通过循环遍历页面中的控件,通过如下代码获取新的颜色:
/**
* @param newTheme
* @return
*/
protected int getColor(Theme newTheme)
{
//返回重新指定后的资源id
TypedValue typedValue = new TypedValue();
newTheme.resolveAttribute(mAttrResId, typedValue, true);
return typedValue.data;
}
所以基于以上的思想,在编写xml文件的时候,给view设置背景颜色或者给textview设置文字颜色时,就不能写死了。所以有以下几个步骤:
1.自定义view的背景参数如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="root_view_bg" format="reference|color"/>
<attr name="cardview_bg" format="reference|color"/>
<attr name="toolbar_bg" format="reference|color"/>
<attr name="one_text_bg" format="reference|color"/>
<attr name="two_text_bg" format="reference|color"/>
</resources>
2.然后需要在colors文件中设置两套颜色,分别开头以day和night来区分,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
//日间模式的颜色
<color name="day_root_view_bg">#F0F7EF</color>
<color name="day_card_view_bg">#F0F7EF</color>
<color name="day_one_text_bg">#353535</color>
<color name="day_two_text_bg">#9e9e9e</color>
<color name="day_toolbar_bg">#3F51B5</color>
//夜间模式的颜色
<color name="night_root_view_bg">#3b3838</color>
<color name="night_card_view_bg">#3b3838</color>
<color name="night_one_text_bg">#BEBBBB</color>
<color name="night_two_text_bg">#9e9e9e</color>
<color name="night_toolbar_bg">#3b3838</color>
<color name="white">#ffffff</color>
</resources>
3.然后定义两个主题DayTheme和NightTheme,代码如下所示:
<style name="DayTheme" parent="NoBarTheme">
<item name="root_view_bg">@color/day_root_view_bg</item>
<item name="cardview_bg">@color/day_card_view_bg</item>
<item name="one_text_bg">@color/day_one_text_bg</item>
<item name="two_text_bg">@color/day_two_text_bg</item>
<item name="toolbar_bg">@color/day_toolbar_bg</item>
</style>
<style name="NightTheme" parent="NoBarTheme">
<item name="root_view_bg">@color/night_root_view_bg</item>
<item name="cardview_bg">@color/night_card_view_bg</item>
<item name="one_text_bg">@color/night_one_text_bg</item>
<item name="two_text_bg">@color/night_two_text_bg</item>
<item name="toolbar_bg">@color/night_toolbar_bg</item>
</style>
4.在编写xml文件中,背景颜色应该这么写比如:?attr/toolbar_bg:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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"
android:orientation="vertical"
tools:context=".ui.NightModeActivity">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/toolbar_bg"
app:navigationIcon="@mipmap/back"
app:title="夜间模式"
app:titleTextColor="@color/white"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/night_rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/root_view_bg"/>
</LinearLayout>
5.然后就是在Activity中的代码。
a.首先setTheme方法说的很清楚,需要在setContentView调用之前调用。所以会有
initTheme();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_night_mode);
b.初始化Colorful,代码如下:
private void setupColorful()
{
ViewGroupSetter toolbarSetter = new ViewGroupSetter(mToolbar, R.attr.toolbar_bg);
ViewGroupSetter rvSetter = new ViewGroupSetter(mNightRv, R.attr.root_view_bg);
rvSetter.childViewTextColor(R.id.category_desc, R.attr.one_text_bg);
rvSetter.childViewTextColor(R.id.category_author, R.attr.two_text_bg);
rvSetter.childViewTextColor(R.id.category_date, R.attr.two_text_bg);
rvSetter.childViewBgColor(R.id.night_rl, R.attr.cardview_bg);
colorful = new Colorful.Builder(this)
.setter(toolbarSetter)
.setter(rvSetter)
.create();
}
代码很简单,构造方法ViewSetter两个参数分别表示目标View和与其对应的特定属性id。如果目标view是ViewGroup类并且有子View需要指定特定属性id,则需要调用childViewTextColor或者childViewBgColor,两个参数分别代表绑定id和与其对应的特定属性。
再加一个转换模式的代码:
private void switchMode()
{
if (!mPref.getBoolean(NIGHT, false))
{
colorful.setTheme(R.style.NightTheme);
mPref.edit().putBoolean(NIGHT, true).commit();
} else
{
colorful.setTheme(R.style.DayTheme);
mPref.edit().putBoolean(NIGHT, false).commit();
}
}
来看一下效果:
可以看到有两个明显的缺陷:
- 就是状态栏的颜色并没有跟随改变;
- 颜色变化过于突兀;
针对问题一:引入猴哥的状态栏的库StatusBarUtil,可以解决问题。
针对问题二:可以利用属性动画来解决这个过于生硬的主题切换问题,思路如下:
首先获取当前窗口的根布局,利用这个根布局创建一个bitmap。然后创建一个临时View,并利用这个bitmap给这个临时View设置和当前根布局一样的背景。然后再来一个逐渐透明的动画,因为主题切换其实是瞬间切好,但是有了这个动画,就 给人的感觉是过度变化的。b话不多说,代码如下:
/**
* 给夜间模式增加一个动画,颜色渐变
*
* @param newTheme
*/
private void animChangeColor(final int newTheme)
{
final View rootView = getWindow().getDecorView();
rootView.setDrawingCacheEnabled(true);
rootView.buildDrawingCache(true);
final Bitmap localBitmap = Bitmap.createBitmap(rootView.getDrawingCache());
rootView.setDrawingCacheEnabled(false);
if (null != localBitmap && rootView instanceof ViewGroup)
{
final View tmpView = new View(this);
tmpView.setBackgroundDrawable(new BitmapDrawable(getResources(), localBitmap));
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup
.LayoutParams.MATCH_PARENT);
((ViewGroup) rootView).addView(tmpView, params);
tmpView.animate().alpha(0).setDuration(400).setListener(new Animator.AnimatorListener()
{
@Override
public void onAnimationStart(Animator animation)
{
colorful.setTheme(newTheme);
System.gc();
}
@Override
public void onAnimationEnd(Animator animation)
{
((ViewGroup) rootView).removeView(tmpView);
localBitmap.recycle();
}
@Override
public void onAnimationCancel(Animator animation)
{
}
@Override
public void onAnimationRepeat(Animator animation)
{
}
}).start();
}
}
然后来看下最终的效果图:
可以看到两个问题都已经解决了。
Github Demo。帮我点赞啊。
上一篇博客:
App架构设计实战二:基于MVP模式的相似UI界面复用问题解决方案
下一篇博客:
App实战:权限管理再封装之一键调用