Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing themes and styles based on the Android version on build

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.

like image 539
vkislicins Avatar asked Jun 25 '15 12:06

vkislicins


People also ask

What is the difference between style and theme in Android?

A style is a collection of attributes that specify the appearance for a single View . A style can specify attributes such as font color, font size, background color, and much more. A theme is a collection of attributes that's applied to an entire app, activity, or view hierarchy—not just an individual view.

How do I use themes on Android?

To change default themes go to File and click on Settings. A new Settings dialog will appear, like this. Under the Appearance & Behaviour -> Appearance, you will find Theme. Choose the right theme from the drop-down and click on Apply and then Ok.

What is an Android theme and why are themes used?

Android Themes A theme is nothing but an Android style applied to an entire Activity or application, rather than an individual View. Thus, when a style is applied as a theme, every View in the Activity or application will apply each style property that it supports.


2 Answers

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.

like image 72
Dmitry Avatar answered Oct 19 '22 00:10

Dmitry


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
like image 1
AaronC Avatar answered Oct 19 '22 00:10

AaronC