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 TextView
s' 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:
- Pass a layout for the subview instead of a style and inflate it in your widget.
- If the subview is a child of
TextView
(asButton
andEditText
are), usesetTextAppearance()
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. - 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:
- Open source of whatever class you're extending, say
AppCompatEditText
. - 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
. - Open
values.xml
from the appcompat-v7 library and find theTheme.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>
- 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
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>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"/>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>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>
- 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
Get Spinner Selected Items Text
How To: Define Theme (Style) Item for Custom Widget
Change Activity's Theme Programmatically
Android - How to Set the Wallpaper Image
How to Convert Dp, Px, Sp Among Each Other, Especially Dp and Sp
Getview Returning Null When Fragment Has Been Created from an Activity
Prevent Status Bar for Appearing Android (Modified)
How to Set the Edittext Keyboard to Only Consist of Numbers on Android
How to Disable the Clear Data Button in Application Info of Manage Application
Android.Content.Activitynotfoundexception: Unable to Find Explicit Activity Class
Misbehavior When Trying to Store a String Set Using Sharedpreferences
Android 4.3 Bluetooth Low Energy Unstable
How to Parse a JSON Object in Android
How to Get the APK of an Installed App Without Root Access
Read_Logs Permission on Jelly Bean (API 16)
Is Uploading Videos from an Sd Card to Facebook Possible with the Facebook Sdk