Android 夜间模式主题切换方案

由于Android的设置中并没有夜间模式的选项,对于喜欢睡前玩手机的用户,只能简单的调节手机屏幕亮度来改善体验。目前越来越多的应用开始把夜间模式加到自家应用中,没准不久google也会把这项功能添加到Android系统中吧。

业内关于夜间模式的实现,有两种主流方案,各有其利弊,我较为推崇第三种方案:

1、通过切换theme来实现夜间模式。
2、通过修改uiMode来切换夜间模式。

3、通过插件方式切换夜间模式。

值得一提的是,上面提到的几种方案,都是资源内嵌在Apk中的方案,像新浪微博那种需要通过下载方式实现的夜间模式方案,网上有很多介绍,这里不去讨论。

下面简要描述下几种方案的实现原理:

1、通过切换theme来实现夜间模式。

首先在attrs.xml中,为需要随theme变化的内容定义属性

<?xml version="1.0" encoding="utf-8"?>  
<resources>  
    <attr name="colorValue" format="color" />  
    <attr name="floatValue" format="float" />  
    <attr name="integerValue" format="integer" />  
    <attr name="booleanValue" format="boolean" />  
    <attr name="dimensionValue" format="dimension" />  
    <attr name="stringValue" format="string" />  
    <attr name="referenceValue" format="color|reference" />  
    <attr name="imageValue" format="reference"/>  
  
    <attr name="curVisibility">  
    <enum name="show" value="0" />  
    <!-- Not displayed, but taken into account during layout (space is left for it). -->  
    <enum name="inshow" value="1" />  
    <!-- Completely hidden, as if the view had not been added. -->  
    <enum name="hide" value="2" />  
    </attr>  
</resources>

从上面的xml文件的内容可以看到,attr里可以定义各种属性类型,如color、float、integer、boolean、dimension(sp、dp/dip、px、pt...)、reference(指向本地资源),还有curVisibility是枚举属性,对应view的invisibility、visibility、gone。

其次在不同的theme中,对属性设置不同的值,在styles.xml中定义theme如下

<style name="DayTheme" parent="Theme.Sherlock.Light">>  
    <item name="colorValue">@color/title</item>  
    <item name="floatValue">0.35</item>  
    <item name="integerValue">33</item>  
    <item name="booleanValue">true</item>  
    <item name="dimensionValue">16dp</item>  
    <!-- 如果string类型不是填的引用而是直接放一个字符串,在布局文件中使用正常,但代码里获取的就有问题 -->  
    <item name="stringValue">@string/action_settings</item>  
    <item name="referenceValue">@drawable/bg</item>  
    <item name="imageValue">@drawable/launcher_icon</item>  
    <item name="curVisibility">show</item>  
</style>  
<style name="NightTheme" parent="Theme.Sherlock.Light">  
    <item name="colorValue">@color/night_title</item>  
    <item name="floatValue">1.44</item>  
    <item name="integerValue">55</item>  
    <item name="booleanValue">false</item>  
    <item name="dimensionValue">18sp</item>  
    <item name="stringValue">@string/night_action_settings</item>  
    <item name="referenceValue">@drawable/night_bg</item>  
    <item name="imageValue">@drawable/night_launcher_icon</item>  
    <item name="curVisibility">hide</item>  
</style>

在布局文件中使用对应的值,通过?attr/属性名,来获取不同theme对应的值。

?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"   
    android:background="?attr/referenceValue"  
    android:orientation="vertical"  
    >  
    <TextView  
        android:id="@+id/setting_Color"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="TextView"  
        android:textColor="?attr/colorValue" />  
    <CheckBox  
                android:id="@+id/setting_show_answer_switch"  
                android:layout_width="wrap_content"  
                android:layout_height="wrap_content"                 
                android:checked="?attr/booleanValue"/>     
    <TextView  
        android:id="@+id/setting_Title"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"   
        android:textSize="?attr/dimensionValue"   
        android:text="@string/text_title"  
        android:textColor="?attr/colorValue" />   
    <TextView  
        android:id="@+id/setting_Text"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"    
        android:text="?attr/stringValue" />  
  
    <ImageView  
        android:id="@+id/setting_Image"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"      
        android:src="?attr/imageValue" />  
  
  
    <View android:id="@+id/setting_line"  
        android:layout_width="match_parent"  
        android:layout_height="1dp"  
        android:visibility="?attr/curVisibility"  
        />   
