How to extend API dependend theme entries in an existing theme? - android

In Android development I'm allowed to provide API depending resources. I've a custom theme which I would like to extend with two new entries for API 29 and higher as described here.
So the file/folder structure looks like that:
res
- values
- dark_theme.xml
- light_theme.xml
- values-v29
- dark_theme.xml
- light_theme.xml
How can I just extend (add items to the existing theme) the theme defined in values\dark_theme.xml and values\light_theme.xml in values-v29\dark_theme.xml and values\light_theme.xml?
Let's say I want to extend the light theme with the new options for "edge-to-edge" feature. How do I need to set up the theme so I don't need to copy all the values from the original theme to the theme in -v29 folder?
Content of values\light_theme.xml:
<resources>
<style name="AppTheme_Light" parent="BaseAppTheme">
<item name="colorPrimary">#color/primary</item>
<item name="colorPrimaryDark">#color/primary_light</item>
...
</style>
</resources>
How does the file need to look like to use the items already defined in the theme in values folder without coping the whole theme?
Content of values-29\light_theme.xml:
<style name="AppTheme_Light" parent="BaseAppTheme">
...How to ensure all the previous items defined in values\light_theme.xml are used here?
<item name="android:navigationBarColor">#android:color/transparent</item>
<item name="android:statusBarColor">#android:color/transparent</item>
</style>

Apparently it seems the way to go is to use the XML attribute
tools:ignore="NewApi"
from the tools namespace.
So I used those lines in my case instead of copying my styles files:
<style name="AppTheme_Light" parent="BaseAppTheme">
...
<item name="android:navigationBarColor" tools:ignore="NewApi">#android:color/transparent</item>
<item name="android:statusBarColor" tools:ignore="NewApi">#android:color/transparent</item>
</style>
If this is good practice seems "to depend" because I hide possible incompatible widget attributes.

Related

Is this the correct way to have different values/styles for different versions

Android Studio 2.1 preview 3
This is just a question, as I am confused as I have seen many alternatives in doing this.
I have created a new android project and my Activity extends AppCompatActivity
public class MainActivity extends AppCompatActivity {
I want to have the transparent statusbar on devices running 21 and over.
So in my values/styles I have the following
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorAccent</item>
</style>
</resources>
And in my values-21/styles I have the following
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorAccent</item>
<!-- Make the statusbar transparent -->
<item name="android:windowTranslucentStatus">true</item>
</style>
</resources>
My Manifest I select the theme
android:theme="#style/AppTheme"
Just some questions
Is this the correct way, or is there any better way to do this?
Would values-21/styles inherit all the colors in values/styles so I would have to repeat this?
It's the right way. May I suggest you to organize your style better?
values/styles.xml
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="CommonTheme">
</style>
<style name="CommonTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorAccent</item>
</style>
</resources>
values-v21/styles.xml
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="CommonTheme">
<!-- All customization of the theme for this version -->
<item name="android:windowTranslucentStatus">true</item>
</style>
</resources>
So you don't need to repeat the common values of the style for every api level.
I will try to answer it giving some references
Maintaining Compatibility
To avoid duplication of code, define your styles inside res/values/,
modify the styles in res/values-v21/ for the new APIs, and use style
inheritance, defining base styles in res/values/ and inheriting from
those in res/values-v21/
So you should try to avoid code duplication in your style.xml at different folders res/values/ and res/values-v21/ by using style inheritance.
Style Inheritence
If you want to inherit from styles that you've defined yourself, you
do not have to use the parent attribute. Instead, just prefix the name
of the style you want to inherit to the name of your new style,
separated by a period.
If you want to inherit a style that you've defined yourself you can skip adding a parent attribute and inherit from it using a dot or period notation.
With this, you can define a base theme BaseTheme in res/values/ with different colors and inherit from it as BaseTheme.StyledStatusBar without specifying a parent attribute.
<resources>
<style name="BaseTheme.StyledStatusBar"></style>
<!-- Base application theme. -->
<style name="BaseTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorAccent</item>
</style>
</resources>
In values-21/, add item android:windowTranslucentStatus to BaseTheme.StyledStatusBar
<resources>
<style name="BaseTheme.StyledStatusBar">
<item name="android:windowTranslucentStatus">true</item>
</style>
</resources>
In manifest, select the theme
android:theme="#style/BaseTheme.StyledStatusBar"
1) Is this the correct way, or is there any better way to do this?
Yes. That's the correct/recommended way of having different values for different API versions.
2) Would values21/styles inherit all the colors in values/styles so I would have to repeat this?
I'm not sure I fully follow this question. Both styles you showed will inherit from Theme.AppCompat.Light.NoActionBar so your colors should be declared again, but I'll present two better alternatives:
Alternative 1, a little bit better:
Use a BaseTheme that is common for both. To view the code for it, please check #mimmo-grottoli answer.
Alternative 2, much better:
If the only different on the two themes is the android:windowTranslucentStatus that was introduced in KitKat (API level 19), you can put it all in the same theme in values/styles, like the following:
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorAccent</item>
<!-- Make the statusbar transparent -->
<item name="android:windowTranslucentStatus">true</item>
</style>
</resources>
The Android Framework ignores XML parameters that it does not recognize. That means that on JellyBean or ICS, the device will ignore windowTranslucentStatus while correctly applying the colors and in KitKat and up, it will correctly apply windowTranslucentStatus.
That tricky is valid for all XML in Android (even layouts), the IDE might give you warnings about API level, but in XML they're always safe to use.
Different folders of values/styles are made to give a unique style when your app is running on a specific version of Android.
So yes you are right when saying that the newer version inherits from the older one. When adding items in your latest version of styles you keep the latest version up to date to the latest APIs.
To conclude, your way is the very commun way, it's an organized and a clean way to keep your app updated.
Is this the correct way, or is there any better way to do this?
Yes. That's the right way of having different values for different API versions.
Would values-21/styles inherit all the colors in values/styles so I would have to repeat this?
Yes
technically <item name="android:windowTranslucentStatus">true</item> won't give you fully transparent statusbar.
If you want it to by fully transparent, you can use <item name="android:statusBarColor">#android:color/transparent</item>

