通过用户配置的Android暗模式

Android 10 released Dark Mode, and with it, a ton of requests on Play Store.

Android 10发布了黑暗模式,并在Play商店上发布了大量请求。

Let’s check out an easy way to provide users with the option of selecting between dark mode, light mode, or following the system night mode.

让我们找出一种简单的方法,为用户提供在暗模式亮模式 或遵循系统夜间模式。

Image for post
The result that we want
我们想要的结果

We will need new preferences and material libraries:

我们将需要新的首选项和材料库:

implementation "androidx.preference:preference-ktx:1.1.1"
implementation 'com.google.android.material:material:1.1.0'

Then we will make our app theme extend Theme.MaterialComponents.DayNight:

然后,我们将使我们的应用程序主题扩展Theme.MaterialComponents.DayNight:

<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.MaterialComponents.DayNight">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>


</resources>

Extending Theme.AppCompat.DayNight is also possible.

也可以扩展Theme.AppCompat.DayNight

For our settings fragment, we will use a preference resource file with ListPreferences to provide theme selection.

对于我们的设置片段,我们将使用带有ListPreferences的首选项资源文件来提供主题选择。

It is not a regular layout file, it needs to be placed inside the XML directory, inside of res.

它不是常规布局文件,需要将其放置在res目录中的XML目录中

Image for post

The preference layout file:

首选项布局文件:

<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">


    <ListPreference
        app:entries="@array/themes_entries"
        app:entryValues="@array/themes_values"
        app:key="@string/theme_preferences_key"
        app:defaultValue="@string/system_theme_preference_value"
        app:title="Theme" />


</PreferenceScreen>

Pay attention to entries and entryValues attributes: they will be used as a key-value pair.

注意entryentryValues属性:它们将用作键值对。

The entries will be used to display a list, and the entryValues will hold the respective value for each entry, so make sure the indexes match.

这些条目将用于显示列表,而entryValues将为每个条目保存各自的值,因此请确保索引匹配。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="themes_entries">
        <item>Dark</item>
        <item>Light</item>
        <item>System default</item>
    </string-array>
    <string-array name="themes_values">
        <item>@string/dark_theme_preference_value</item>
        <item>@string/light_theme_preference_value</item>
        <item>@string/system_theme_preference_value</item>
    </string-array>
</resources>

Since we are going to use the values defined at themes_res.xml to save selected theme to sharedPreferences, let’s create a ThemeProvider class, whose’s scope is getting these values and return theme-specific values accordingly:

由于我们将使用在themes_res.xml定义的值将选定的主题保存到sharedPreferences ,因此我们创建一个ThemeProvider类,其范围是获取这些值并相应地返回特定于主题的值:

package com.lucianoluzzi.darkthemesample


import android.app.UiModeManager
import android.content.Context
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.PreferenceManager
import java.security.InvalidParameterException


class ThemeProvider(private val context: Context) {


    fun getThemeFromPreferences(): Int {
        val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
        val selectedTheme = sharedPreferences.getString(
            context.getString(R.string.theme_preferences_key),
            context.getString(R.string.system_theme_preference_value)
        )


        return selectedTheme?.let {
            getTheme(it)
        } ?: AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
    }


    fun getThemeDescriptionForPreference(preferenceValue: String?): String =
        when (preferenceValue) {
            context.getString(R.string.dark_theme_preference_value) -> context.getString(R.string.dark_theme_description)
            context.getString(R.string.light_theme_preference_value) -> context.getString(R.string.light_theme_description)
            else -> context.getString(R.string.system_theme_description)
        }


    fun getTheme(selectedTheme: String): Int = when (selectedTheme) {
        context.getString(R.string.dark_theme_preference_value) -> UiModeManager.MODE_NIGHT_YES
        context.getString(R.string.light_theme_preference_value) -> UiModeManager.MODE_NIGHT_NO
        context.getString(R.string.system_theme_preference_value) -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
        else -> throw InvalidParameterException("Theme not defined for $selectedTheme")
    }
}

The getTheme method returns one of the following values:

