Changing themes and styles based on the Android version on build
Asked Answered
S

3

12

I know I can add different XMLs for different API levels, for example having different styles for values-v21 and values-v19. What I'm trying to understand is how the build system actually works with those different values? So for example if I have the bulk of my styles common across all APIs and one item of one style changes between 21 and the rest, do I:

1) Copy the whole styles.xml into v21 and change the one value I need to change

2) Only add that one style that changed to styles.xml under v21

3) Only add that one item of that one style that changed under 21

It's confusing and I couldn't find any documentation how the built process handles merging styles.

Sitter answered 25/6, 2015 at 12:23 Comment(0)
G
18

Rules are quite clear:

  1. While running Android selects the best-matching style
  2. If selected style is a child style, Android merges its items with parent best-matching style

    If you provide your mutable item via a reference, just define its value to match selected api version.

    <style name="SomeStyle">
        <item name="someColor">@color/some_color</item>
    </style>
    

You can have some_color.xml in color-v21 folder for API 21 and a common version of this file in a color folder for all other api levels.

Example:

You want to have the following style for non-v21 API

    <style name="FinalStyle">
        <item name="commonText">It\'s a common text</item>
        <item name="specificDrawable">@drawable/icon</item>
        <item name="specificColor">@color/primary_color</item>
        <item name="specificText">non-v21</item>
    </style>

And the following style for v21 API

    <style name="FinalStyle">
        <item name="commonText">It\'s a common text</item>
        <item name="specificDrawable">@drawable/icon</item>
        <item name="specificColor">@color/secondary_color</item>
        <item name="specificText">v21</item>
    </style>

Specific-parameters differ between v21/non-v21 API, common parameters are common.

How to do it?

  • res/values/styles.xml

    <style name="BaseStyle">
        <item name="commonText">It\'s a common text</item>
        <item name="specificDrawable">@drawable/icon</item>
    </style>
    
    <style name="FinalStyle" parent="BaseStyle">
        <item name="specificColor">@color/primary_color</item>
        <item name="specificText">non-v21</item>
    </style>
    
  • res/values-v21/styles.xml

    <style name="FinalStyle" parent="BaseStyle">
        <item name="specificColor">@color/secondary_color</item>
        <item name="specificText">v21</item>
    </style>
    
  • res/drawable/icon.png Common icon

  • res/drawable-v21/icon.png v21 icon

When Android searches FinalStyle for v21, it selects FinalStyle definition from res/values-v21 as best-matching style, and merges with BaseStyle. In this example there is also another best-matching resource search, when Android searches @drawable/icon.

Globate answered 25/6, 2015 at 13:33 Comment(4)
so essentially if I have two styles with 10 items each in v21 and common version and 1 item should differ, I need to duplicate the whole style under v21?Sitter
and also if my first style has a parent, I need to copy both that style and it's parent, i.e. it is not possible to inherit between the versions of the style?Sitter
you can't inherit between versions of style, but you can extract common base style and after that create children styles for common and v21 versions.Globate
an example would be much appreciated :) I'm dealing with a lot of app flavors so copy-pasting all styles for all versions of all flavors is just hellSitter
D
2

This is for anyone who comes across this and is still just as confused as I was, even after a lot of reading and trial & error. Hopefully this helps.

The folder structure is like @Dmitry stated.

res/values/styles.xml

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">

    <style name="AppBase" parent="Theme.MaterialComponents.NoActionBar">
        <!-- simple: overrides colorPrimary in parent theme -->
        <item name="colorPrimary">@color/brand_blue</item>
        <item name="colorSecondary">@color/brand_grey</item>
        <!-- sets the attributes in materialButtonStyle with style: myMaterialButton -->
        <!-- the materialButtonStyle attribute is what actually changes the button settings -->
        <item name="materialButtonStyle">@style/myMaterialButton</item>
    </style>

    <!-- this style consists of common 'attributes' among all API versions -->
    <!-- you can choose to add a parent to inherit an additional style -->
    <!-- unlike the materialButtonStyle attribute, this parent is not necessary to change the button settings -->
    <style name="myMaterialButton" parent="Widget.MaterialComponents.Button">
        <item name="cornerRadius">60dp</item>
        <item name="android:paddingVertical" tools:targetApi="26">20dp</item>
    </style>

    <!-- this will add on and override AppBase and should include any changes that differ from other API versions -->
    <style name="AppBaseChanges" parent="AppBase">
        <!-- to inherit myMaterialButton, you don't have to include it in here, since it's in AppBase -->
        <!-- however, if you want to extend myMaterialButton, create a new style as its child -->
        <item name="materialButtonStyle">@style/myMaterialButtonAPI_All</item>
    </style>

    <!-- make sure the parent is myMaterialButton to inherit/override its settings -->
    <!-- this will be picked for all APIs lower than other styles like this -->
    <style name="myMaterialButtonAPI_All" parent="myMaterialButton">
        <item name="backgroundTint">?attr/colorPrimary</item>
    </style>
</resources>

res/values-v2/styles.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!-- restate the same declaration as the other xml file-->
    <style name="AppBaseChanges" parent="AppBase">
        <!-- use a different name (...API_2) for the overriding style -->
        <item name="materialButtonStyle">@style/myMaterialButtonAPI_2</item>
    </style>

    <style name="myMaterialButtonAPI_2" parent="myMaterialButton">
        <item name="backgroundTint">?attr/colorSecondary</item>
    </style>
</resources>

Set the manifest theme to AppBaseChanges. The app will pick only one AppBaseChanges style to apply changes, so be sure to carefully override the right styles to ensure you are inheriting from lower level versions.

For some reason, AndroidStudio doesn't do a good job at all previewing themes, so before you think it's not working, relaunch the app to see the changes. There are also situations where I have no idea why it wasn't updating the setting and couldn't find where it was overriding the theme. In those cases you can dig further, or avoid the hassle and just apply the relevant style directly to the view.

Here's the order of precedence for the sample themes described above. The higher the style, the higher precedence it has and will override the lower style.

  1. either myMaterialButtonAPI_All or myMaterialButtonAPI_2
  2. AppBaseChanges (only one is chosen)
  3. myMaterialButton
  4. Widget.MaterialComponents.Button
  5. AppBase
  6. Theme.MaterialComponents.NoActionBar
Danille answered 13/10, 2021 at 9:18 Comment(0)
S
0

You may maintain only one styles.xml(default) file for all the device version.

Checkout my answer to How to remove repeating of similar styles in v19/styles.xml and v21/styles.xml files https://mcmap.net/q/76738/-repeating-style-in-v19-v21

Stralka answered 23/11, 2018 at 11:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.