Repeating style in v19/v21

I have this in my styles.xml:
<style name="UserTheme" parent="ThemeBase">
<item name="android:editTextStyle">#style/EditTextTheme</item>
</style>
Why do I have to repeat the editTextStyle line in v19/styles.xml and v21/styles.xml.
v21/styles.xml:
<style name="UserTheme" parent="ThemeBase">
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
<item name="android:editTextStyle">#style/EditTextTheme</item>
</style>
Is there a way to just call it in the main styles.xml and have it apply everywhere so I don't have to write it multiple times?
I couldn't find any recommended solution so I i digged into AppCompat source. The way they do it is like this.
In your styles.xml
<style name="Base.V7.Theme.YourThemeName" parent="Theme.AppCompat.Light.NoActionBar">
</style>
<style name="Base.Theme.YourThemeName" parent="Base.V7.Theme.YourThemeName" />
<style name="Theme.YourThemeName" parent="Base.Theme.YourThemeName" >
<item name="colorPrimary">#color/primary</item>
<item name="colorPrimaryDark">#color/primary_dark</item>
<item name="colorAccent">#color/accent</item>
</style>
In your styles-v21.xml
<style name="Base.V21.Theme.YourThemeName" parent="Base.V7.Theme.YourThemeName">
<item name="android:navigationBarColor">#color/white</item>
<item name="android:windowTranslucentStatus">true</item>
</style>
<style name="Base.Theme.YourThemeName" parent="Base.V21.Theme.YourThemeName" />
In your styles-v22.xml
<style name="Base.V22.Theme.YourThemeName" parent="Base.V21.Theme.YourThemeName">
<item name="android:navigationBarColor">#color/black</item>
<item name="android:windowTranslucentStatus">false</item>
</style>
<style name="Base.Theme.YourThemeName" parent="Base.V22.Theme.YourThemeName" />
For every new version you extend the previous base version. If you want to override any attribute for different version just put it inside Base.VXX.Theme.YourThemeName block on your new styles-vXX.xml file.
Why do I have to repeat the editTextStyle line in v19/styles.xml and
v21/styles.xml?
If you've applied some STYLE to some attribute, Android will search styles.xml file for highest api level for which file_api_level<=Android_device_api_level and searches for STYLE in it. If it finds it would apply that STYLE to view otherwise will continue searching for the STYLE in lower api level files.
e.g. - If you have four files styles.xml(default), v19/styles.xml, v21/styles.xml, v25/styles.xml and your devices is running on api level 24. Then it'll search for STYLE in v21/styles.xml first, then v19/styles.xml and finally in styles.xml(default). Only first occurrence of the STYLE will get applied. So you can't just define only extra attributes in version-specific styles.xml file.
If you don't want to repeat common attributes here is an alternate. To declare window transitions for Android 5.0 (API level 21) and higher, you need to use some new attributes. So your base theme in res/values/styles.xml could look like this:
<resources>
<!-- base set of styles that apply to all versions -->
<style name="BaseAppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">#color/primaryColor</item>
<item name="colorPrimaryDark">#color/primaryTextColor</item>
<item name="colorAccent">#color/secondaryColor</item>
</style>
<!-- declare the theme name that's actually applied in the manifest file -->
<style name="AppTheme" parent="BaseAppTheme" />
</resources>
Then add the version-specific styles in res/values-v21/styles.xml as follows:
<resources>
<!-- extend the base theme to add styles available only with API level 21+ -->
<style name="AppTheme" parent="BaseAppTheme">
<item name="android:windowActivityTransitions">true</item>
<item name="android:windowEnterTransition">#android:transition/slide_right</item>
<item name="android:windowExitTransition">#android:transition/slide_left</item>
</style>
Now you can apply AppTheme in your manifest file and the system selects the styles available for each system version.
Is there a way to just call it in the main styles.xml and have it
apply everywhere so I don't have to write it multiple times?
Yes, there is a way in which you can maintain only one styles.xml file.
First of all, start using AppCompat themes. They provide backward compatibility and will work for older android versions as well.
Now define all of your styles in styles.xml(default) file and if your Android Studio is showing you some warning/error for some attribute which is supported in higher level apis:
You can suppress that warning using: tools:targetApi="SupportedAndroidVersionName"
Now Android will ignore that particular attribute if it's not supported and your whole style will work perfectly for both lower and higher api levels.
Read more about Styles and Themes here.
Hope it helps :)
Newer versions of Android have additional themes available to applications, and you might want to use these while running on those platforms while still being compatible with older versions. You can accomplish this through a custom theme that uses resource selection to switch between different parent themes, based on the platform version.
Why do I have to repeat the editTextStyle line in v19/styles.xml and
v21/styles.xml?
Because if your app is running on v21, v21/styles.xml will be loaded and if running on v19, v19/styles.xml will be loaded. In case you don't have v21/styles.xml or v19/styles.xml the app will automatically use your default values/styles.xml but you wont be able to take advantage of new features provide only for v21 or v19.
For more reference you can read Supporting Different Devices and Select a theme based on platform version.

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.
Rules are quite clear:
While running Android selects the best-matching style
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.
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.
either myMaterialButtonAPI_All or myMaterialButtonAPI_2
AppBaseChanges (only one is chosen)
myMaterialButton
Widget.MaterialComponents.Button
AppBase
Theme.MaterialComponents.NoActionBar
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://stackoverflow.com/a/53445541/5745574

