Default values for attributes not available in theme - android

I have defined custom attribute to use in my views:
<attr name="customColor" format="color"/>
And specified its value in my theme:
<style name="AppTheme" parent="Theme.AppCompat.Light">
<!-- some other values -->
<item name="customColor">#6d4c41</item>
</style>
Now, I can use this attribute value in my view. Both in XML:
<TextView
android:textColor="?attr/customColor"
/>
and in code (Kotlin):
with(
context.theme.obtainStyledAttributes(
intArrayOf(R.attr.customColor)
)
) {
textColor = getColor(0, Color.parseColor("#ffffff"))
recycle()
}
(Java):
TypedArray a = context.getTheme().obtainStyledAttributes(
new int[]{R.attr.customColor}
);
textColor = a.getColor(0, Color.parseColor("#ffffff"));
a.recycle();
In case I forget to define the value in my theme, the code above will work fine, since I specified the default value. However, the XML will throw an exception when I try to inflate it.
Is there a way to specify default attribute value for XML?

Related

How to get an ?attr/ value programmatically

I'm trying to do some custom view styling and I'm having trouble correctly picking up styled attributes from the theme.
For instance, I would like to get the themes EditText's Text Colour.
Looking through the theme stack you can see my theme uses this to style it's EditText's:
<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>
What I'm looking for, is how do I get that ?attr/editTextColor
(Aka, the value assigned by the theme to "android:editTextColor")
Searching through google, I have found enough of this answer:
TypedArray a = mView.getContext().getTheme().obtainStyledAttributes(R.style.editTextStyle, new int[] {R.attr.editTextColor});
int color = a.getResourceId(0, 0);
a.recycle();
But I'm pretty sure I must be doing this wrong as it always displays as black rather than grey?
As commented from #pskink:
TypedValue value = new TypedValue();
getContext().getTheme().resolveAttribute(android.R.attr.editTextColor, value, true);
getView().setBackgroundColor(value.data);
will pull the attribute from the theme currently assigned to the context.
Thanks #pskink!
Have you tried this ?
EDIT :
This is my short answer if you want a complete answer ask me
Your attrs.xml file :
<resources>
<declare-styleable name="yourAttrs">
<attr name="yourBestColor" format="color"/>
</declare-styleable>
</resources>
EDIT 2 : Sorry I forgot to show how I'm using my attr value in layout.xml, so :
<com.custom.coolEditext
android:id="#+id/superEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:yourBestColor="#color/any_color"/>
and then in your custom Editext :
TypedArray a = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.yourAttrs, 0, 0);
try {
int colorResource = a.getColor(R.styleable.yourAttrs_yourBestColor, /*default color*/ 0);
} finally {
a.recycle();
}
I'm not sure if it is the answer what you want but it can put you on good way

Default Style Resource pre API Level 21

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.

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);

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

multiple style values inside a view

I have 2 styles defined inside styles.xml. I want to apply it to a textview. How to implement that using style = "#style/"
You can't. You will have to create a style which combines the two styles. (Or create just one style that inherits from one of your styles, and add the extra data of the second style).
You can make a style that inherit the other style
For example:
<style name="Side_Menu_Button" parent="android:attr/buttonStyleSmall">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">match_parent</item>
</style>
Where the side_menu_button inherit from all the attribute of buttonStyleSmall
As a workaround that can work in some situations, you can wrap your target view with LinearLayout and assign one style to the layout another to the view:
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
style="#style/padding">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Bold text with padding"
style="#style/text_bold" />
</LinearLayout>
This is a hack that I got to work:
<style name="TextAppearance.Title.App" parent="TextAppearance.AppCompat.Subhead">
<item name="android:textColor">#color/primary_text_default_material_light</item>
</style>
<style name="Custom.TV" parent="TextView.App">
<item name="android:textAppearance">#style/TextAppearance.Other.App</item>
</style>
For the particular case of a Button and other Views which support the textAttribute attribute, you can divide the two styles into a Button specific style which would be assigned to attribute:style and a Text specific style which would be assigned to attribute:textAppearance. Note though, that attributes defined in attribute:style will override values defined in attribute:textAppearance.
I know I'm 10 years late but I came across this problem myself and found a solution for it albeit it's quite a workaround.
To get started you need to declare styleable attributes to assign to your view later on
<declare-styleable name="TextView">
<attr name="style1" format="reference" />
<attr name="style2" format="reference" />
<attr name="style3" format="reference" />
<attr name="style4" format="reference" />
<attr name="style5" format="reference" />
</declare-styleable>
You can just add these style attributes to your view within the layout like
<TextView
android:id="#+id/button_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/nice_cta"
app:style1="#style/Background_Blue"
app:style2="#style/CallToAction.Primary"
app:style3="#style/Button_Layout" />
To make it work you need to implement a custom ViewInflater that you assign to your application's theme under viewInflaterClass. Inside this ViewInflater you collect the styleable attributes and merge them into a theme as follows:
class MultiStyleViewInflater : MaterialComponentsViewInflater() {
// override the creators of any view you want to have multiple styles
override fun createTextView(context: Context, attrs: AttributeSet?): AppCompatTextView {
// create context if needed and set the attributes as usual
return super.createTextView(createContextIfMultiStyle(context, attrs), attrs)
}
// override fun anyOtherView as needed ...
private fun createContextIfMultiStyle(context: Context, attrs: AttributeSet?): Context {
// get our handy custom attributes
val styleAttributes = context.obtainStyledAttributes(attrs, R.styleable.TextView)
// collect the styles added to the view
val styles = extractStyles(styleAttributes)
// create the custom ContextThemeWrapper only if the view has a custom multi style attribute
val createdContext = if (styles.any { it != 0 }) {
// create a theme, add styles and create the wrapper using the theme
val theme = context.resources.newTheme()
theme.applyValidStyles(styles)
ContextThemeWrapper(context, theme)
} else {
// or just return the original context
context
}
// don't forget to call this!
styleAttributes.recycle()
return createdContext
}
private fun extractStyles(styleAttributes: TypedArray) = listOf(
// the zero values help us determine if we have a custom style added at all
styleAttributes.getResourceId(R.styleable.TextView_style1, 0),
styleAttributes.getResourceId(R.styleable.TextView_style2, 0),
styleAttributes.getResourceId(R.styleable.TextView_style3, 0),
styleAttributes.getResourceId(R.styleable.TextView_style4, 0),
styleAttributes.getResourceId(R.styleable.TextView_style5, 0)
)
private fun Resources.Theme.applyValidStyles(styles: List<Int>) {
// adding styles that actually exist. note we force update duplicate attributes
styles.filterNot { it == 0 }.forEach { this.applyStyle(it, true) }
}
}
To make this your app theme's ViewInflater add this line to it:
<item name="viewInflaterClass">com.agostonr.multistyleapp.utils.MultiStyleViewInflater</item>
After this if you build your application the styles should show up in the editor as well as on the running app on your device.
For a more detailed explanation see the article I wrote about it on Medium.

Categories

Resources