Every Android developer had to work with styles one way or another. Someone feels confident with them, someone has only superficial knowledge, which often does not allow them to solve the problem on their own.
In anticipation of the release of the dark theme, it was decided to refresh in memory all the information relating to themes and styles in Android applications.
In their structure, themes and styles have a common structure:
<style name="MyStyleOrTheme"> <item name="key">value</item> </style>
To create, use the style
tag. Each style has a name and it stores the key-value
parameters.
Everything is quite simple. But what is the difference between theme and style?
The only difference is how we use them.
A theme is a set of parameters that apply to the entire application, Activity, or View component. It contains the basic colors of the application, styles for rendering all components of the application and various settings.
Example topic:
<style name="Theme.MyApp.Main" parent="Theme.MaterialComponents.Light.NoActionBar"> <!--Base colors--> <item name="colorPrimary">@color/color_primary</item> <item name="colorPrimaryVariant">@color/color_primary_variant</item> <item name="colorSecondary">@color/color_secondary</item> <item name="colorOnPrimary">@color/color_on_primary</item> <item name="colorOnError">@color/color_on_error</item> <!--Style attributes--> <item name="textAppearanceHeadline1">@style/TextAppearance.MyTheme.Headline1</item> <item name="bottomSheetDialogTheme">@style/ThemeOverlay.MyTheme.BottomSheetDialog</item> <item name="chipStyle">@style/Widget.MaterialComponents.Chip.Action</item> <item name="textInputStyle">@style/Widget.MaterialComponents.TextInputLayout.FilledBox</item> <!--Params--> <item name="android:windowTranslucentStatus">true</item> <!-- ... --> </style>
The theme has redefined the main colors of the application ( colorPrimary
, colorSecondary
), the style for the text ( textAppearanceHeadline1
) and some standard application components, as well as the parameter for the transparent status bar.
In order for the style to become a real topic, it is necessary to inherit (we will talk about inheritance a bit later) from the default implementation of the topic.
A style is a set of parameters for styling a single View component.
Sample style for TextInputLayout :
<style name="Widget.MyApp.CustomTextInputLayout" parent="Widget.MaterialComponents.TextInputLayout.FilledBox"> <item name="boxBackgroundMode">outline</item> <item name="boxStrokeColor">@color/color_primary</item> <item name="shapeAppearanceOverlay">@style/MyShapeAppearanceOverlay</item> </style>
An attribute is a style or theme key. These are small bricks from which everything is built:
colorPrimary colorSecondary colorOnError boxBackgroundMod boxStrokeColor shapeAppearanceOverlay ...
All of these keys are standard attributes.
We can create our own attributes:
<attr name="myFavoriteColor" format="color|reference" />
The myFavoriteColor
attribute will point to a color or link to a color resource.
In the format, we can specify quite standard values:
By its nature, an attribute is an interface . It must be implemented in the topic:
<style name="Theme.MyApp.Main" parent="Theme.MaterialComponents.Light.NoActionBar"> <!-- ... --> <item name="myFavoriteColor">@color/color_favorite</item> </style>
Now we can refer to it. The general structure of the appeal looks like this:
1 — , ; 2 — namespace ( Material Components Library); 3 — , (); 4 — .
Well, finally, let's change, for example, the text color of the field:
<androidx.appcompat.widget.AppCompatTextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="?attr/myFavoriteColor"/>
Thanks to the attributes, we can add any kind of abstraction that will change inside the topic.
As in OOP, we can adopt the functionality of an existing implementation. There are two ways to do this:
For explicit inheritance, we specify the parent using the parent
keyword:
<style name="SnackbarStyle" parent="Widget.MaterialComponents.Snackbar"> <!-- ... --> </style>
For implicit inheritance, we use dot-notation
to indicate the parent:
<style name="SnackbarStyle.Green"> <!-- ... --> </style>
There is no difference in the work of these approaches.
Very often we can meet similar styles:
<style name="Widget.MyApp.Snackbar" parent="Widget.MaterialComponents.Snackbar"> <!-- ... --> </style>
It may seem that the style is created by double inheritance. This is actually not the case. Multiple inheritance is prohibited. In this definition, explicit inheritance always wins .
That is, a style will be created with the name Widget.MyApp.Snackbar
, which is the descendant of Widget.MaterialComponents.Snackbar
.
ThemeOverlay - these are special "lightweight" themes that allow you to override the attributes of the main theme for the View-component.
We will not go far for an example, but take a case from our application. Designers decided that we need to create a standard login field, which will have a color different from the main style.
With the main topic, the input field looks like this:
It looks great, but the designers insist that the field be in a brown style.
Okay, how can we solve such a problem?
Override the style?
Yes, we can redefine the style and manually change the main colors of the view, but for this you will need to write a lot of code, and there is a chance that we will forget about something.
Write your view on guidelines and with custom parameters?
A good option, so we can satisfy any Wishlist of designers and at the same time pump skill, but all this is laborious and can lead to unwanted bugs.
Override the primary color in the theme?
We found out that for the type we need, just change the colorPrimary
in the topic. A working option, but in this way we will affect the appearance of the remaining components, but we do not need this.
The correct solution is to use ThemeOverlay .
Create ThemeOverlay
and redefine the main color of the theme :
<style name="ThemeOverlay.MyApp.Login" parent="ThemeOverlay.MaterialComponents.TextInputEditText"> <item name="colorPrimary">@color/colorBrown</item> </style>
Next, we specify it using the special android:theme
tag in our TextInputLayout
:
<com.google.android.material.textfield.TextInputLayout android:theme="@style/ThemeOverlay.MyApp.Login" android:hint="Login" ... > <com.google.android.material.textfield.TextInputEditText ... /> </com.google.android.material.textfield.TextInputLayout>
Everything works as we need.
Of course, the question arises - how does it work under the hood?
This magic allows you to crank ContextThemeWrapper
. When creating a View in LayoutInflater
, a context will be created where the current theme will be taken as a basis and the parameters that we specified in our Overlay theme will be redefined in it.
../LayoutInflater.java final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME); final int themeResId = ta.getResourceId(0, 0); if (themeResId != 0) { context = new ContextThemeWrapper(context, themeResId); } ta.recycle();
Similarly, we can independently override any theme parameter in the application.
The main priority is the markup file. If a parameter is defined in it, then all similar parameters will be ignored.
<Button android:textColor="@color/colorRed" ... />
The next priority is the View style:
<Button style=“@Widget.MyApp.ButtonStyle" ... />
The following uses predefined styles for the component:
<style name="Theme.MyApp.Main" parent="Theme..."> <item name=“materialButtonStyle”>@Widget.MyApp.ButtonStyle</item> <!-- ... --> </style>
If no parameters were found, then the theme attributes are used:
<style name="Theme.MyApp.Main" parent="Theme..."> <item name=“colorPrimary”>@colorPrimary</item> <!-- ... --> </style>
In general, this is all you need to know in order to start working with topics. Now let's take a quick look at the updated Material Components design library.
Material Components was introduced at Google I / O 2018 and is a replacement for Design Support Library.
The library gives us the opportunity to use the updated components from Material Design 2.0. In addition, a lot of interesting customization options appeared in it. All this allows you to write bright and unique applications.
Here are some examples of applications in the new style: Owl , Reply , Crane .
To create a topic, you need to inherit from the base topic:
Theme.MaterialComponents Theme.MaterialComponents.NoActionBar Theme.MaterialComponents.Light Theme.MaterialComponents.Light.NoActionBar Theme.MaterialComponents.Light.DarkActionBar Theme.MaterialComponents.DayNight Theme.MaterialComponents.DayNight.NoActionBar Theme.MaterialComponents.DayNight.DarkActionBar
All of them are very similar to AppCompat
themes, but have additional attributes and settings.
You can learn more about the new attributes at material.io .
If for some reason you can’t switch to a new topic now, then Bridge
themes will do. They inherit from AppCompat
themes and have all new Material Components attributes. You just need to add the Bridge
postfix and use all the features without fear:
<!-- ... --> Theme.MaterialComponents.Light.Bridge <!-- ... -->
And here is our topic:
<style name="Theme.MyApp.Main" parent="Theme.MaterialComponents.Light.NoActionBar"> <item name="colorPrimary">@color/color_primary</item> <item name="colorPrimaryVariant">@color/color_primary_variant</item> <item name="colorSecondary">@color/color_secondary</item> <item name="colorSecondaryVariant">@color/color_secondary_variant</item> <style>
The names of the primary colors (brand-colors) have changed:
colorPrimary — ( AppCompat); colorPrimaryVariant — ( colorPrimaryDark AppCompat); colorSecondary — ( colorAccent AppCompat); colorSecondaryVariant — .
Further information on colors can be found at material.io .
I already mentioned that the theme contains standard styles for each View component. For example, for Snackbar
style will be called snackbarStyle
, for checkbox
will be called checkboxStyle
and then everything will be similar. An example will put everything in its place:
Create your own style and apply it to the theme:
<style name="Theme.MyApp.Main" parent="Theme.MaterialComponents.Light.NoActionBar"> <!-- ... --> <item name="snackbarStyle">@style/Widget.MyApp.SnackbarStyle</item> </style> <style name="Widget.MyApp.SnackbarStyle" parent="Widget.MaterialComponents.Snackbar"> <!-- ... --> </style>
It is important to understand that when you redefine a style in a theme, it will apply to all View of this type in the application (Activity).
If you want to apply the style to only one specific View, then you need to use the style
tag in the markup file:
<com.google.android.material.button.MaterialButton style="@style/Widget.MyApp.SnackbarStyle" ... />
One of the innovations that really impressed me was ShapeAppearance . It allows you to change the shape of components right in the subject!
Each View component belongs to a certain group:
shapeAppearance Small Component
shapeAppearance Medium Component
shapeAppearance Large Component
As we can understand from the name, in groups of views of different sizes.
Check in practice:
<style name="Theme.MyApp.Main" parent="Theme.MaterialComponents.Light.NoActionBar"> <!-- ... --> <item name="shapeAppearanceSmallComponent">@style/Widget.MyApp.SmallShapeAppearance</item> </style> <style name="Widget.MyApp.SmallShapeAppearance" parent=“ShapeAppearance.MaterialComponents.SmallComponent”> <item name="cornerFamilyTopLeft">rounded</item> <item name="cornerFamilyBottomRight">cut</item> <item name="cornerSizeTopLeft">20dp</item> <item name="cornerSizeBottomRight">15dp</item> <!--<item name="cornerFamily">cut</item>--> <!--<item name="cornerSize">8dp</item>--> </style>
We created Widget.MyApp.SmallShapeAppearance
for the "small" components. We rounded the upper left corner by 20dp
and the lower right corner cut off by 15dp
.
Got this result:
It looks interesting. Will it work in real life? Time will tell.
As with styles, we can apply ShapeAppearance
only one View component.
Soon Android Q release will take place, and with it the official dark theme will come to us.
Perhaps one of the most interesting and spectacular features of the new version of Android is the automatic application of a dark theme for the entire application with one line of code.
Sounds great, let's try. I suggest taking a favorite gitlab client from terrakok .
Allow repainting the application (disabled by default):
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <!-- ... --> <item name="android:forceDarkAllowed">true</item> </style>
The android:forceDarkAllowed
is available with API 29 (Android Q).
We start, look what happened:
Agree that for one line of code it looks very cool.
Of course, there are problems - BottomNavigationBar
merges with the background, the loader remains white, the selection of code suffers and, it seems, everything, at least nothing serious has struck me.
I’m sure that spending not so much time can solve the main problems. For example, turning off the automatic dark mode for individual views (yes, it is also possible - android:forceDarkAllowed
is available for View in a markup file).
It should be remembered that this mode is available only for light themes, if you use a dark one, then a forced dark theme will not work.
Work recommendations can be found in the documentation and on material.io .
No matter how easy it is to use a forced dark theme, this mode is devoid of flexibility. In fact, everything works according to predefined rules that may not suit us and, more importantly, the customer. I think that such a decision can be considered temporary, until we make our implementation of a dark topic.
In API 8 (Froyo), the -night
qualifier was added, which to this day is used to apply a dark theme. It allows you to automatically apply the desired theme depending on the time of day.
DayNight
themes already use such an implementation, it’s enough for us to inherit from them.
Let's try to write our own:
../values/themes.xml <style name="Theme.DayNight.Base" parent="Theme.MaterialComponents.Light"/> <style name="Theme.MyApp.Main" parent="Theme.DayNight.Base> <!-- ... --> </style> ../values-night/themes.xml <style name="Theme.DayNight.Base" parent="Theme.MaterialComponents"/>
In the usual resource for the theme ( values/themes.xml
) we inherit from the light theme, in the "night" ( values-night/themes.xml
) we inherit from the dark theme.
That's all. We got a library implementation of a dark theme. Now we should support resources for two topics.
To switch between themes while the application is running, you can use AppCompatDelegate.setDefaultNightMode
, which takes the following parameters:
MODE_NIGHT_NO
- Light theme;MODE_NIGHT_YES
- Dark theme;MODE_NIGHT_AUTO_BATTERY
- Automatic mode. The dark theme turns on if the power saving mode is active;MODE_NIGHT_FOLLOW_SYSTEM
- Mode based on system settings.As I noted, Google has officially begun to force a dark topic. I am sure that many customers began to receive questions - “Can we add a dark theme?”. It’s good if you do everything right from the very beginning and it’s easy for you to change the light colors to dark, while receiving a completely repainted application.
Unfortunately, this is not always the case. There are old applications that require significant effort in order to make the necessary changes.
Let's try to formulate recommendations for working with styles together:
I think that every developer is faced with a situation when some strange color appears in the new layout that is not yet defined in the application palette. What to do in this case?
The correct answer is to talk to the designer and try to work out a color palette. Now there are many programs (Zeplin, Sketch, etc.) that allow you to render primary colors and then reuse them.
The sooner you do this, the less headaches you will have in the future.
In each application, there is a color that has many brightness options. You can start inventing names for them:
<color name="green_tiny">...</color> <color name="green_light">...</color> <color name="green_dark">...</color>
Agree, it does not look very. The question immediately arises - which color is lighter than tiny
or light
? And if we have a dozen options?
It’s best to stick with the Google concept and add the appropriate brightness to the color names (Google calls this color option - colorVariant
):
<color name="material_green_300">...</color> <color name="material_green_700">...</color> <color name="material_green_900">...</color>
With this approach, we can have any number of brightness options of one color and we don’t have to come up with specific names, which is really difficult.
Since we are writing an application in which there will be at least two topics, we cannot afford to refer to a specific color if it is implemented differently in the themes.
Let's look at an example:
We see that in a light theme, for example, the toolbar is colored in purple, and in the dark it is dark gray. How would we implement this behavior using only the capabilities of themes?
Everything is quite simple - we will create an attribute and implement it in light and dark themes with the appropriate color, as described previously.
Google recommends associating attribute names with usage semantics.
When a lot of different styles, themes and attributes are typed in the styles.xml
file, it becomes difficult to maintain.
It’s best to group everything into separate files:
themes.xml — Theme & ThemeOverlay styles.xml — Widget styles type.xml — TextAppearance, text size etc shape.xml — ShapeAppearance motion.xml — Animations styles system_ui.xml — Booleans, colors for UI control //may be other files
Such a simple rule will avoid God-files and, therefore, it will be easier to maintain styles.
What will we do if we want to redefine an attribute that is available only from a specific version of the API?
We can create two separate topics:
../values/themes.xml <style name="Theme.MyApp.Main" parent=”Theme.MaterialComponents.NoActionBar”> <!--Many definition--> </style> ../values-v27/themes.xml <style name="Theme.MyApp.Main" parent=”Theme.MaterialComponents.NoActionBar”> <!--Many definition--> <name="android:windowLightNavigationBar">...</item> </style>
Do we now have a theme with all the parameters for each version of the API? Of course not! We will make a basic topic where the basic attributes that are available for all versions of the API will be determined and inherited from it in the desired version of the API:
../values/themes.xml <style name="Theme.MyApp.Base" parent=”Theme.MaterialComponents.DayNight.NoActionBar”> <!--Many definition--> </style> <style name="Theme.MyApp.Main" parent=”Theme.MyApp.Base”/> ../values-v27/themes.xml <style name="Theme.MyApp.Base.V27" parent="Theme.MyApp.Base"> <name="android:windowLightNavigationBar">...</item> </style> <style name="Theme.MyApp.Main" parent=”Theme.MyApp.Base.V27”/>
By this principle, all topics in the standard library are built.
I think it’s not worth saying why vector resources are good. Everyone already knows (just in case, a link to the documentation ). Well, tinting will help us color them in theme colors.
You can see what tinting is and how to work with it in this example .
When accessing resources, we have the opportunity to use both system attributes and attributes from the Material Components library. It is important to understand that some attributes exist only with a specific version of the API. As we all know well, accessing a non-existent resource leads to a crash (lint, of course, will tell us if something is wrong, but you should not always rely on it)
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
In the first case, we access the system resource, as indicated by android
. In the second case, to an attribute from the library where backward compatibility is implemented.
It is better to always use the second option.
There can be parameters in the parent style, without which the component will be incorrectly rendered, so you should always specify the parent.
<style name="Widget.MyApp.LoginInputLayout" parent="Widget.MaterialComponents.TextInputLayout.FilledBox"> <item name="errorTextColor">@color/colorError</item> </style>
When creating your own themes and styles, it will be great if you specify a prefix that says what kind of style it is and what it is defined for. Such naming will make it very easy to structure and extend styles.
<style name="Theme.MyApp.Main" parent=”...”/> <style name="Widget.MyApp.LoginInputLayout" parent="..."/> <style name="Widget.MyApp.LoginInputLayout.Brown"/> <style name="ThemeOverlay.MyApp.Login" parent=”...”/>
It will be a good tone to expand the basic styles for the text and use them everywhere.
A lot of useful information can be found on Material Design: Typography , Typography Theming .
In conclusion, I want to say that styling an application is the responsibility of not only developers but also designers. - . Material Components. . Sketch — Material Theme Editor . . , .
Material Components GitHub — Modular and customizable Material Design UI components for Android . . , — sample, .
Source: https://habr.com/ru/post/461201/
All Articles