Material design: Where to place elevation in android styles?

I want to set item elevation in some of my app's styles. Now elevation is only 21 and higher with no support library, so my natural inclination was to just create a styles-v21 xml and place it in there:
<style name="Widget.MyApp.Drawer" parent="ThemeOverlay.AppCompat.Dark.ActionBar">
<item name="android:elevation">4dp</item>
</style>
The problem with this is any changes I make to Widget.MyApp.Drawer in the regular styles.xml file will be overwritten by this completely. What I'd want is for elevation to just be tacked on to the bottom of the list of style changes I made for the v21 version of this style listed in styles.xml.
So I took to creating base styles which the style I use in the views inherits from:
<style name="BaseListElement">
<item name="android:background">#drawable/listitem_background</item>
<item name="android:layout_height">#dimen/list_item_height</item>
</style>
<style name="BaseListElement.ListItem">
</style>
I leave the style blank in styles.xml, and in styles-v21, I add elevation and it works.
However this get's kind of tricky when I want to use some advanced styles:
<style name="BaseListElement">
<item name="android:background">#drawable/listitem_background</item>
<item name="android:layout_height">#dimen/list_item_height</item>
</style>
<style name="BaseListElement.BaseItem">
<item name="android:padding">#dimen/list_item_padding</item>
</style>
<style name="Widget.MyApp.ListItem" parent="#style/BaseListElement.BaseItem">
</style>
<style name="BaseListElement.BaseHeader">
</style>
In this case, BaseItem is just one style that inherits from BaseListElement, styles such as BaseHeader inherit from it as well. This is getting kind of ridiculous as you can see.
Am I overthinking this? The way I see it I have 3 choices here:
1) Continue as is and feel like an idiot
2) On the BaseListElement level, create a child style with some goofy name which is the point at which I apply the elevation, which would then (hopefully) trickle down to all the children. As soon as I have a difference between v21 children of the base however, this wouldn't work.
3) Just throw android:elevation into the styles.xml file (don't use a v21 file) and place an ignore flag on the element. I only have 5.0 devices here, so I can't easily test at the moment if this will cause a crash on older versions.
Any thoughts?
To accomplish something like this you could just create a BaseListElement.BaseItem in both styles.xml and syles-v21.xml the first one without the elevation and the second one with it. Then just extend Widget.MyApp.ListItem from BaseListElement.BaseItem which should get updated in v21 to use the elevation.
styles.xml
<style name="BaseListElement.BaseItem">
</style>
<style name="Widget.MyApp.ListItem" parent="#style/BaseListElement.BaseItem">
</style>
styles-v21.xml
<style name="BaseListElement.BaseItem">
<item name="android:padding">#dimen/list_item_padding</item>
</style>
Method 3 you can safely implement as follows:
<item name="android:elevation" tools:ignore="NewApi">4dp</item>

Setting Android Theme background color

I'm trying to modify the default background theme color, which should be easy but surprisingly I can't get it working. Please note that I want the change to be across the entire app, not just for a single activity. Here is my code:
styles.xml
<resources>
<color name="white_opaque">#FFFFFFFF</color>
<color name="pitch_black">#FF000000</color>
<style name="AppTheme" parent="android:Theme.Light">
<item name="android:background">#color/white_opaque</item>
<item name="android:windowBackground">#color/white_opaque</item>
<item name="android:colorBackground">#color/white_opaque</item>
</style>
</resources>
and of course in the manifest
<application
.
.
.
android:theme="#style/AppTheme" >
</application>
Android doc which I consulted on modifying themes:
http://developer.android.com/guide/topics/ui/themes.html
I've tried switching between white_opaque and pitch_black for all the xml attributes but it doesn't change a thing. Any suggestions?
Okay turned out that I made a really silly mistake. The device I am using for testing is running Android 4.0.4, API level 15.
The styles.xml file that I was editing is in the default values folder. I edited the styles.xml in values-v14 folder and it works all fine now.
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="android:Theme.Holo.NoActionBar">
<item name="android:windowBackground">#android:color/black</item>
</style>
</resources>
Open res -> values -> styles.xml and to your <style> add this line replacing with your image path <item name="android:windowBackground">#drawable/background</item>. Example:
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorAccent</item>
<item name="android:windowBackground">#drawable/background</item>
</style>
</resources>
There is a <item name ="android:colorBackground">#color/black</item> also, that will affect not only your main window background but all the component in your app. Read about customize theme here.
If you want version specific styles:
If a new version of Android adds theme attributes that you want to
use, you can add them to your theme while still being compatible with
old versions. All you need is another styles.xml file saved in a
values directory that includes the resource version qualifier. For
example:
res/values/styles.xml # themes for all versions
res/values-v21/styles.xml # themes for API level 21+ only
Because the styles in the values/styles.xml file are available for all
versions, your themes in values-v21/styles.xml can inherit them. As
such, you can avoid duplicating styles by beginning with a "base"
theme and then extending it in your version-specific styles.
Read more here(doc in theme).

Categories

Resources