Search code examples
androidandroid-theme

How to: Define theme (style) item for custom widget


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?


Solution

  • 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.