How to reference style attributes from a drawable? - android

I want to have 2 selectable themes for my application. In order to do that, I defined some attributes, like this:
<attr format="color" name="item_background" />
Then, I created both themes, like this:
<style name="ThemeA">
<item name="item_background">#123456</item>
</style>
<style name="ThemeB">
<item name="item_background">#ABCDEF</item>
</style>
This method works great, allowing me to create and modify several themes easily. The problem is that it seems that it can be used only in Views, and not in Drawables.
For example, referencing a value from a View inside a layout works:
<TextView android:background="?item_background" />
But doing the same in a Drawable doesn't:
<shape android:shape="rectangle">
<solid android:color="?item_background" />
</shape>
I get this error when running the application:
java.lang.UnsupportedOperationException: Can't convert to color: type=0x2
If instead of ?item_background I use a hardcoded color, it works, but that doesn't allow me to use my themes. I also tried ?attr:item_background, but the same happens.
How could I do this? And why does it work in Views but not in Drawables? I can't find this limitation anywhere in the documentation...

In my experience it is not possible to reference an attribute in an XML drawable.
In order to make your theme you need to:
Create one XML drawable per theme.
Include the needed color into you drawable directly with the #color tag or #RGB format.
Make an attribute for your drawable in attrs.xml.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Attributes must be lowercase as we want to use them for drawables -->
<attr name="my_drawable" format="reference" />
</resources>
Add your drawable to your theme.xml.
<style name="MyTheme" parent="#android:style/Theme.NoTitleBar">
<item name="my_drawable">#drawable/my_drawable</item>
</style>
Reference your drawable in your layout using your attribute.
<TextView android:background="?my_drawable" />

Starting with lollipop (API 21) this feature is supported, see
https://code.google.com/p/android/issues/detail?id=26251
However, if you're targeting devices without lollipop, don't use it, as it will crash, use the workaround in the accepted answer instead.

Although it's not possible to reference style attributes from drawables on pre-Lollipop devices, but it's possible for color state lists. You can use AppCompatResources.getColorStateList(Context context, int resId) method from Android Support Library. The downside is that you will have to set those color state lists programmatically.
Here is a very basic example.
color/my_color_state.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:color="?colorControlActivated" />
<item android:color="?colorControlNormal" />
</selector>
A widget that needs a color state list:
<RadioButton
android:id="#+id/radio_button"
android:text="My Radio" />
And the most important:
ColorStateList csl = AppCompatResources.getColorStateList(context, R.color.my_color_state);
RadioButton r = (RadioButton) findViewById(R.id.radio_button);
r.setTextColor(csl);
Well, not the most elegant or shortest way, but this is what Android Support Library does to make it work on older versions (pre-Lollipop) of Android.
Unfortunately, the similar method for drawables doesn't work with style attributes.

