Allows your Android app to use multiple themes (Part 2) and androidpart
Original article: Supporting multiple themes in your Android app (Part 2)
Translator: chaossss
Proofreader: Mr. Simple
Status: complete
In the first part of this post, we have created a light theme and made initial preparation to support multiple themes. in this blog post, we will continue that effort, creating another theme and allowing dynamic switching of themes during runtime.
In the previous blog, we created a bright style topic and made some preparations for using multiple themes. What about today, I plan to continue with the previous blog post in this blog post. The content I want to talk about today is about the following three parts: Enable Android applications to use multiple themes, create a dark-style topic and allow the Android app to switch between different topics at runtime.
Ideally, if we treat theme as a configuration, we shocould be able to specify theme-specific resources under a 'Theme-qualifier 'resources directory, e.g. values-dark for dark theme resources and values-light for light theme resources. unfortunately, this is not yet an option at the time of this post.
Ideally, if we regard theme settings as a configuration, we should be able to specify the desired theme in a directory similar to theme-qualifier, for example: values-dark is the gray style theme we want, while values-light is the bright style theme. But unfortunately, in the implementation method described in this blog, this method has not become one of the implementation methods.
So how shoshould we specify resources for multiple themes? If we look at how resources are organized in appcompat, we will have a rough idea of how the Android team organize their theme specific resources. Materialistic also employs a similar approach.
So how do we specify the corresponding resource files for different themes? If we know how appcompat uses resource files, we will have a rough understanding of how the Android system manages and uses resource files. There is no doubt that the method used in Materialistic is similar to that used in Android.
Theming topic settings
Values/styles. xml
<style name = "AppTheme" parent = "Theme.AppCompat.Light">
<!-original theme attributes->
...
</ style>
<style name = "AppTheme.Dark" parent = "Theme.AppCompat">
<item name = "colorPrimary"> @ color / colorPrimaryInverse </ item>
<item name = "colorPrimaryDark"> @ color / colorPrimaryDarkInverse </ item>
<item name = "colorAccent"> @ color / colorAccentInverse </ item>
<item name = "android: textColorPrimary"> @ color / textColorPrimaryInverse </ item>
<item name = "android: textColorSecondary"> @ color / textColorSecondaryInverse </ item>
<item name = "android: textColorPrimaryInverse"> @ color / textColorPrimary </ item>
<item name = "android: textColorSecondaryInverse"> @ color / textColorSecondary </ item>
...
</ style>
values / color.xml
<!-original color palette->
...
<!-alternative color palette->
<color name = "colorPrimaryInverse"> ... </ color>
<color name = "colorPrimaryDarkInverse"> ... </ color>
<color name = "colorAccentInverse"> ... </ color>
Here we add a new dark theme called AppTheme.Dark, and for style and color consistency, we extend from appcompat's theme Theme.AppCompat (a dark theme). Unfortunately, since our two themes extend two different base themes, we cannot share any common attributes (the same way a class in Java cannot extend two or more classes).
In the above operation, we created a dark style theme called AppTheme.Dark. In addition, in order to maintain the consistency of style and color, our AppTheme.Dark theme is derived from appcompat's Theme.AppCompat theme (an Android comes with Dim style theme). However, since our two themes (bright style and dark style) are derived from different basic themes, it is not possible to share attributes between these two themes (in JAVA, classes can only be single inherited).
The two themes should have appropriate (different if applicable) values for base Android and appcompat theme attributes, eg android: textColorPrimary for dark theme should be light, and for light theme should be dark. By convention, here we suffix alternative theme colors with Inverse .
These two themes should have some appropriate attribute values, which can be used to set basic Android and appcompat theme attributes, for example: in the dark style, android: textColorPrimary should be set to bright, and in the bright style, android : textColorPrimary should be gray. According to common naming conventions, we will use the opposite suffix here to distinguish alternative theme colors.
Tip
Try out your alternative theme by temporary switching android: theme for application in AndroidManifest.xml to see what extra colors / style you need to create. For certain cases a color may look okay in both dark and light theme.
Tips
In some cases, a color can be used in both bright and dark-themed themes, which is of course a favorite, but it is not possible in most themes. So I hope that in the process of designing the theme, you can briefly switch the alternative theme that is being used in your application in AndroidManifest.xml to determine whether your theme needs to add other colors / style files to meet your theme Design requirements.
Theme-specific resources
At this point, we should have a pretty decent dark theme for our app, except for some anomalies here and there, eg drawables used for action bar menu items. A dark action bar expects light-color menu items, and vice versa. In order to tell Android to use different drawables for different app themes, we create custom attributes that allow specifying reference to the correct drawable, and provide different drawable references as values for these custom attributes under different themes (the same way appcompat library provides custom attributes such as colorPrimary).
Specific theme resource file
Up to now, I believe that we can easily design a picturesque gray style theme for our App, but there are still some small troubles here, such as: drawables resource file for beautifying the action bar menu options. The gray-style action bar needs to decorate its menu options with bright colors, and vice versa. In order to allow Android to distinguish different drawables resource files under different App themes, we created custom attribute references that can specify the correct resource files, and provided different drawable references under different themes, assigning their values to specific Custom attributes. (Wan Wan is like a wife, the appcompat library has prepared a custom attribute value similar to colorPrimary for us intimately)
values / attrs.xml
<attr name = "themedMenuStoryDrawable" format = "reference" />
<attr name = "themedMenuCommentDrawable" format = "reference" />
...
values / styles.xml
<style name = "AppTheme" parent = "Theme.AppCompat.Light">
<!-original theme attributes->
...
<item name = "themedMenuStoryDrawable"> @ drawable / ic_subject_white_24dp </ item>
<item name = "themedMenuCommentDrawable"> @ drawable / ic_mode_comment_white_24dp </ item>
</ style>
<style name = "AppTheme.Dark" parent = "Theme.AppCompat">
<!-alternative theme attributes->
...
<item name = "themedMenuStoryDrawable"> @ drawable / ic_subject_black_24dp </ item>
<item name = "themedMenuCommentDrawable"> @ drawable / ic_mode_comment_black_24dp </ item>
</ style>
menu / my_menu.xml
<menu xmlns: android = "http://schemas.android.com/apk/res/android">
<item android: id = "@ id / menu_comment"
android: icon = "? attr / themedMenuCommentDrawable" />
<item android: id = "@ id / menu_story"
android: icon = "? attr / themedMenuStoryDrawable" />
<item android: id = "@ id / menu_share"
app: actionProviderClass = "android.support.v7.widget.ShareActionProvider" />
</ menu>
Similar implementation can be used to specify most custom attributes you need for theme specific resource values. One hiccup to this approach is that attribute resolving in drawable resources seems to be broken before API 21. For example, if you have a drawable which is a layer -list of colors, their values must be fixed for API <21. See this commit from Google I / O 2014 app for a fix.
Depending on your actual theme design needs, similar implementations can also be used to specify corresponding resource values for most custom properties. But there is a problem with this method: According to the actual needs, the corresponding attribute values are parsed from the drawable resource file, and the methods applied to the theme seem to be not feasible in versions prior to API 21. Take an example to illustrate this problem: if you have a layer-list that contains various drawable resource files of the colors you need, in the version before API 21, the values of these colors should be fixed, not It changes constantly during the running of the App. This problem was raised at the Google I / O 2014 conference, and a corresponding solution was requested. (See details Click Me!).
An alternative approach to avoid duplicating drawable resources for different themes is to use drawable tint. This attribute is available from API 21. Dan Lew in his blog shows how to do this for all API levels. Personally I would prefer to keep my Java implementation free of view logic if possible, so I choose to have different drawable resources per theme.
In addition, in order to avoid reusing the same resource file in different themes, we can use the tint attribute of drawable to solve this demand. Although this property can be used in API 21 and later. But Dan Lew explained in his blog how to use the tint attribute in all API versions. But in terms of personal preference, if possible, I would prefer to choose a Java implementation that is not affected by View logic, so I chose to provide different drawable resource files for each theme.
Dynamic theme switching
Now that we have two polished themes ready to be used, we need to allow users to choose which one they prefer and switch theme dynamically during runtime. This can be done by having a SharedPreferences, says pref_dark_theme to store theme preference and use its value to decide which theme to apply. Application of theme should be done for all activies, before their views are created, so onCreate () is our only option to put the logic.
Dynamic theme switching
Now that we have two themes that can be used, the next thing we need to do is to allow users to switch between different themes according to their personal preferences when using the app. To achieve this function, we can use SharedPreferences to achieve, by changing the value of pref_dark_theme to store the currently selected theme and decide what theme our App will be switched to. However, considering the actual situation, after the theme is switched, the View of all the activities of the App should be changed before it is created, so we only need to implement our logic in the onCreate () method.
BaseActivity.java
public abstract class BaseActivity extends ActionBarActivity {
@Override
protected void onCreate (Bundle savedInstanceState) {
if (PreferenceManager.getDefaultSharedPreferences (this)
.getBoolean ("pref_dark_theme"), false)) {
setTheme (R.style.AppTheme_Dark);
}
super.onCreate (savedInstanceState);
}
}
Here, since our app already has a default light theme, we only need to check if default preference has been overriden to override dark theme. The logic is put in the ‘base’ activity so it can be shared by all activities.
Here, because our app already uses the default bright style theme, we only need to check whether the default reference is overloaded and whether it is used to overload the dark style theme. In order for the default reference to be shared by all activities, the logic has been written in the "base" activity.
Note that this approach will only apply theme for activities that are not in the back stack. For those that are already in current stack, they will still exhibit previous theme, as going back will only trigger on Resume (). Depends on product requirements, the implementation to handle these 'stale' screens can be as simple as clearing the back stack, or restarting every single activity in the back stack upon preference change. Here we simply clear back stack and restart current activity upon theme change.
It is worth noting that this method can only be used to change the theme of Acitivity that is not in the back stack. The activities that are already in the back stack will still be displayed as the previous theme, because when we end the current Activity and return to the previous Activity, only the onResume () method will be triggered, not the onCreate () method we expect. Therefore, considering the actual product functional design requirements, we of course have to solve these "outdated" activities. I am here to provide you with two solutions, which are quite simple: on the one hand, we can empty our back stack ; On the other hand, once the preference is changed, we will make all Acitivty pop out of the stack and reload in order in the back stack, and change all Activity themes before re-stacking. For simplicity here, the implementation method we chose is: when the theme is changed, we simply clear the back stack, and then restart the current Activity.
SettingsFragment.java
public class SettingsFragment extends PreferenceFragment {
...
@Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated (savedInstanceState);
mListener = new SharedPreferences.OnSharedPreferenceChangeListener () {
@Override
public void onSharedPreferenceChanged (SharedPreferences sharedPreferences, String key) {
if (! key.equals ("pref_dark_theme")) {
return;
}
getActivity (). finish ();
final Intent intent = getActivity (). getIntent ();
intent.setFlags (Intent.FLAG_ACTIVITY_NEW_TASK | IntentCompat.FLAG_ACTIVITY_CLEAR_TASK);
getActivity (). startActivity (intent);
}
};
}
...
So that ’s it. Now we have an app with two polished themes for even the most picky users! Head over to hidroh / materialistic GitHub repository to checkout complete implementation!
Although it ended a bit abruptly, our explanation for today is over. Now that our app has two such elegant themes, even the picky literary arts will not dismiss our app as low! If you want to know the specific implementation of the entire Materialistic, or the source code of this function, you can get it on my GitHub ~
============= ↑ Article content display ↑ =============