How To: Define Theme (Style) Item for Custom Widget

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

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.

Add custom widget styling to app theme

Constructors should be like this:

    public BarGraph(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.barGraphStyle);
}

public BarGraph(Context context, @Nullable AttributeSet attrs, @AttrRes int defStyle) {
super(context, attrs, defStyle);

// grab all the custom styling values
TypedArray styledAttributes = context.obtainStyledAttributes( attrs, R.styleable.BarGraph, defStyle, 0);

...
}

Apply built-in Android widget styles in a custom view

Unfortunately, this doesn't seem to be possible. The only example I could find that does something similar is ActionBar: you can pass in styles for the title, subtitle, and progress indicators. Looking at the source for ActionBarView, the title and subtitle TextViews' styles are applied with setTextAppearance(). The ProgressBar class has an extra constructor that accepts a fourth parameter for the style. Since most View classes don't have this extra constructor, passing in a style to them is not possible. However, there are a few alternatives:

  1. Pass a layout for the subview instead of a style and inflate it in your widget.
  2. If the subview is a child of TextView (as Button and EditText are), use setTextAppearance() for the passed style. This will apply a good bit of the styling for text. If you want to allow the user to apply other styles like background or padding, you will still need to add custom attributes for each of those as well. If you're making a compound widget there's a good chance that the user won't need to apply every possible style to the subviews, so only exposing a subset will probably suffice.
  3. Add a theme-wide style, as you already mentioned.

How can you create your own default style for a subclassed widget that adds to/overrides the base class's default style, not replaces it?


The problem there is I don't know what the default style is for the base control

Widgets and their default styles provided by the AppCompat follow a naming convention. Example: AppCompatEditText default style is controlled by the ?editTextStyle theme attribute which by default points to @style/Widget.AppCompat.EditText.

Most of the time you can guess the correct name (and the IDE will help you), but if you're looking for a "correct" way of determining the style, follow these steps:

  1. Open source of whatever class you're extending, say AppCompatEditText.
  2. Look into its two-parameter constructor. It calls through to the three-parameter constructor with the third parameter being the default style theme attribute. In this case R.attr.editTextStyle.
  3. Open values.xml from the appcompat-v7 library and find the Theme.AppCompat definition. It will contain all the default styles. Find which concrete style is referenced by the theme attribute.

It looks like this:

<style name="Theme.AppCompat" parent="@style/Base.Theme.AppCompat">
<item name="editTextStyle">@style/Widget.AppCompat.EditText</item>
<!-- Other styles... -->
</style>

  1. Define you own style...

...as extension of the style you found.

<style name="Widget.VeryCustomEditText" parent="@style/Widget.AppCompat.EditText">

If you had extended platform widgets such as EditText directly, it would have been a bit different. I'll expand the answer only if necessary.

Android, setting the theme in the style does not work

I found that the theme attribute is not set if the defining style is itself set by a theme, but only when the style is set in the designer.

The materialThemeOverlay attribute on the other hand works correctly and actually applies the theme.

I found a comment in a material design theme definition:

<style name="Widget.MaterialComponents.Toolbar.Primary">
<item name="android:elevation" ns2:ignore="NewApi">@dimen/design_appbar_elevation</item>
<item name="android:background">?attr/colorPrimary</item>
<item name="titleTextColor">?attr/colorOnPrimary</item>
<item name="subtitleTextColor">@color/material_on_primary_emphasis_medium</item>
<!-- Note: this theme overlay will only work if the style is applied directly to a Toolbar. -->
<item name="android:theme">@style/ThemeOverlay.MaterialComponents.Toolbar.Primary</item>

So, in my case, this works as expected:

<!-- Customize your theme here. -->
<item name="toolbarStyle">MyToolbarStyle</item>
...

<style name="MyToolBarTheme" parent="ThemeOverlay.MaterialComponents.Toolbar.Primary">
<item name="android:textColorPrimary">@color/white</item>
<item name="colorOnPrimary">@color/white</item>
</style>

<style name="MyToolbarStyle" parent="Widget.AppCompat.Toolbar">
<item name="materialThemeOverlay">@style/MyToolBarTheme</item>
</style>


Update

How the style and theme are applied to the views is highly dependent on how the views are implemented.

materialThemeOverlay is used inside the material view source code, so to know exactly the effect of the theme style or attribute, you should look into the view source code.

How to define a custom Application theme to specify a default background for a layout in Android?

I finally figured out how to define a custom default background for every container layout in my application

  1. First, I defined a a custom theme as a style with the app compat background as the parent background which overrides the default window background. Please note that I am using the theme from the Android compatibility library to support pre-honeycomb Android devices

     <style name="Theme.MyCustomAppTheme" parent="Theme.AppCompat.Light.DarkActionBar">

    <item name="android:windowBackground">@color/my_custom_window_background</item>
    <item name="android:colorBackground">@color/my_custom_window_background</item>

    </style>
  2. I applied the above theme to the whole application in the AndroidManifest

    <
    application android:icon="@drawable/ic_launcher" android:label="@string/app_title" android:theme="@style/Theme.MyCustomTheme"/>

  3. The above steps resulted in every window having my default window background color defined in step 1 above including action bar. I definitely did not want my action bar, edit text, text view etc to be the default window color. So, I needed to to go back to step 1 and override the style of specific UI widgets I did not want to have the default window background

      <style name="Theme.MyCustomAppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="android:windowBackground">@color/my_custom_window_background</item>
    <item name="android:colorBackground">@color/my_custom_window_background</item>
    <item name="android:actionMenuTextColor">@color/customMenuColor</item>
    <item name="android:textViewStyle">@style/customTextViewStyle</item>
    <item name="android:editTextStyle">@style/customEditTextStyle</item>
    <item name="android:buttonStyle">@style/customButtonStyle</item>
    <item name="android:listViewStyle">@style/customListViewStyle</item>
    <item name="android:tabWidgetStyle">@style/customTabWidgetStyle</item>
    <!-- Support library compatibility -->
    <item name="actionBarStyle">@style/customActionBarStyle</item>
    <item name="actionMenuTextColor">@color/customMenuColor</item>

  4. The last step was to define the each custom style define above either to have custom styles for them or have then assigned the system default styles (overridden because of custom window background)

For example, the custom action bar style will look like this:

<style name="customActionBarStyle"
parent="@style/Widget.AppCompat.Light.ActionBar.Solid.Inverse">
<item name="android:background">@color/light_black</item>
<item name="android:displayOptions">showHome|showTitle</item>
<item name="android:titleTextStyle">@style/customActionBarTitleTextStyle</item>
<!-- Support library compatibility -->
<item name="background">@color/light_black</item>
<item name="displayOptions">showHome|showTitle</item>
<item name="titleTextStyle">@style/customActionBarTitleTextStyle</item>
</style>

  1. The advantage of this approach is you can define a default theme in a centralized way and don't have to define a background for each layout as it will be injected through the custom theme applied to the entire app.. Any future re-theming of the app will be very easy with this approach

Please refer to the Android Documentation on theming including how to theme the action bar for more information



Related Topics



Leave a reply



Submit