I answered the same question in https://stackoverflow.com/a/59467269/3841352 but i will post it here as well:
I encountered the same problem and as of 2019 it hasn't been resolved so you can't have an attribute referenced in a selector as a drawable. I will share the solution I got for the problem as I don't see it posted in here. I found it in the last comment of the bug report.
The workaround is basically create a drawable resource that will be the one referring the attribute value.
To illustrate your case the solution would be instead of:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?attr/colorPrimary" android:state_enabled="true" android:state_window_focused="false"/>
<item android:drawable="?attr/colorPrimaryDark" android:state_pressed="true"/>
<item android:drawable="#android:color/darker_gray" android:state_enabled="false"/>
<item android:drawable="?attr/colorPrimary"/>
</selector>
you would replace the ?attr/* for a drawable resource:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/colorPrimaryDrawable" android:state_enabled="true" android:state_window_focused="false"/>
<item android:drawable="#drawable/colorPrimaryDarkDrawable" android:state_pressed="true"/>
<item android:drawable="#android:color/darker_gray" android:state_enabled="false"/>
<item android:drawable="#drawable/colorPrimaryDrawable"/>
</selector>
The drawables would be defined as:
drawable/colorPrimaryDrawable
<shape xmlns:android="http://schemas.android.com/apk/res/android"android:shape="rectangle">
<solid android:color="?attr/colorPrimary" />
</shape>
drawable/colorPrimaryDarkDrawable
<shape xmlns:android="http://schemas.android.com/apk/res/android"android:shape="rectangle">
<solid android:color="?attr/colorPrimaryDark" />
</shape>
Hope it helps!!

As #marmor stated this is now supported on API 21. But for those us who need to support legacy versions of Android, you can use this feature. Using the v7 support library you can still use it on apps with minimum SDK level all the way down to 7.
The AppCompatImageView in the v7 Android Support Library has a bug free implementation of this feature. Simply replace your usages of ImageView with AppCompatImageView.

Related

Add rounded corners when having android:drawableLeft with png

To make it short, this is what I would like to add a custom button that looks like this:
IMG is a .png file in my mipmap folders and SOME TEXT is just a string value. What the dashed line is added just as a separator in the image, not in the button.
The issue is that the rounded edges don't appear where the image is added. It looks like this:
My questions are the following:
Can this be achieved?
Is there a way to override the <solid /> attribute in <shape />?
I will have to create 10 of these buttons each with different colors and if I add android:color with a different value, the color does not change
When adding the image, it makes me choose only one (e.g. the mdpi one). If this will be displayed on larger screens, will it take a different .png image based on the size?
Is there a specific type of button I should use? I would like to revert the colors when the button is pressed and stay as pressed. I have a vague idea about how this can be achieved, but is there a way to do this for the .png files as well or do I need to import into the project others with the colors already inverted and just switch them?
custom_button.xml
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:topLeftRadius="250px"
android:bottomLeftRadius="250px"
android:topRightRadius="50px"
android:bottomRightRadius="50px" />
<solid
android:color="#color/YellowPrimary"/>
</shape>
button_styles.xml
<resources>
<style name="CategoryToggle">
<item name="android:background">#drawable/custom_button</item>
<item name="android:textAllCaps">true</item>
</style>
<style name="CategoryToggle.First">
<item name="android:color">#color/bluePrimary</item> // Does not override <solid>
<item name="android:drawableLeft">#mipmap/icon_48mdpi</item>
<item name="android:text">#string/first_cat</item>
</style>
</resources>
button_layout.xml
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
style="#style/CategoryToggle.History"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
I have no java code at the moment as I just started and trying to implement this weird button format.
This is how it looks at my end:
Absolutely. As I mentioned in the comments, either make sure your drawable has a transparent background, or create a custom button to mask the drawable.
You'll want to use a style attribute apply a different style to each button. By this I mean the color defined in your custom_button.xml should reference a color attribute (something like colorAccent should work in your case), instead of a static color.
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?attr/colorAccent"/>
</shape>
Then change this color in your button styles instead of android:color.
<style name="CategoryToggle">
<item name="colorAccent">#color/YellowPrimary</item>
</style>
Make sure you have the support library dependency added, or colorAccent will not be available.
Use the android:theme attribute, instead of the style attribute to apply the button theme.
<Button
android:width="wrap_content"
android:height="wrap_content"
android:theme="#style/CategoryToggle"/>
It looks like your drawables are not using resource qualifiers. You'll need to make sure each alternative resource has the exact same name as the original (i.e. your icon_48mdpi.png should instead be called icon_48dp.png for all configurations) and is placed in the corresponding drawable folder for its density. Your drawable resources should look like the following (in the Project view structure, not the Android view structure).
res/
|-- drawable/
| +-- custom_button.xml
|-- drawable-hdpi/
| +-- icon_48dp.png
|-- drawable-mdpi/
| +-- icon_48dp.png
|-- drawable-xhdpi/
| +-- icon_48dp.png
|-- drawable-xxhdpi/
| +-- icon_48dp.png
|-- drawable-xxxhpdi/
| +-- icon_48dp.png
~
To change the color of a drawable based on state, you will need to abstract your color one step further and create a color state list.
res/color/button_color_selector.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#color/pressed_color" android:state_pressed="true"/>
<item android:color="#color/focused_color" android:state_focused="true"/>
<item android:color="#color/state_hovered" android:state_hovered="true"/>
<item android:color="?attr/colorAccent"/>
</selector>
Then you can use this color resource in your shape drawable instead of colorAccent.
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#color/button_color_selector"/>
</shape>
You can also make each of the colors in your color state list a styleable attribute by defining custom attributes and referencing those attributes in your styles. I won't go into that further for the sake of brevity though.
You can do this for drawables similarly by creating a state list drawable.
Lastly, you'll want to get into the habit of using dp instead of px unless you are absolutely certain you want to use px. This will prevent strange appearances at different screen densities.
On 4 "is there a way to (revert colors when pressed) for the .png files".. yes there are ways.
The by-the-book option is to use xml styles to specify different drawables for different button states. Consider ImageButton which has an example of this in its docs: https://developer.android.com/reference/android/widget/ImageButton.html
Another option is to apply tints or other filters to the drawable in your own code in button interaction callbacks. Compared to using different drawables, in this approach you add custom code instead of leveraging builtin resources, so you may wind up testing and debugging more on different devices, and you get full control so you can do custom animations and other elaborate things that may not fit well into Android resource tooling. Can checkout setColorFilter for starters:
https://developer.android.com/reference/android/widget/ImageView.html#setColorFilter(int,%20android.graphics.PorterDuff.Mode)
Thanks for adding some code and images or your problem, helps in understanding it.
Big shout out to Bryan for his insight on this matter. It led me to the paths I needed to follow in order to solve this issue.
I am posting this answer so that others with similar cases will know the steps. Although I have quite a number of files, this procedure did the trick perfectly.
I have my custom button as described in the link provided by Bryan.
For the rest I have the following:
Change the text color:
button_text_color.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#color/redPrimary" android:state_pressed="true"/>
<item android:color="#color/redDark"/>
</selector>
Change the background: button_background.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape android:shape="rectangle">
<corners android:bottomLeftRadius="400dp" android:radius="100dp" android:topLeftRadius="400dp" />
<solid android:color="#color/redDark" />
</shape>
</item>
<item android:state_enabled="true">
<shape android:shape="rectangle">
<corners android:bottomLeftRadius="400dp" android:radius="100dp" android:topLeftRadius="400dp" />
<solid android:color="#color/redPrimary" />
</shape>
</item>
</selector>
Change the drawable: button_drawable.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/ic_button_inverted" android:state_pressed="true" />
<item android:drawable="#drawable/ic_button"/>
</selector>
button_style.xml
<style name="MyButtonStyle">
<item name="android:background">#drawable/button_background</item>
<item name="android:drawableLeft">#drawable/button_drawable</item>
<item name="android:text">#string/button_text</item>
<item name="android:textColor">#color/button_text_color</item>
</style>
Implementing it in main_layout.xml
<packagename.MyCustomButton xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/myButton"
style="#style/MyBUttonStyle"
android:layout_marginBottom="8dp" />
Just use the official library.
Add the MaterialButton component using these attributes:
app:icon to add the icon
app:iconGravity to decide the position of the icon
app:iconPadding to increase/decrease the space between the icon and the text
android:paddingLeft the padding between the edge and the icon
<com.google.android.material.button.MaterialButton
android:paddingLeft="2dp"
app:icon="#drawable/ic_add_24px"
app:iconGravity="start"
app:iconPadding="4dp"
app:shapeAppearanceOverlay="#style/ShapeAppearanceOverlay.Button.RoundedRight"
.../>
With the app:shapeAppearanceOverlay you can customize the shape of the component.
<style name="ShapeAppearanceOverlay.Button.RoundedRight" parent="">
<item name="cornerFamily">rounded</item>
<item name="cornerSizeTopRight">4dp</item>
<item name="cornerSizeBottomRight">4dp</item>
<item name="cornerSizeTopLeft">16dp</item>
<item name="cornerSizeBottomLeft">16dp</item>
</style>
You can obtain the same result using a style.

Error inflating ImageView / ImageButton with ColorStateList tint value

Using an ImageView / ImageButton (AppCompatImageView / AppCompatImageButton) in conjunction with a style attribute of android:tint which makes use of a ColorStateList resource works fine on >= API 21, but throws an InflateException on API < 21.
Firstly, I don't even know whether the AppCompatImageView / (Button) tinting supports ColourStateList xml resources as an android:tint value, I can't seem to find a definitive answer to this. Suggestions I can find on S/O suggest implementing a TintableImageView etc, but these answers are quite dated, and it seems from the source of the appcompat implementations this should be a feature.
To clarify this is definitely the issue. Removing the android:tint attribute or setting it to a single colour resource works.
Also to clarify, I'm aware this is achievable programmatically. I'm trying to get it backwards compatible in xml.
Minimal example
activity_foo.xml
<android.support.v7.widget.AppCompatImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_caret_up"
style="#style/IconButton.Primary"
/>
styles.xml
<style name="IconButton.Primary">
<item name="android:tint">#color/link_button_color</item>
</style>
link_button_color.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#color/btnLinkPressedTextColor"
android:state_selected="true" />
<item android:color="#color/btnLinkPressedTextColor"
android:state_pressed="true" />
<item android:color="#color/btnLinkTextColor" />
</selector>
In my case i replaced android:tint with app:tint and added to root element xmlns:app="http://schemas.android.com/apk/res-auto". It fixed crashing issue on API level < 21.
And color state selector /res/color/color_selector.xml looks like:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="#151515"/>
<item android:state_focused="true" android:color="#151515"/>
<item android:color="#424242"/>
</selector>
It looks like AppCompat drawable tinting only works for action bar and "some widgets" on < API 21, but works for all drawables on API 21+.
When you set these attributes, AppCompat automatically propagates their values to the framework attributes on API 21+. This automatically colors the status bar and Overview (Recents) task entry.
On older platforms, AppCompat emulates the color theming where possible. At the moment this is limited to coloring the action bar and some widgets.
Source: https://android-developers.googleblog.com/2014/10/appcompat-v21-material-design-for-pre.html
The answer here also has more detail: https://stackoverflow.com/a/29155611/608312
item name="android:tint" is wrong.
fix the 'android:tint' to 'tint'
<style name="IconButton.Primary">
<item name="tint">#color/link_button_color</item>
</style>

Why isn't my ImageButton style being applied to my ImageButton?

I have some styles set up to work on different API's (one for v21 and one for anything below that). I want to have a style for my ImageButton but it doesn't seem to be working out the way I expect.
The style for the v-21 is
<style name="BorderlessImageButton" parent="AppTheme.BorderlessImageButton">
<item name="android:background">?android:attr/selectableItemBackgroundBorderless</item>
<item name="android:tint">#color/secondary_text</item>
</style>
The style that will be used for all other API's below v-21 is
<style name="BorderlessImageButton" parent="AppTheme.BorderlessImageButton"/>
<style name="AppTheme.BorderlessImageButton" parent="#android:style/Widget.ImageButton">
<item name="android:tint">#color/secondary_text</item>
<item name="android:background">#color/borderless_button_background</item>
</style>
Here is my xml resource
<ImageButton
android:id="#+id/imageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toStartOf="#id/date_picker"
android:background="#null"
android:padding="10dp"
android:src="#drawable/ic_today_white_24dp"
android:theme="#style/BorderlessImageButton" />
If I run this on a device that has v-21 or v-22, the button doesn't visually react to my touch as I would expect using the ?android:attr/selectableItemBackgroundBorderless. Also, on any device below v-21, the button still doesn't react to the selector resource I have set up for it.
res/color/borderless_button_background.xml:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="#color/accent" />
<item android:color="#color/transparent"/>
</selector>
Please help me get my button to react properly to touch based on which API the user has on their device.
Thanks
You need to apply your style with
style="#style/BorderlessImageButton"
The attribute android:theme is used for activities, see this.
UPDATE
Since Android 5.0, or using AppCompat 22.1.0+ (api 11+), you can also use a ThemeOverlay and use android:theme on single views. You can read about this technique here. Thanks to Ian for this.

Use colorPrimary in drawable on pre-lollipop with AppCompat

i am using AppCompat 22.1.1.
For some reasons, my app can change its theme on the fly, during the user navigation. (i.e. move to an another part of the app, like in the google play store app, when you're moved from the "my apps" part, to the "movie" part for example)
To avoid creating one drawable background per theme, i tried to create a background like this :
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?attr/colorPrimary" /></shape>
When changing the theme programmatically, i suppose the colorPrimary will change too, and a button inflated after that, will be tinted with the colour of the new theme.
But i have an inflate exception on pre-lollipop (but works on lollipop). The drawable cannot find the attribute attr/colorPrimary, why ?
Here is the simple theme.xml i'm using :
<style name="MyTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">#color/my_blue</item>
<item name="colorPrimaryDark">#color/my_blue_dark</item>
<item name="colorAccent">#color/my_blue_light</item>
</style>
The colors are in values/colors.xml, just hexa colors.. And all resources are in the "values" dir, and NOT in values-r21 dir.
Create a color_primary.xml color resource in res/color/:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/colorPrimary"/>
</selector>
Then reference this in your drawable:
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#color/color_primary" />
</shape>
From my experience, I don't know, but I'm pretty sure there's nothing you can do via XML. Lots of framework resources are provided twice, in the form some_drawable_dark.xml and some_drawable_light.xml; it looks like you can't reference theme values from drawable and color folders.
So you should either:
Create x static resources, where x is the number of themes you are putting in;
Operate at runtime, using setColorFilter() and such. Depending on the case, it can be hard.

Color reference in <item>-Tag, inside <layer-list>

I have a question about using references in a layer-list drawable.
I want to use a custom button in my app, made of a layer-list.
That is the final drawable btn.xml for the button, made of a selector:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/btn_pressed" android:state_pressed="true"/>
<item android:drawable="#drawable/btn_normal"/>
</selector>
The pressed-state-drawable btn_pressed looks like that:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<shape android:shape="rectangle" >
<solid android:color="?custom_color" />
</shape>
</item>
<item android:drawable="#drawable/btn_normal"/>
</layer-list>
The essential part is the line, where i declare the color to be a reference to custom_color, that i define as follows.
attrs.xml:
<resources>
<attr name="custom_color" format="reference|color" />
</resources>
themes.xml:
<resources>
<style name="MyTheme" parent="android:Theme.Light.NoTitleBar">
<item name="custom_color">#ff33b5e5</item>
</style>
</resources>
Eclipse gives me no errors at all, and it compiles just fine.
But when i start the application on my ICS Nexus S, or the Emulator (no matter what version), it crashes. Logcat points the following out:
03-23 14:33:38.832: E/AndroidRuntime(636): Caused by: android.content.res.Resources$NotFoundException: File res/drawable/btn.xml from drawable resource ID #0x7f020006
[...]
03-23 14:33:38.832: E/AndroidRuntime(636): Caused by: android.content.res.Resources$NotFoundException: File res/drawable/btn_pressed.xml from drawable resource ID #0x7f020009
If I comment out, the shape-element in the layer-list, or set the color hard-coded, everything works. So there seems to be a problem referencing attributes in a layer-list.
So, does anyone know a solution to this problem? I want to change colors in that layer-list, depending on the theme, that my application is using.
Ok, seems like this is a bug that wasn't looked at until Android L.
More details on the Android Issue Tracker: Issue 26251
It should be fixed in Android L, but at least testing with the Android L Preview in the Emulator it doesn't seem to work completely, yet. At least it won't crash the app, but instead of showing the correct color, it just gives me transparency (#00ffffff). Maybe this isn't the case on the devices and/or once Android L stable is released.
Try android:color="#color/custom_color"

Categories

Resources