/**
* 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)}.
*
* This method only takes effect for those situations where {@link #applyDayNight()} works.
* Defaults to {@link #MODE_NIGHT_NO}.
*
* This only takes effect for components which are created after the call. Any components
* which are already open will not be updated.
*
* @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,然后自定义方法设置夜间背景颜色,工作量巨大。
利用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设置文字颜色时,就不能写死了。所以有以下几个步骤:
<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>
<resources>
<color name="colorPrimary">#3F51B5color>
<color name="colorPrimaryDark">#303F9Fcolor>
<color name="colorAccent">#FF4081color>
//日间模式的颜色
<color name="day_root_view_bg">#F0F7EFcolor>
<color name="day_card_view_bg">#F0F7EFcolor>
<color name="day_one_text_bg">#353535color>
<color name="day_two_text_bg">#9e9e9ecolor>
<color name="day_toolbar_bg">#3F51B5color>
//夜间模式的颜色
<color name="night_root_view_bg">#3b3838color>
<color name="night_card_view_bg">#3b3838color>
<color name="night_one_text_bg">#BEBBBBcolor>
<color name="night_two_text_bg">#9e9e9ecolor>
<color name="night_toolbar_bg">#3b3838color>
<color name="white">#ffffffcolor>
resources>
<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>
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实战:权限管理再封装之一键调用