Default Style Resource pre API Level 21 - android

I am looking to create a custom ViewGroup to be used in a library; which contains a few ImageButton objects. I would like to be able to apply a style each ImageButton; but I cannot figure out how to apply a style programmatically other than by applying a attribute resource to the defStyleAttr parameter; like so:
mImageButton = new ImageButton(
getContext(), // context
null, // attrs
R.attr.customImageButtonStyle); // defStyleAttr
The issue with this is that the only way to change the style of each ImageButton would be by applying a style to this attribute in a parent theme. But I would like to be able to set a default style, without having to manually set this attribute for each project that uses this library.
There is a parameter that does exactly what I am looking for; defStyleRes, which can be used like so:
mImageButton = new ImageButton(
getContext(), // context
null, // attrs
R.attr.customImageButtonStyle, // defStyleAttr
R.style.customImageButtonStyle); // defStyleRes
This parameter is only available at API Level 21 and above, but my projects target API Level 16 and above. So how can I set the defStyleRes, or apply a default style, without access to this parameter?
I applied my style using a ContextThemeWrapper, as suggested by #EugenPechanec, which seems to work well, but each ImageButton now has the default ImageButton background, even though my style applies <item name="android:background">#null</item>.
Here is the style I am using:
<style name="Widget.Custom.Icon" parent="android:Widget">
<item name="android:background">#null</item>
<item name="android:minWidth">56dp</item>
<item name="android:minHeight">48dp</item>
<item name="android:tint">#color/selector_light</item>
</style>
And this is how I am applying it:
ContextThemeWrapper wrapper = new ContextThemeWrapper(getContext(), R.style.Widget_Custom_Icon);
mImageButton = new AppCompatImageButton(wrapper);
On the left is what I am getting, and on the right is what I would like it to look like:

defStyleAttr is for resolving default widget style from theme attribute.
Example: AppCompatCheckBox asks for R.attr.checkBoxStyle. Your theme defines <item name="checkBoxStyle">#style/Widget.AppCompat.CheckBox</item>.
If that attribute is not defined in your theme the widget would pickup its defStyleRes e.g. R.style.Widget_AppCompat_CheckBox.
Note that these are not actual values used by the widget.
I have not seen defStyleRes constructor parameter used outside of the framework. All of these parameters (plus defaults) are however used when asking TypedArray for resources.
How to actually solve your problem
So the four parameter constructor is not available on all platforms. You need to find a way to feed in your default style. Consider a style you'd like to apply:
<style name="MyImageButtonStyle" parent=""> ... </style>
You need a way to convert it to a defStyleAttr parameter. Define the default style on a theme overlay:
<style name="MyImageButtonThemeOverlay" parent="">
<!-- AppCompat widgets don't use the android: prefix. -->
<item name="imageButtonStyle">#style/MyImageButtonStyle</item>
</style>
Now you can create your ImageButton using this theme overlay:
// When creating manually you have to include the AppCompat prefix.
mImageButton = new AppCompatImageButton(
new ContextThemeWrapper(getContext(), R.style.MyImageButtonThemeOverlay)
);
You don't need to specify any other parameters as AppCompatImageButton will pickup R.attr.imageButtonStyle by default.
If that looks hacky you can always inflate your custom view hierarchy or individual widgets from XML where you specified the style="#style/MyImageButtonStyle" attribute.

Related

How to programmatically create a static styled MaterialButton?

Goal
How to programmatically create a MaterialButton with a custom style resources instead of attr?
styles.xml
<style name="CustomButtonStyle" parent="Widget.MaterialComponents.Button.OutlinedButton">
<item name="android:textColor">#FF0000</item>
</style>
I have tried with the following, but it is not working as expected (the button will fallback to R.style.Widget_MaterialComponents_Button no matter which style were provided).
MaterialButton button = new MaterialButton(context, null, R.style.CustomButtonStyle);
Note
I do NOT wish to create a custom theme attribute and pass it like so new MaterialButton(context, null, R.attr.CustomButtonStyle);
Tried with material-components-android 1.2.0-alpha05 and 1.1.0

How can you locate the default style for a widget (i.e. AppCompatEditText)

