6

Assign multiple styles to a single View in Android just like in CSS

 3 years ago
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.
1*QGh6MHGwpY2YH3IN6f514Q.png?q=20
assign-multiple-styles-to-a-single-view-in-android-just-like-in-css-815040be4c2b

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:

1*A3edU-MlrWvyioAaptFmHA.png?q=20
assign-multiple-styles-to-a-single-view-in-android-just-like-in-css-815040be4c2b

And the built sample application:

1*Bc5131UxjTRh4PkHXodixA.png?q=20
assign-multiple-styles-to-a-single-view-in-android-just-like-in-css-815040be4c2b

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!

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK