This article is published simultaneously on my wechat public account. You can follow it by searching “Guo Lin” on wechat. The article is updated every weekday.

Hello everyone, today I’m bringing you an original article I wrote last year about Android 10 adaptation.

Earlier, I published an article about Android 10 adaptation, which covers scoped storage. See the Android 10 Adaptation tips, Scoped Storage link.

In addition to scoped storage, dark themes are one of the highlights of Android 10 and will need to be adapted by developers. Therefore, in this article we will explore the content of the dark theme.

By the way, this article is taken from Chapter 14 of The first Line of Code — Android Version 3, and I’ve expanded on it.

Why post a copy of the book on a blog? Mainly because the operation effect of dark theme has certain requirements for color, the black and white printing mode in the book is not easy to show the effect of dark theme intuitively, so I use an additional article to present. In addition to have not seen “the first line of code” friends, this is also a very good learning article.

one

The operating system we’ve been using has been based on a light theme, which works fine during the day or in well-lit conditions, but can be very harsh when the lights are off at night.

As a result, many apps have a one-click button inside the app that allows users to switch to night mode to make it easier to use in low-light environments. When the user turns on the night mode, the overall color palette of the application is adjusted to make it more suitable for nighttime browsing.

However, this application implementation of the nightly mode is difficult to achieve global uniformity, meaning that some applications may support the nightly mode, while others do not. Repeat operations are also a headache. For example, IF I turn on night mode in one app, I have to turn it on again in another app, and I have to repeat the same operation to turn off night mode.

As a result, there have been strong calls for Android to support night mode at the system level. In Android 10.0, Google has finally introduced dark themes, making night mode officially supported.

You may be wondering why Android didn’t support this seemingly innocuous feature until 10.0. This is because it is not useful for the operating system itself to support dark themes; it has to be supported by all applications, which is never easy.

For this reason, all future applications should support dark themes as much as possible according to the requirements of the Android system. Otherwise, when users turn on dark themes, only your application still uses a light theme, it will look out of place.

two

In addition to making your eyes more comfortable at night, a dark theme can also reduce battery consumption and thus extend your phone’s battery life, a very useful feature. So, let’s start learning how to make the application support dark themes.

First of all, for Android 10.0 and above, Dark theme can be turned on and off in Settings -> Display -> Dark Theme. When dark theme is turned on, the system’s interface style includes some built-in applications that change to dark theme tones, as shown below.

However, if you open up our own application at this point, you will see that the current interface style is still using a light theme mode, which is different from the system’s theme style, indicating that we need to adapt to this.

Here, I’m going to use the MaterialTest project I wrote in Chapter 12 as an example to see how it can be made to better fit the dark-topic schema. (see sample download address link download www.ituring.com.cn/book/2744) with books.

First take a look at the initial run of the MaterialTest project.

Next we’ll learn how to fit the dark theme pattern.

The simplest adaptation is to use Force Dark, which is a way for applications to quickly adapt to Dark themes with little extra code to be written. Force Dark works by analyzing each layer of views under a light-colored theme and automatically converting them to a color more appropriate for a Dark theme before drawing them on the screen. Note that only applications with a light theme can use this method. If your application is using a Dark theme, Force Dark will not work.

Here we try to use the Force Dark transformation for the MaterialTest project as an example. To enable Force Dark, you need to use the Android :forceDarkAllowed property. However, this property is available from API 29 (android 10.0) and cannot be specified in previous operating systems. So, we need to do some system differential programming.

Right-click the res Directory -> New -> Directory to create a values-v29 Directory. Then right-click the values-v29 Directory -> New -> Values resource file to create a styles.xml file. Then write the file as follows:

<resources>
    <style >
        <item >@color/colorPrimary</item>
        <item >@color/colorPrimaryDark</item>
        <item >@color/colorAccent</item>
        <item >true</item>
    </style>
</resources>

Copy the code

three

With the exception of the Android :forceDarkAllowed attribute, everything else is copied from the previous styles.xml file. Add the Android :forceDarkAllowed property to the AppTheme and set it to true, which means we now allow the system to use Force Dark to Force the app to a Dark theme. In addition, the values-v29 directory is only read by Android 10.0 and above, so it is a system differential programming implementation.

Now re-run the MaterialTest project as shown below.

It can be seen that although the overall interface style does seem to change into a Dark theme mode, it is not very beautiful, especially the effect of the card layout, after Force Dark has been completely invisible.

Force Dark is such a simple and crude conversion method, and its conversion effect is usually not satisfactory. Therefore, I do not recommend that you implement dark themes in such an automated way, but rather use the more traditional way of implementing them manually.

Yes, the best way to achieve dark themes is not to rely on magic to do it all in one click, but to design both light and dark themes for every interface. This may sound a bit complicated, but there are some handy tricks to make the process easier.

We once studied in chapter 12, AppCompat library built-in Theme just mainly divided into two categories, Light and dark themes, such as MaterialTest projects currently in use in the Theme. AppCompat. Light. NoActionBar is Light color Theme, And the Theme. AppCompat. NoActionBar is dark themes. Choose different themes, in the control of the default color and other aspects will have a completely different effect.

Let’s give it a try. First delete the values-v29 directory and the contents under it, then modify the code in values/styles.xml as follows:

<resources> <style > <! -- Customize your theme here. --> <item >@color/colorPrimary</item> <item >@color/colorPrimaryDark</item> <item > @ color/colorAccent < item > < style >... </resources>Copy the code

