App实战:夜间模式实现方法一

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)}.
    *
    * 

    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,然后自定义方法设置夜间背景颜色,工作量巨大。

下面介绍第一种实现。

第一种开源方案来自何红辉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的背景参数如下:


<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来区分,如下所示:


<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>

3.然后定义两个主题DayTheme和NightTheme,代码如下所示:



4.在编写xml文件中,背景颜色应该这么写比如:?attr/toolbar_bg:


<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();
    }
}

来看一下效果:

App实战:夜间模式实现方法一_第1张图片

可以看到有两个明显的缺陷:

  • 就是状态栏的颜色并没有跟随改变;
  • 颜色变化过于突兀;

针对问题一:引入猴哥的状态栏的库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实战:权限管理再封装之一键调用

你可能感兴趣的:(Android实战开发)