UPDATE: As pointed out in the comments, 'Widget.AppCompat.EditText' is the correct parent style for an AppCompatEditText subclass. In my case, the real issue was I had forgotten to assign a value to our control's default style attribute in our theme, so our control wasn't getting any style to use as a default.
However, this question still could use an answer as to how one properly identifies which style to use as a parent when defining your own default styles when subclassing the standard controls. As such, I've also renamed its title.
As such, I'm leaving this open in hopes someone can answer that question since it will help any who wish to do something similar.
We're trying to define a common look-and-feel for all AppCompatEditText controls used throughout the app. As such, rather than having to manually apply the 'style' attribute on each usage, we're instead trying to replace the default style with our own.
Replacing the default style is actually the easy part. What isn't is knowing what the parent style for our style should be set to so we still have all aspects of the original style which we haven't explicitly overwritten with those in ours.
Digging in the source code for AppCompatEditText, it shows the default style to be stored in R.attr.editTextStyle but I'm not sure where now to look to see what value is stored in it.
Experimenting too didn't get us anywhere. No matter what we have tried so far, we lose the default appearance completely. No underline, no background, no padding, nothing. Just the values we've set, which means it's not picking up the parent style.
We've tried the following without success...
<style name="ZinEditText" parent="android:Widget.EditText">
<item name="zinTypeface">light</item>
<item name="android:textSize">#dimen/defaultTextSize</item>
<item name="android:lineSpacingMultiplier">#dimen/defaultLineSpacing</item>
</style>
<style name="ZinEditText" parent="Widget.AppCompat.EditText">
<item name="zinTypeface">light</item>
<item name="android:textSize">#dimen/defaultTextSize</item>
<item name="android:lineSpacingMultiplier">#dimen/defaultLineSpacing</item>
</style>
<style name="ZinEditText" parent="Base.V7.Widget.AppCompat.EditText">
<item name="zinTypeface">light</item>
<item name="android:textSize">#dimen/defaultTextSize</item>
<item name="android:lineSpacingMultiplier">#dimen/defaultLineSpacing</item>
</style>
<style name="ZinEditText" parent="Widget.Holo.EditText">
<item name="zinTypeface">light</item>
<item name="android:textSize">#dimen/defaultTextSize</item>
<item name="android:lineSpacingMultiplier">#dimen/defaultLineSpacing</item>
</style>
As I said, none of the above seemed to work.
So how does one find the actual parent style to use?
To address the immediate issue, the default style for an AppCompatEditText is Widget.AppCompat.EditText. The second example you've shown is the correct one:
<style name="ZinEditText" parent="Widget.AppCompat.EditText">
...
This needs to be set as the editTextStyle in your app theme.
<style name="AppTheme" parent="#style/Theme.AppCompat">
<item name="editTextStyle">#style/ZinEditText</item>
...
Finding these default styles and attributes is not well documented anywhere officially, as far as I'm aware. The official documentation for Styles and Themes simply directs one to the various R.attr pages for the framework and support packages, to "discover" what's available. However, a generally reliable way to find this for most Views that allow a default style is to inspect the source code.
A View subclass will often implement at least three constructors: one that takes only a Context; one that takes a Context and an AttributeSet; and one that takes a Context, an AttributeSet, and an int for defStyleAttr, a default style attribute. This attribute is what we're looking for. It will usually have a sensible name, like editTextStyle, textViewStyle, checkboxStyle, etc. If you already know the name, you can skip checking the View class for it.
In Views that chain their constructors, this attribute will be normally be in the call to the three-parameter constructor from the two-parameter one. In AppCompatEditText, we can see that the name of this attribute is editTextStyle.
public AppCompatEditText(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.editTextStyle);
}
After we've got the name for the attribute, we then head to the res/values/ directory for the platform or support package the View is in. The default value will be in the relevant themes*.xml file for your app's parent theme.
For the platform themes, there is a base themes.xml, and a few others for specific theme versions, such as Holo and Material.
For support library Views, these theme files will be under the package-specific res/values/ directory, and the default attribute value may be in themes.xml or themes_base.xml.
In v7 appcompat, our app's exact parent theme is likely in v7/appcompat/res/values/themes.xml, though most of the themes there are just direct aliases for base themes; i.e., they don't override any of their parents' attribute values. The default for AppCompatEditText is actually in v7/appcompat/res/values/themes_base.xml. There are separate entries for different themes - the regular, and the light - but they are both the same.
<item name="editTextStyle">#style/Widget.AppCompat.EditText</item>
This is enough to determine which style to use as our parent, but should we want to check out the style specifics, we can then refer to v7/appcompat/res/values/styles.xml, where we find that style's parent:
<style name="Widget.AppCompat.EditText" parent="Base.Widget.AppCompat.EditText"/>
which leads us to v7/appcompat/res/values/styles_base.xml:
<style name="Base.Widget.AppCompat.EditText" parent="Base.V7.Widget.AppCompat.EditText" />
<style name="Base.V7.Widget.AppCompat.EditText" parent="android:Widget.EditText">
<item name="android:background">?attr/editTextBackground</item>
<item name="android:textColor">?attr/editTextColor</item>
<item name="android:textAppearance">?android:attr/textAppearanceMediumInverse</item>
</style>

