I've written a custom widget for a control that we use widely throughout our application. The widget class derives from ImageButton
and extends it in a couple of simple ways. I've defined a style which I can apply to the widget as it's used, but I'd prefer to set this up through a theme. In R.styleable
I see widget style attributes like imageButtonStyle
and textViewStyle
. Is there any way to create something like that for the custom widget I wrote?
2 Answers
Yes, there's one way:
Suppose you have a declaration of attributes for your widget (in attrs.xml
):
<declare-styleable name="CustomImageButton">
<attr name="customAttr" format="string"/>
</declare-styleable>
Declare an attribute you will use for a style reference (in attrs.xml
):
<declare-styleable name="CustomTheme">
<attr name="customImageButtonStyle" format="reference"/>
</declare-styleable>
Declare a set of default attribute values for the widget (in styles.xml
):
<style name="Widget.ImageButton.Custom" parent="android:style/Widget.ImageButton">
<item name="customAttr">some value</item>
</style>
Declare a custom theme (in themes.xml
):
<style name="Theme.Custom" parent="@android:style/Theme">
<item name="customImageButtonStyle">@style/Widget.ImageButton.Custom</item>
</style>
Use this attribute as the third argument in your widget's constructor (in CustomImageButton.java
):
public class CustomImageButton extends ImageButton {
private String customAttr;
public CustomImageButton( Context context ) {
this( context, null );
}
public CustomImageButton( Context context, AttributeSet attrs ) {
this( context, attrs, R.attr.customImageButtonStyle );
}
public CustomImageButton( Context context, AttributeSet attrs,
int defStyle ) {
super( context, attrs, defStyle );
final TypedArray array = context.obtainStyledAttributes( attrs,
R.styleable.CustomImageButton, defStyle,
R.style.Widget_ImageButton_Custom ); // see below
this.customAttr =
array.getString( R.styleable.CustomImageButton_customAttr, "" );
array.recycle();
}
}
Now you have to apply Theme.Custom
to all activities that use CustomImageButton
(in AndroidManifest.xml):
<activity android:name=".MyActivity" android:theme="@style/Theme.Custom"/>
That's all. Now CustomImageButton
tries to load default attribute values from customImageButtonStyle
attribute of current theme. If no such attribute is found in the theme or attribute's value is @null
then the final argument to obtainStyledAttributes
will be used: Widget.ImageButton.Custom
in this case.
You can change names of all instances and all files (except AndroidManifest.xml
) but it would be better to use Android naming convention.
-
4Is there a way to do the exact same thing without using a theme? I have some custom widgets, but I want users to be able to use these widgets with any theme they want. Doing it the way you posted would require users to use my theme. – theDazzler Dec 08 '13 at 22:41
-
3@theDazzler: If you mean a library that developers can use, then they will be able to create their own themes and specify the style for the widget using a style attribute in the theme (`customImageButtonStyle` in this answer). That's how the action bar is customized on Android. And if you mean changing a theme at runtime, then it's not possible with this approach. – Michael Dec 09 '13 at 07:02
-
@Michael, Yes I meant a library that developers can use. Your comment solved my issue. Thanks! – theDazzler Dec 09 '13 at 10:09
-
I don't understand the third block. Specifically, I'm not understanding how you tie the custom widget/class and it's package in here, other than just using it's custom attribute names in the first and third code blocks shown. Is the parent attribute in the third block meant to be the parent class you over rode when you created your custom widget? In this case, I'm assuming CustomImageButton extends ImageButton? – wkhatch Nov 20 '14 at 20:48
-
The main purpose of the third and the fourth blocks is to provide default values for custom and predefined attributes for a custom widget. The style and the theme are applied when you pass them to `Context.obtainStyledAttributes()` for retrieving attribute values. But in order for this to work you need to have your custom theme set as the activity's default theme (last block). In this case `defStyle` in the third constructor will be resolved to `@style/Widget.ImageButton.Custom`. – Michael Nov 21 '14 at 08:54
-
36The most amazing thing about all this is that somebody at Google thinks this is okay. – Glenn Maynard Jul 13 '16 at 22:41
-
1Does `name="CustomTheme"` in the `declare-styleable` attribute wrapper serve any purpose? Is that just for organization, because I don't see it used anywhere. Can I just put all my style attributes for various widgets in one wrapper? – Tenfour04 Jun 14 '17 at 03:27
-
I would say it's a some kind of a convention so you can call it whatever you want. – Michael Jun 14 '17 at 08:02
-
Why are there two `declare-stylable` blocks? would it not work to put the style attribute in the first block? – clarkep Jul 16 '17 at 23:22
-
@pjc The first block is for the widget. This block is used when reading attributes of the widget so it's better not to put unrelated stuff into it. The second one is for themes, and you can combine it with other theme blocks. – Michael Jul 17 '17 at 06:24
-
@GlennMaynard that is true in all levels! they made it twisted and desperate. setting styles in android is PITA – M.kazem Akhgary Nov 02 '18 at 15:12
Another aspect in addition to michael's excellent answer is overriding custom attributes in themes. Suppose you have a number of custom views that all refer to the custom attribute "custom_background".
<declare-styleable name="MyCustomStylables">
<attr name="custom_background" format="color"/>
</declare-styleable>
In a theme you define what the value is
<style name="MyColorfulTheme" parent="AppTheme">
<item name="custom_background">#ff0000</item>
</style>
or
<style name="MyBoringTheme" parent="AppTheme">
<item name="custom_background">#ffffff</item>
</style>
You can refer to the attribute in a style
<style name="MyDefaultLabelStyle" parent="AppTheme">
<item name="android:background">?background_label</item>
</style>
Notice the question mark, as also used for reference android attribute as in
?android:attr/colorBackground
As most of you have noticed, you can -and probably should- use @color references instead of hard coded colors.
So why not just do
<item name="android:background">@color/my_background_color</item>
You can not change the definition of "my_background_color" at runtime, whereas you can easily switch themes.

- 371
- 2
- 5