</LinearLayout>
在Activity中调用如下changeTheme方法,其中isNightMode为一个全局变量用来标记当前是否为夜间模式,在设置完theme后,还需要调用restartActivity或者setContentView重新刷新UI。
@Override  
    protected void onCreate(Bundle savedInstanceState) {         
        super.onCreate(savedInstanceState);  
        if(AppThemeManager.isLightMode()){  
            this.setTheme(R.style.NightTheme);  
        }else{  
            this.setTheme(R.style.DayTheme);  
        }  
        setContentView(R.layout.setting);  
    }

到此即完成了一个夜间模式的简单实现,包括Google自家在内的很多应用都是采用此种方式实现夜间模式的,这应该也是Android官方推荐的方式。

但这种方式有一些不足,规模较大的应用,需要随theme变化的属性会很多,都需要逐一定义,有点麻烦,另外一个缺点是要使得新theme生效,一般需要restartActivity来切换UI,会导致切换主题时界面闪烁。

不过也可以通过调用自定义的updateTheme方法,重启Activity即可

public static void updateTheme(Activity activity,isNight)
{
  AppThemeManager.setNightMode(isNight);
  activity.recreate();
  /
  *
  * activity.finish(); 
  * Intent intent=new Intent(); 
  * intent.setClass(context, MainActivity.class); 
  * context.startActivity(intent);
  */
}

当然,潜在的问题也是存在的,比如,我们动态获取资源Resource,那么遇到这种情况的解决办法是自定义资源获取规则,并且在资源名称上下功夫

public static Drawable getDrawable(Context context,String resName,boolean isForce)
{
    int  resId;
    if(AppThemeManager.isLightMode() && isForce) //这里使用isForce参数主要是为了一些主题切换时共用的图片被匹配
    {
    //约定,黑夜图片带_night
       resId = context.getResources().getIdentifier(resName+"_night", "drawable", context.getPackageName());
    }else{
       resId = context.getResources().getIdentifier(resName, "drawable", context.getPackageName());
    }
    
    return context.getResources().getDrawable(resId);
}

public static Drawable getDrawable(Context context,int resid,boolean isForce)
{
    String resName  = context.getResources().getResourceEntryName(resid);
    if(AppThemeManager.isLightMode() && isForce)
    {
       resName = resName+"_night";
    }
    int  resId = context.getResources().getIdentifier(resName, "drawable", context.getPackageName());
    return context.getResources().getDrawable(resId);
}

//当然,获取string,dimens等资源也是这种方式,这里就不再论述

优点:可以匹配多套主题,并不局限于黑白模式

缺点:需要大量定义主题

2、通过修改uiMode来切换夜间模式。

修改uimode是修改Configuration,这种主题切换只限于黑白模式,没有其他模式,核心代码如下

Configuration newConfig = new Configuration(activity.getResources().getConfiguration());
newConfig.uiMode &= ~Configuration.UI_MODE_NIGHT_MASK;
newConfig.uiMode |= uiNightMode;
activity.getResources().updateConfiguration(newConfig, null);
activity.recreate();

但这种切换的前提是,我们的资源目录必须具备切换-night后缀,类似国际化语言的切换,如:

values-night/
drawable-night/
drawable-night-xxdpi/
.....

下面来一个开源的Helper

package com.example.androidtestcase;
import android.app.Activity;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.preference.PreferenceManager;

import java.lang.ref.WeakReference;


public class NightModeHelper
{

    private static final String PREF_KEY = "nightModeState";

    private static int sUiNightMode = Configuration.UI_MODE_NIGHT_UNDEFINED;

    private WeakReference<Activity> mActivity;

    private SharedPreferences mPrefs;


    public NightModeHelper(Activity activity)
    {

        int currentMode = (activity.getResources().getConfiguration()
                .uiMode & Configuration.UI_MODE_NIGHT_MASK);
        mPrefs = PreferenceManager.getDefaultSharedPreferences(activity);
        init(activity, -1, mPrefs.getInt(PREF_KEY, currentMode));
    }

   
    public NightModeHelper(Activity activity, int theme)
    {

        int currentMode = (activity.getResources().getConfiguration()
                .uiMode & Configuration.UI_MODE_NIGHT_MASK);
        mPrefs = PreferenceManager.getDefaultSharedPreferences(activity);
        init(activity, theme, mPrefs.getInt(PREF_KEY, currentMode));
    }


    public NightModeHelper(Activity activity, int theme, int defaultUiMode)
    {

        init(activity, theme, defaultUiMode);
    }