getTheme方法返回以下值之一:

  • MODE_NIGHT_YES — in other words, Dark Mode

    MODE_NIGHT_YES-换句话说,黑暗模式
  • MODE_NIGHT_NO — Light Mode

    MODE_NIGHT_NO-灯光模式
  • MODE_NIGHT_FOLLOW_SYSTEM — the app will follow the system dark mode

    MODE_NIGHT_FOLLOW_SYSTEM —应用程序将遵循系统暗模式

Method getThemeDescriptionForPreference will return a description for each theme, which we will use in our SettingsFragment.

方法getThemeDescriptionForPreference将返回每个主题的描述,我们将在SettingsFragment中使用该描述。

Next, let’s create a settings fragment:

接下来,让我们创建一个设置片段:

package com.lucianoluzzi.darkthemesample


import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat


class SettingsFragment : PreferenceFragmentCompat() {


    private val themeProvider by lazy { ThemeProvider(requireContext()) }
    private val themePreference by lazy {
        findPreference<ListPreference>(getString(R.string.theme_preferences_key))
    }


    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
        setPreferencesFromResource(R.xml.settings, rootKey)
        setThemePreference()
    }


    private fun setThemePreference() {
        themePreference?.onPreferenceChangeListener =
            Preference.OnPreferenceChangeListener { _, newValue ->
                if (newValue is String) {
                    val theme = themeProvider.getTheme(newValue)
                    AppCompatDelegate.setDefaultNightMode(theme)
                }
                true
            }
        themePreference?.summaryProvider = getThemeSummaryProvider()
    }


    private fun getThemeSummaryProvider() =
        Preference.SummaryProvider<ListPreference> { preference ->
            themeProvider.getThemeDescriptionForPreference(preference.value)
        }
}

Looking at our preference listener, that’s where we get the user selected value, the ThemeProvider finds out which theme is represented by the selected string, and finally, we set the theme night mode.

查看我们的首选项侦听器,这是我们获得用户所选值的位置,ThemeProvider将找出所选字符串代表的主题, 最后,我们设置主题夜间模式。

themePreference?.onPreferenceChangeListener =
            Preference.OnPreferenceChangeListener { _, newValue ->
                if (newValue is String) {
                    val theme = themeProvider.getTheme(newValue)
                    AppCompatDelegate.setDefaultNightMode(theme)
                }
                true
            }
        themePreference?.summaryProvider = getThemeSummaryProvider()

The static method AppCompatDelegate.setDefaultNightMode will apply at run-time the selected theme to your application, but beware, it will recreate any already started activity — assert you holding data in lifecycle-aware components.

静态方法AppCompatDelegate.setDefaultNightMode将在运行时将所选主题应用于您的应用程序,但是请注意,它将重新创建任何已开始的活动-断言您将数据保存在生命周期感知的组件中。

Preferences give us the option to set a summary provider for each preference.

首选项使我们可以选择为每个首选项设置摘要提供程序。

The summary provider will update a preference summary accordingly to it’s selected value.

摘要提供程序将根据其选择的值来更新首选项摘要。

private fun getThemeSummaryProvider() =
        Preference.SummaryProvider<ListPreference> { preference ->
            themeProvider.getThemeDescriptionForPreference(preference.value)
        }

At this point, the functionality is already up and running: the theme is applied as soon as the user selects one of the options.

至此,该功能已经启动并正在运行:用户选择其中一个选项后,就会立即应用主题。

But when restarting the app, we will see that it is launched with the default light theme again, even though user selection is persisted.

但是,当重新启动应用程序时,即使用户选择仍然存在,我们仍会看到它再次使用默认的灯光主题启动。

Dealing with this is easy, for this example, we will apply the selected theme on each Application creation:

这很容易处理,对于本示例,我们将在每次创建应用程序时应用选定的主题:

package com.lucianoluzzi.darkthemesample


import android.app.Application
import androidx.appcompat.app.AppCompatDelegate


class ThemeApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        val theme = ThemeProvider(this).getThemeFromPreferences()
        AppCompatDelegate.setDefaultNightMode(theme)
    }
}

Complete example code:

完整的示例代码:

翻译自: https://medium.com/swlh/androids-dark-mode-through-user-configurations-4df9d75b0db0

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值