Assign multiple styles to a single View in Android just like in CSS
source link: https://proandroiddev.com/assign-multiple-styles-to-a-single-view-in-android-just-like-in-css-815040be4c2b
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
If your application has a very complex style hierarchy where inheriting a style from another is not trivial anymore you might have wished Android had a style system similar to CSS where you can assign multiple styles to a single View just by adding more styles in the layout xml.
Find the sample project on github.
The goal
We aim for something like the following:
Using the feature is straightforward: add the styles one after the other and the latest (determined by the style index) will overwrite all already defined properties in the styles that came before. Note that you cannot use android:style
together with this solution as it overwrites everything.
To have our own style tags we need to declare them first so let’s see how to create those custom attribute tags.
Creating custom attribute tags
In a value resource file just create a declare-styleable
that contains as many styles as you need.
These are the tags you see in the example above. It is also worth mentioning that using these tags our view will show up in Android Studio as well!
Tags alone don’t do us much good, unfortunately. To style a view outside of its xml layout we have to apply that style using a ContextThemeWrapper
.
Styling a view programmatically
To instantiate a View with a custom style the following will do:
val someTextView = TextView(ContextThemeWrapper(context, R.style.Style_I_Wish_I_Had))
But we see a problem here: the style we want to apply has to be predefined in xml which is exactly what we want to avoid.
To get around this we will create the ContextThemeWrapper
from code as well. It is necessary to get the assigned style attributes from the View, merge them and apply them into a theme that can be used to create our ContextThemeWrapper
.
Merging the styles into a theme
Android’s Context provides a method for us to get our handystyleable
attributes from the View we created:
val styleAttributes = context.obtainStyledAttributes(attrs, R.styleable.TextView)
This will return a TypedArray
that we can query for the specific attributes. To denote a missing attribute we add a default value that will be zero in this case:
val attribute = styleAttributes.getResourceId(
R.styleable.TextView_style1, 0)
Once we have the attributes we can create a Resources.Theme
and apply the styles to it:
val theme = context.resources.newTheme()
// be mindful that the second parameter should be true!
theme.applyStyle(attribute, true)
When applying the styles, setting the second parameter to true
will overwrite already defined values in the theme. ContextThemeWrapper
can be created using a Resources.Theme
as well:
ContextThemeWrapper(context, theme)
Putting it together
To make use of our code so far we know we have to call it in the constructor of the View we intend to instantiate. What’s a better place to call those constructors than the activity style’s ViewInflater
.
Fun fact: to implement the AppCompat
library Google uses a ViewInflater
as well to replace Views declared in layouts. For more info, check out AppCompatViewInflater
.
We can organize our code we have written so far in a custom ViewInflater
like so:
And to make use of it, set the viewInflaterClass
attribute of our style as follows:
After all these we need to build the project so that multiple styles can take effect.
Testing it out
Let’s create some styles that make a TextView
look like a button. One style will apply a background, another will be responsible for the font and yet another will set the paddings and the alignment of the text.
Note the parent of the style. During implementation on certain devices the build threw an UnsupportedOperationException
. The reason was that when setting styles from code you cannot leave a property undefined and depending on the complexity of your styles you might not set all of them. Adding a parent that sets the problematic properties resolves the issue.
Using these styles in the layout xml file as follows:
Which gives the following result in the editor:
And the built sample application:
Note that the ugly button has the secondary
font style applied first, then the ugly
one later so it unfortunately looks ugly.
Final thoughts
- This solution works with databinding as at its core it does nothing besides creating a
Context
- There is no need to create custom views.
- Adding styles via code relies on View creation. As a result binding adapters would not work to get the same effect.
- If there are no
styleX
attributes added to a View the whole view creation process will go as if we didn’t implement these changes - Android created its style hierarchy for a reason. Structure your styles well and use this method sparingly!
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK