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.
让我们找出一种简单的方法,为用户提供在暗模式 , 亮模式 , 或遵循系统夜间模式。
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目录中。
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.
注意entry和entryValues属性:它们将用作键值对。
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)
}
}
翻译自: https://medium.com/swlh/androids-dark-mode-through-user-configurations-4df9d75b0db0