    private void init(Activity activity, int theme, int defaultUiMode)
    {

        mActivity = new WeakReference<Activity>(activity);
        if (sUiNightMode == Configuration.UI_MODE_NIGHT_UNDEFINED)
        {
            sUiNightMode = defaultUiMode;
        }
        updateConfig(sUiNightMode);

        if (theme != -1)
        {
            activity.setTheme(theme);
        }
    }

    private void updateConfig(int uiNightMode)
    {

        Activity activity = mActivity.get();
        if (activity == null)
        {
            throw new IllegalStateException("Activity went away?");
        }
        Configuration newConfig = new Configuration(activity.getResources().getConfiguration());
        newConfig.uiMode &= ~Configuration.UI_MODE_NIGHT_MASK;
        newConfig.uiMode |= uiNightMode;
        activity.getResources().updateConfiguration(newConfig, null);
        sUiNightMode = uiNightMode;
        if (mPrefs != null)
        {
            mPrefs.edit()
                    .putInt(PREF_KEY, sUiNightMode)
                    .apply();
        }
    }

    public static int getUiNightMode()
    {

        return sUiNightMode;
    }

    public void toggle()
    {

        if (sUiNightMode == Configuration.UI_MODE_NIGHT_YES)
        {
            notNight();
        } else
        {
            night();
        }
    }


    public void notNight()
    {

        updateConfig(Configuration.UI_MODE_NIGHT_NO);
        System.gc();
        System.runFinalization(); 
        System.gc();
        mActivity.get().recreate();
    }

    public void night()
    {

        updateConfig(Configuration.UI_MODE_NIGHT_YES);
        System.gc();
        System.runFinalization(); // added in https://github.com/android/platform_frameworks_base/commit/6f3a38f3afd79ed6dddcef5c83cb442d6749e2ff
        System.gc();
        mActivity.get().recreate();
    }
}

当然,Android也为这种过于冗杂的模式提供了UIModeManager,优点是我们再也不需要使用Perference手动保存并管理一些信息了。

UiModeManager umm = (UiModeManager )context.getSystemService(Context.UI_MODE_SERVICE);
umm.getNightMode(UI_MODE_NIGHT_YES);

对于第二种方案,优缺点如下:

优点:

/res/xxx-night形式避免了切换中需要手动管理资源的问题,避免了代码手动管理夜间模式配置

缺点:

只能局限于2种主题。


3、通过插件方式切换夜间模式。


插件换肤具体请参考如下博客:

Android更换皮肤解决方案

Android 插件化开发-主题皮肤更换


题外话:对于插件换肤,我们还可以思考定义一套属于自己的Resource资源加载框架,而不是使用系统的Resource,这样也是一种切换方案。





转载于:https://my.oschina.net/ososchina/blog/668384

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android日间模式切换是指在Android设备中切换应用程序的主题模式,以更好地适应当前环境或用户的个人喜好。在日间模式下,应用程序通常使用浅色的背景和黑色的文本,以提供清晰明亮的外观。下面是关于Android日间模式切换的一些信息: 1. 如何切换日间模式Android设备通常提供了一个系统菜单或设置选项,允许用户切换日间模式。用户可以在“设置”或“显示”菜单中找到“主题”或“外观”选项,并选择日间模式以启用它。 2. 日间模式的用途:日间模式在白天或明亮的环境下使用,可提供更好的可读性和可见性。由于浅色背景和黑色文本的对比度较高,阅读和浏览应用程序内容会更加舒适和方便。 3. 切换日间模式的优势:切换到日间模式可以节省设备的电池寿命,因为设备的背光需要较少的功耗。同时,对于部分用户来说,浅色背景和黑色文本也可能更易于处理眼睛疲劳问题。 4. 日夜模式的应用场景:日间模式适用于大多数日常使用情况,特别是在户外环境或光线充足的环境下。例如,用户在户外使用应用程序时,日间模式可以保证内容清晰可见。 5. 自动切换和自定义设置:一些Android设备允许用户根据日出和日落时间自动切换日间模式。此外,用户还可以根据自己的喜好,自定义日间模式的背景颜色、文本颜色和其他外观设置。 总结起来,Android日间模式切换是一个方便的功能,允许用户根据环境或个人偏好来调整应用程序的外观。通过切换到日间模式,用户可以获得更好的可读性和可见性,并且在一定程度上延长设备的电池寿命。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值