How to get the parent theme programmatically

Let's say I have the following custom theme declared in themes.xml:
<style name="Theme.Custom.Light" parent="#style/Theme.Sherlock.Light">
<item name="android:actionBarTabStyle">#style/Widget.Custom.Light.ActionBar.TabView</item>
<item name="android:actionBarTabTextStyle">#style/Widget.Custom.Light.ActionBar.TabText</item>
<item name="android:actionMenuTextColor">#color/ab_item_text</item>
<item name="android:actionMenuTextAppearance">#style/TextAppearance.Custom.Light.Widget.ActionBar.Menu</item>
</style>
From the application context, we are able to get the Theme class currently applied using
Theme myTheme = appContext.getTheme();
and also, we are able to get the theme's resource id using:
int themeResId = appContext.getApplicationInfo().theme;
What I want
From my code, I would like to check programmatically which is the parent theme of the theme I'm using in order to differentiate between Sherlock, Sherlock.Light & Sherlock.Light.DarkActionBar.
In the example above, I would like to know that I am using the Light variation of the Sherlock theme.
Note: You may wonder why I need to check the parent if I declared it in the xml. Reason is that I'm in a particular situation in which I actually won't know, but this goes beyond the scope of this question.
Theme myTheme = appContext.getTheme();
You can get its parent class using
Theme myThemeParent = appContext.getTheme().getClass().getSuperclass();
and compare it with Sherlock.getclass() to verify if it is the parent. Likewise for other comparisons.

Use appropriate colors irrespective of themes

I want my activity to have two possible themes, say Theme_Holo and Theme_Holo_Light, as selected by the user. I need to programmatically draw things like horizontal dividers in this activity. The color of the divider should depend on the selected theme. How can I do that easily?
Ideally there should be a name for the standard color of a divider irrespective of the theme used, and the actual RGB realization of that color name would match the selected theme automatically. Is there such thing? It seems unlikely to me that the programmer needs to hardcode RBG values.
Of course, the divider is only an example. I would also like to name the color of EditText, or other widgets, in a way that does not depend on the theme.
In your Activity's onCreate() method(s), before calling setContentView(), set the theme using this.setTheme(customTheme);
or give it a try, Using Themes in Android Applications
You can create your own theme attributes and use them in your app's themes, and allow the user to switch between your app's themes.
First, make a file called attrs.xml in /res/values folder and define some theme attributes:
<resources>
<attr name="myDividerColor" format="color" />
</resources>
Next, make two themes in your /res/values/styles.xml (or in /res/values/themes.xml if you do your themes and styles separately). One theme extends Android's dark theme and one extends Android's light theme. Add your custom attributes to your themes:
<resources>
<!-- Dark theme -->
<style name="AppTheme_Dark" parent="#android:Theme.Holo">
...
<item name="myDividerColor">#color/divider_dark</item>
</style>
<!-- Light theme. -->
<style name="AppTheme_Light" parent="#android:Theme.Holo.Light">
...
<item name="myDividerColor">#color/divider_light</item>
</style>
</resources>
Note that I used name="myDividerColor", NOT android:name="myDividerColor"
Finally, in your Activity code you can get the color as follows:
// the attrs you want
int[] attrs = {R.attr.myDividerColor};
// get attr values for the current theme
TypedArray a = obtainStyledAttributes(attrs);
// first arg is the index of the array, in same order as attrs array above
// second arg is a default value (if not defined or not a resource)
int dividerColor = a.getColor(0, Color.TRANSPARENT);

Custom Android attribute without namespace

I'm making custom LayoutInflater.Factory and in onCreateView() method I'd like to obtain custom attribute specified for current view. However I don't want to declare styleable for this attribute. I can get this parameter if it was specified in xml attribute for this View using:
attrs.getAttributeValue(null, attributeName);
I can even get it if it was specified in style parameter of the View using:
context.obtainStyledAttributes(attrs, new int[]{R.attr.custom_attribute);
However it seems to be impossible to get this attribute if it's specified in theme. Any help would be appreciated.
Here is my xml resouces:
<style name="CustomTheme"
parent="android:Theme.DeviceDefault">
<item name="android:textViewStyle">#style/TextView.Custom</item>
</style>
<style name="TextView.Custom">
<item name="custom_attribute">"blabla"</item>
</style>
P.S. I know I should declare slyleable resource for custom attributes, and it works if I do so, but I want to be sure it is not possible otherwise because it is simplier to use this way.
Turnd out you can get it usig this:
TypedValue value = new TypedValue();
theme.resolveAttribute(android.R.attr.textViewStyle, value, true);
typedArray = theme.obtainStyledAttributes(value.resourceId, new int[] {R.attr.custom_attribute});

Categories

Resources