0

I am looking to create a custom ViewGroup to be used in a library; which contains a few ImageButton objects. I would like to be able to apply a style each ImageButton; but I cannot figure out how to apply a style programmatically other than by applying a attribute resource to the defStyleAttr parameter; like so:

mImageButton = new ImageButton(
        getContext(),                    // context
        null,                            // attrs
        R.attr.customImageButtonStyle);  // defStyleAttr

The issue with this is that the only way to change the style of each ImageButton would be by applying a style to this attribute in a parent theme. But I would like to be able to set a default style, without having to manually set this attribute for each project that uses this library.

There is a parameter that does exactly what I am looking for; defStyleRes, which can be used like so:

mImageButton = new ImageButton(
        getContext(),                    // context
        null,                            // attrs
        R.attr.customImageButtonStyle,   // defStyleAttr
        R.style.customImageButtonStyle); // defStyleRes

This parameter is only available at API Level 21 and above, but my projects target API Level 16 and above. So how can I set the defStyleRes, or apply a default style, without access to this parameter?


I applied my style using a ContextThemeWrapper, as suggested by @EugenPechanec, which seems to work well, but each ImageButton now has the default ImageButton background, even though my style applies <item name="android:background">@null</item>.

Here is the style I am using:

<style name="Widget.Custom.Icon" parent="android:Widget">
    <item name="android:background">@null</item>
    <item name="android:minWidth">56dp</item>
    <item name="android:minHeight">48dp</item>
    <item name="android:tint">@color/selector_light</item>
</style>

And this is how I am applying it:

ContextThemeWrapper wrapper = new ContextThemeWrapper(getContext(), R.style.Widget_Custom_Icon);
mImageButton = new AppCompatImageButton(wrapper);

On the left is what I am getting, and on the right is what I would like it to look like:

enter image description hereenter image description here

Bryan
  • 14,756
  • 10
  • 70
  • 125

1 Answers1

1

defStyleAttr is for resolving default widget style from theme attribute.

Example: AppCompatCheckBox asks for R.attr.checkBoxStyle. Your theme defines <item name="checkBoxStyle">@style/Widget.AppCompat.CheckBox</item>.

If that attribute is not defined in your theme the widget would pickup its defStyleRes e.g. R.style.Widget_AppCompat_CheckBox.

Note that these are not actual values used by the widget.

I have not seen defStyleRes constructor parameter used outside of the framework. All of these parameters (plus defaults) are however used when asking TypedArray for resources.

How to actually solve your problem

So the four parameter constructor is not available on all platforms. You need to find a way to feed in your default style. Consider a style you'd like to apply:

<style name="MyImageButtonStyle" parent=""> ... </style>

You need a way to convert it to a defStyleAttr parameter. Define the default style on a theme overlay:

<style name="MyImageButtonThemeOverlay" parent="">
    <!-- AppCompat widgets don't use the android: prefix. -->
    <item name="imageButtonStyle">@style/MyImageButtonStyle</item>
</style>

Now you can create your ImageButton using this theme overlay:

// When creating manually you have to include the AppCompat prefix.
mImageButton = new AppCompatImageButton(
    new ContextThemeWrapper(getContext(), R.style.MyImageButtonThemeOverlay)
);

You don't need to specify any other parameters as AppCompatImageButton will pickup R.attr.imageButtonStyle by default.


If that looks hacky you can always inflate your custom view hierarchy or individual widgets from XML where you specified the style="@style/MyImageButtonStyle" attribute.

Eugen Pechanec
  • 37,669
  • 7
  • 103
  • 124
  • `ContextThemeWrapper` seems like the correct way to go. I have come across it before, but it completely escaped my mind. Unfortunately it is causing another issue; for some reason it is adding a background resource on each `ImageButton`. I can remove the background after the fact by setting `mImageButton.setBackgroundResource(0)` but I cannot do this in my style resource with `@null`, even though I can change other attributes. Any idea of what could be causing this? – Bryan Mar 10 '17 at 14:23
  • @Bryan ok, post what changed at the end of your question, I'll look into it. – Eugen Pechanec Mar 10 '17 at 14:32
  • @Bryan You're mixing up styles and themes (theme overlays). **Don't skip the theme overlay.** Don't use style resource IDs with ContextThemeWrapper. Don't define style attributes in themes and vice versa. This may help you https://chris.banes.me/2014/11/12/theme-vs-style/ – Eugen Pechanec Mar 10 '17 at 16:43
  • Ah, I see the issue, thank you. Though, I wan't to be able to apply styles dynamically, so if the `ContextThemeWrapper` applies a *theme* to the `ImageButton`; is there also a way to apply a *style* to an `ImageButton` upon creation, other than doing so in a theme overlay? – Bryan Mar 10 '17 at 16:50
  • @Bryan in that case this approach (a set of corresponding theme overlays and styles) may not be the best option. You may want to style the widget after constructor in Java. – Eugen Pechanec Mar 10 '17 at 16:55
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/137776/discussion-between-bryan-and-eugen-pechanec). – Bryan Mar 10 '17 at 16:56
  • It seemed a little strange that I couldn't apply the style through a `ContextThemeWrapper`, so I did a little more searching. I found (based on [this comment](http://stackoverflow.com/questions/3142067/android-set-style-in-code#comment45167295_17442610)) that you have to use the three-argument constructor, or the `defAttrAttr` will be applied *after* the style defined in the `ContextThemeWrapper`. Now it works as expected, without the default `ImageButton` background. – Bryan Mar 15 '17 at 13:50
  • The one argument constructor invokes the two arg ctor, the two arg ctor invokes the three arg ctor which reads (in case of AppCompatImageButton) `R.attr.imageButtonStyle` from current theme. Which in the theme overlay points to the style you wanted to use. Apparently you managed to get it working using a different setup. Maybe you'd like to post an answer yourself. – Eugen Pechanec Mar 15 '17 at 17:03