Here you can see, we have designated AppTheme parent Theme became a Theme. The AppCompat. DayNight. NoActionBar, this is a kind of DayNight Theme. Therefore, in normal cases, the MaterialTest project will still use a light-colored theme, which is the same as before, but once the user opens a dark-colored theme in the system Settings, the MaterialTest project will automatically use the corresponding dark-colored theme.

Now we can re-run the program to see what the default interface of the MaterialTest project looks like using the DayNight theme, as shown below.

It’s clear that the interface looks much better than it did with the Force Dark conversion, at least with the card layout.

However, while the main content in the interface is now automatically switched to a dark theme, you’ll notice that the title bar and hover buttons remain the same color as with the light theme. This is because the title bar and hover button use several color values we defined in colors.xml, as shown below:

<resources>
    <color >#008577</color>
    <color >#00574B</color>
    <color >#D81B60</color>
</resources>

Copy the code

Specifying color value references in this way hardcodes the colors of the controls, and the DayNight theme cannot dynamically convert them.

Fortunately, the solution isn’t complicated, and we just need to do some thematic differential programming. Right-click res Directory -> New -> Directory to create a values-night Directory, then right-click values-night Directory -> New -> Values resource file, Create a colors.xml file. Then specify the color value under the dark theme in this file, as follows:

<resources>
    <color >#303030</color>
    <color >#232323</color>
    <color >#008577</color>
</resources>

Copy the code

In this case, the values in the values/colors.xml file will still be read in the normal case, but the values in the values-night/colors.xml file will be read once the user has turned on the dark theme.

Now run the program again. The result should look like the following image.

This dark theme is obviously much better at night, isn’t it?

four

While theme-differentiated programming can help you solve almost all of your adaptation problems, with DayNight themes, it’s best to minimize hard-coding and use theme properties that automatically switch colors based on the current theme. For example, black text should always be set against a white background, and white text should always be set against a black background. In this case, we can use the theme property to specify the background and color of the text as follows:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="?android:attr/colorBackground">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Hello world"
        android:textSize="40sp"
        android:textColor="?android:attr/textColorPrimary" />

</FrameLayout>

Copy the code

These theme attributes will automatically select the most appropriate color values according to the current theme mode of the system and present them to the user, as shown in the picture below.

In addition, you may have special requirements, such as executing different code logic in light and dark themes. Android also supports this, and you can use the following code at any time to determine if the current system is a dark theme:

fun isDarkTheme(context: Context): Boolean {
    val flag = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
    return flag == Configuration.UI_MODE_NIGHT_YES
}

Copy the code

Call the isDarkTheme() method to determine whether the current system has a light or dark theme, and then execute the different code logic based on the returned value.

Because the Kotlin cancelled the bitwise operators method and changed to use keywords in English, so the above code and keywords are corresponding to the & operator in Java, and the Kotlin or key corresponding to the | operator in Java, The xor keyword corresponds to the ^ operator in Java and is easy to understand.

In my opinion, in most cases, it is best to let the application follow the system’s Settings to decide whether to use a light or dark theme. From the system Settings, but if you must want to make your own applications use light color theme or independent control darker themes, the Android also support this, as long as you use AppCompatDelegate. SetDefaultNightMode () method.

The setDefaultNightMode() method receives a mode parameter that controls the night mode of the current application. Mode parameters can be selected from the following values:

  • MODE_NIGHT_FOLLOW_SYSTEM: Default mode, which allows the current application to follow the system Settings to decide whether to use a light or dark theme.

  • MODE_NIGHT_YES: Forces the current application to use a dark theme without the system setting.

  • MODE_NIGHT_NO: Forces the current application to use a light-colored theme by leaving the system setting.

  • MODE_NIGHT_AUTO_BATTERY: Determines whether to use a light or dark theme based on the battery status of the phone. If node mode is enabled, use a dark theme.

In MaterialTest, we only need to use the following code to realize the function of dynamic switching between light-colored topic and dark-colored topic:

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {... fab.setOnClickListener { view -> if (isDarkTheme(this)) { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) } else { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) } } } ... }Copy the code

The result after running is shown below.

Note that when the setDefaultNightMode() method is called and the theme is successfully switched, All activities in the application that are in the started state are recreated. (Activities that are not in the started state are recreated when they resume. For an explanation of started, see Chapter 13, Lifecycles section, First Line of Code, 3rd Edition). This makes sense, because how can an Activity switch themes without recreating an Activity?

But if you’re currently in an interface that doesn’t want to be recreated, like an interface that’s playing a video. You can configure uiMode in the Activity configChanges property to prevent the current Activity from being recreated, as shown below:

<activity
    android:
    android:configChanges="uiMode" />

Copy the code

Now when the theme of the application changes, instead of being recreated, MainActivity triggers a callback to the onConfigurationChanged() method, where you can do some manual logic.

override fun onConfigurationChanged(newConfig: Configuration) {
    val currentNightMode = newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK
    when (currentNightMode) {
        Configuration.UI_MODE_NIGHT_NO -> {} 
        Configuration.UI_MODE_NIGHT_YES -> {} 
    }
}

Copy the code

If you do nothing, the current Activity will behave as if it had not switched themes, and nothing will change in the interface.

Well, that’s enough for Android 10’s dark-themed adaptations. To learn more about the latest Android, check out my new book, Line 1 of Code — Android Version 3, Kotlin, Jetpack, MVVM, and everything you care about. Click here for details.

Pay attention to my technical public account “Guo Lin”, there are high-quality technical articles pushed every day.