如何: 为自定义小部件定义主题(样式)项

我为我们在整个应用程序中广泛使用的控件编写了一个自定义小部件。小部件类派生自 ImageButton并以几种简单的方式扩展它。我已经定义了一个样式,我可以应用到小部件,因为它的使用,但我更喜欢通过一个主题设置这一点。在 R.styleable中,我看到了像 imageButtonStyletextViewStyle这样的小部件样式属性。有什么方法可以为我编写的自定义小部件创建类似的东西吗?

43896 次浏览

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.

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.