Why So Complex to Set Style from Code in Android

Why so complex to set style from code in Android

It has to do with the encouraged patterns within Android around using Views. This isn't the intended approach for what it looks like you're trying to do. First I'll explain what this mechanism is for and then suggest an approach for your app.

The third argument to View constructors that takes an attr resource is generally used when implementing View subclasses and as you've shown, lets you specify a theme attribute to use as a reference to the View's default style. If you had a special kind of button called AwesomeButton you might implement its constructors like this:

public class AwesomeButton extends Button {
public AwesomeButton(Context context) {
this(context, null);
}

public AwesomeButton(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.awesomeButtonStyle);
}

public AwesomeButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr) {
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.AwesomeButton, defStyleAttr, 0);
// Read AwesomeButton-specific style attributes from a
a.recycle();
}

// More code
}

When Android's LayoutInflater inflates views it uses the 2-argument constructor with the arguments (Context, AttributeSet). The R.attr constant is passed through to the 3-argument version and then down to Button's 3-argument constructor in the super call. This means that Button will read default styling info for the things it encapsulates from AwesomeButton's default style as specified in your theme. Some Views within Android differ from their superclass only in the default style they use. (Button is actually one of these.)

You specify android:layout_width and android:layout_height in your style but this can be problematic. LayoutParams (any attribute that starts with layout_) are specific to the parent view, not the view they appear on. This is why you always pass the intended parent view as the second parameter to LayoutInflater#inflate - it tells the inflater which class should be responsible for interpreting the LayoutParams. If you skip this you will often find that your LayoutParams don't behave as you expect and are often ignored outright. By convention we don't put LayoutParams in styles even though in some special cases it sort of works.

It looks like you're trying to use a style as a sort of template. Is there a reason not to use a layout resource for this and specify the styling there?

final LayoutInflater inflater = LayoutInflater.from(mActivity);
Button btn = (Button) inflater.inflate(R.layout.styled_button, parentView, false);

res/layout/styled_button.xml:

<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/my_button_background"
[...] />

What's the downside to styling Android Views by subclassing in Java as opposed to using the XML style system?

The BlueButton class you posted could instead be a style resource:

<style name="BlueButton">
<item name="android:background">@drawable/bg_button_blue_gradient</item>
<item name="android:textSize">20sp</item>
<item name="android:textAllCaps">true</item>
<item name="android:gravity">center</item>
<item name="android:textColor">@android:color/white</item>
</style>

It seems likely from your question that you already knew this, but I say it so that we can compare the style resource to the Java code you posted.

In one case, you change the tag you're using:

<app.ui.widget.BlueButton ... />

In the other case, you add a style attribute:

<Button style="@style/BlueButton" ... />

These don't seem significantly different to me. You can use IDE tools like "find usages" equally easily on a Java class as you can a style resource. So the only question is whether you think style resources are "harder to manage" than a Java class.

Personally, I don't think they are.

Perhaps you don't like having a huge styles.xml file with 500 style definitions in it. If this is the case, then I would say that the right approach is to split your styles.xml file up into smaller files, and not to create Java classes. You could, after all, have a bluebutton.xml file that only had this one style resource in it.


Edit: I'm putting this above the below because I think it's even more important. Too bad I didn't think of it the first time around.

As for downsides, creating View subclasses like your BlueButton will "break" the attributes you set in the constructor. Let's say I want to use BlueButton, but this time I want black text instead of white. Everything else is very "blue-button-ish", but I can't write this:

<app.ui.widget.BlueButton
android:textColor="@android:color/black"
... />

My button will still have white text in this case! That's because the super constructor parses the attributes, but then the BlueButton constructor overrides them. If, on the other hand, I had used a style:

<Button
android:textColor="@android:color/black"
style="@style/BlueButton"
... />

My button will have black text now.


Another argument I have against creating View subclasses as opposed to using styles is that the View subclass has no way to enforce that the instances match the given styling over the lifetime of the app. Nothing is stopping me from adding a BlueButton to my layout but then calling button.setBackgroundResource(R.drawable.foo) later on.

Using styles makes it more explicit that this is just the initial state of the button. Then I'm never tempted to check if my button has a blue background by doing something like if (button instanceof BlueButton), which isn't even guaranteed to work (as described above).

tl;dr: Making View subclasses isn't terrible, but I think it's marginally worse than using style attributes.

Android: set view style programmatically

Technically you can apply styles programmatically, with custom views anyway:

private MyRelativeLayout extends RelativeLayout {
public MyRelativeLayout(Context context) {
super(context, null, R.style.LightStyle);
}
}

The one argument constructor is the one used when you instantiate views programmatically.

So chain this constructor to the super that takes a style parameter.

RelativeLayout someLayout = new MyRelativeLayout(new ContextThemeWrapper(this,R.style.RadioButton));

Or as @Dori pointed out simply:

RelativeLayout someLayout = new RelativeLayout(new ContextThemeWrapper(activity,R.style.LightStyle));

Now in Kotlin:

class MyRelativeLayout @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
defStyleAttr: Int = R.style.LightStyle,
) : RelativeLayout(context, attributeSet, defStyleAttr)

or

 val rl = RelativeLayout(ContextThemeWrapper(activity, R.style.LightStyle))

In the Android full screen activity template, why does the empty style change the look of the button?

Your ButtonBarButton and buttonBarButtonStyle has no relation to each other. You apply buttonBarButtonStyle style which is a theme attribute and defines some style for a bar button. If you can to apply your custom empty style to a button use:

style="@style/ButtonBarButton"

Why do most fields (class members) in Android tutorial start with `m`?

This notation comes from AOSP (Android Open Source Project) Code Style Guidelines for Contributors:

Follow Field Naming Conventions

  • Non-public, non-static field names
    start with m.
  • Static field names start with s.
  • Other fields start with a lower case letter.
  • Public static final fields (constants) are ALL_CAPS_WITH_UNDERSCORES.

Note that the linked style guide is for code to be contributed to the Android Open Source Project.

It is not a style guide for the code of individual Android apps.

How to apply Theme-Specific Styles?

Answering my own question:

found this

so instead of my - working - switchstatement, there's still the need for some code, but it's at least no hardcoding of theme-ids,... -> updated my getThemedStyle with this snippet

public static int getThemedStyle(int normalStyle) {
Resources.Theme theme = appContext.getTheme();
TypedValue styleID = new TypedValue();
if (theme.resolveAttribute(R.attr.TextAppRed, styleID, true)) return styleID.data;
else return normalStyle;
} //getThemedStyle

change programmatically the style of my relativeLayout

I do not believe you can set the style programatically. To get around this you can create a template layout xml file with the style assigned, for example in res/layout create tvtemplate.xml as with the following content:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="This is a template"
style="@style/my_style" />

then inflate this to instantiate your new TextView:

TextView myText = (TextView)getLayoutInflater().inflate(R.layout.tvtemplate, null);

Hope this helps.

Also refer this LINK & set style from code



Related Topics



Leave a reply



Submit