ripple drawable crashes the app on Android API 19 - android

I am using a custom ripple drawable
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="#android:integer/config_shortAnimTime"
android:color="#android:color/white">
<item android:id="#android:id/mask">
<shape android:shape="rectangle" >
<solid android:color="#android:color/white" />
</shape>
</item>
</ripple>
but it crashes the app on API 19
android.content.res.Resources$NotFoundException: File res / drawable /
ripple_effect_square2.xml from drawable resource ID #0x7f02017d
at android.content.res.Resources.loadDrawable(Resources.java:2101)
at android.content.res.Resources.getDrawable(Resources.java:700)
at android.view.View.setBackgroundResource(View.java:15303)
Caused by: org.xmlpull.v1.XmlPullParserException: Binary XML file line # 2: invalid drawable tag ripple
at android.graphics.drawable.Drawable.createFromXmlInner(Drawable.java: 933)
at android.graphics.drawable.Drawable.createFromXml(Drawable.java: 877)
at android.content.res.Resources.loadDrawable(Resources.java: 2097)
at android.content.res.Resources.getDrawable(Resources.java: 700) 
at android.view.View.setBackgroundResource(View.java: 15303) 
What should i do to prevent crashing ?

The RippleDrawable was added in API 21, so it's not available on earlier SDKs.
You can move your drawable file to res/drawable-v21 to ensure it doesn't crash on earlier releases.

I had the same problem.
Mina Samy's answer solved my problem also.
Atef Hares asked for an alternative in older versions.
What works for me is to use a selector instead of ripple
for example:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#ccffffff" />
</shape>
</item>
<item android:state_focused="true">
<shape android:shape="rectangle">
<stroke android:color="#android:color/white" />
</shape>
</item>
<item android:drawable="#color/playColor" />
</selector>
where
<color name="playColor">#E8EAF6</color>

Ripple is only available from Lollipop (Android API 21). Here is an example for backwards compatibility which includes states and no default background (Transparent):
Create two drawable xml files with the same name, one will be placed in the drawable-v21 folder, and the other in the normal drawable folder.
drawable-v21/ripple_red_solid.xml:
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#android:color/red">
<item android:id="#android:id/mask">
<color android:color="#android:color/red" />
</item>
<item>
<selector>
<item android:state_selected="true">
<color android:color="#android:color/red" />
</item>
<item android:state_activated="true">
<color android:color="#android:color/red" />
</item>
<item android:state_focused="true">
<color android:color="#android:color/red" />
</item>
</selector>
</item>
</ripple>
drawable/ripple_red_solid.xml:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<shape android:shape="rectangle">
<solid android:color="#android:color/red" />
</shape>
</item>
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#android:color/red" />
</shape>
</item>
<item android:state_focused="true">
<shape android:shape="rectangle">
<solid android:color="#android:color/red" />
</shape>
</item>
</selector>
And then simply use it on the view like:
<LinearLayout
...
android:background="#drawable/ripple_red_solid" />

Related

Android: Button with Ripple and State Selector in Background: Resource Not Found Exception

I have a button with a ripple effect and would like to add a state selector for the background color when the button is enabled/disabled.
state_selector.xml:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#color/green" android:state_enabled="true"/>
<item android:color="#color/grey" android:state_enabled="false"/>
</selector>
ripple.xml:
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:colorControlHighlight">
<item android:id="#android:id/mask" android:drawable="#android:color/white" />
<item android:id="#android:id/background" android:drawable="#drawable/state_selector" />
</ripple>
Button.xml:
<Button
android:background="#drawable/ripple"
android:id="#+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
But the app crashes while rendering this with the following:
Resources$NotFoundException: Drawable drawable/ripple with resource ID #0x7f070071
Caused by: android.content.res.Resources$NotFoundException: File res/drawable/ripple.xml from drawable resource ID #0x7f070071
and
android.content.res.Resources$NotFoundException: Drawable drawable/state_selector with resource ID #0x7f070075
Caused by: android.content.res.Resources$NotFoundException: File res/drawable/state_selector.xml from drawable resource ID #0x7f070075
could someone please help figure out the issue?
thanks
I think #Sunny's answer works well.
This suggestion may be overkill but you can also combine the ripple and selector into a single xml file like this. This is a rounded transparent button with a ripple and an enabled and disabled state.
button_transparent_background.xml
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="#color/white">
<item>
<selector>
<item android:state_enabled="true">
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="100dp" />
<solid android:color="#color/transparent" />
<stroke
android:width="1dp"
android:color="#color/white" />
</shape>
</item>
<item android:state_enabled="false">
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="100dp" />
<solid android:color="#color/transparent" />
<stroke
android:width="1dp"
android:color="#color/whiteTransparent" />
</shape>
</item>
</selector>
</item>
<item
android:id="#android:id/mask"
android:state_pressed="true">
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="100dp" />
<solid android:color="#color/white" />
<stroke
android:width="1dp"
android:color="#color/white" />
</shape>
</item>
</ripple>
Add it to the button's background property or to the styles.xml
<style name="buttonRound" parent="Base.Widget.AppCompat.Button">
<item name="android:background">#drawable/button_transparent_background</item>
</style>
I found the issue.
The ripple needs a drawable resource.
The state selector which I had used did not provide a drawable for each state but rather it provided a color for each state. And thus it was crashing.
The following xml for the state selector would fix this issue. Everything else remains the same.
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#color/green" android:state_enabled="true"/>
<item android:drawable="#color/grey" android:state_enabled="false"/>
</selector>

Ripple effect in pressed state + transparency in normal state

I would like the ImageView in the ViewGroup, when pressed, draws the ripple and this is working! But when the ViewGroup is pressed, the ImageView inside it, should remain transparent otherwise the ImageView background is visible: (that color alpha-orange, you actually see, is the ripple when pressed).
This happens only with devices API 21+. With devices API <21 I use a selector and the image background remains transparent as wanted when the ViewGroup is pressed.
layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/item_detail_field"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="#dimen/margin_left"
android:paddingStart="#dimen/margin_left"
android:paddingTop="#dimen/margin_top_item_field"
android:paddingBottom="#dimen/margin_bottom"
android:baselineAligned="false"
android:background="#drawable/layout"
android:stateListAnimator="#anim/touch_raise"
android:orientation="horizontal">
...
<ImageView
android:id="#+id/item_detail_showfield_icon"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/button_mini_oval"
android:stateListAnimator="#anim/touch_raise"
android:clickable="true"
android:contentDescription="#null"
android:scaleType="center"
android:visibility="invisible"
android:src="#drawable/show_button"/>
...
</LinearLayout>
drawable/button_mini_oval.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape android:shape="oval">
<solid android:color="#color/primary_highlight" />
</shape>
</item>
<item>
<shape android:shape="oval">
<solid android:color="#android:color/transparent" />
</shape>
</item>
</selector>
drawable-v21/button_mini_oval.xml
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="#android:integer/config_shortAnimTime"
android:color="?android:colorControlHighlight">
<item android:state_pressed="true">
<shape android:shape="oval">
<solid android:color="#android:color/white" />
</shape>
</item>
<item>
<color android:color="#android:color/transparent" />
</item>
</ripple>
I tried many configurations in the ripple xml, adding a <selector> it works (see below), but if you just tap the ImageView, the ripple is not visible (the ripple animation seems cut), only if you keep pressed you see the ripple...
What is the combination to have ripple in pressed state and transparent in normal state?
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="#android:integer/config_shortAnimTime"
android:color="?android:colorControlHighlight">
<item>
<selector>
<item android:state_pressed="true">
<shape android:shape="oval">
<solid android:color="#android:color/white" />
</shape>
</item>
<item>
<color android:color="#android:color/transparent" />
</item>
</selector>
</item>
</ripple>
Removing android:state_pressed="true", I've found 2 solutions but in both cases I lost the touch_raise animation (why?):
With masked ripple:
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="#android:integer/config_shortAnimTime"
android:color="?android:colorControlHighlight">
<item android:id="#android:id/mask">
<selector>
<item>
<shape android:shape="oval">
<solid android:color="#android:color/white" />
</shape>
</item>
<item>
<color android:color="#android:color/transparent" />
</item>
</selector>
</item>
</ripple>
With unmasked little ripple (nicer than 1):
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="#android:integer/config_shortAnimTime"
android:color="?android:colorControlHighlight">
<selector>
<item>
<color android:color="#android:color/transparent" />
</item>
</selector>
</ripple>
I don't know why it is sufficient to add <selector> to have a ripple on a transparent background!!! a lot of people were trying to achieve this but it wasn't the intention of Google, instead it is very useful is such situations.
With me, it works
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#color/white"
android:radius="3dp">
<item android:drawable="#drawable/transparent_ripple_button"/>
</ripple>
and a separate
transparent_ripple_button.xml
in your folder
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true">
<shape android:shape="rectangle">
<solid android:color="#color/black_12" />
<stroke android:width="0.2dp" android:color="#color/white" />
<corners android:radius="3dp" />
</shape>
</item>
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#color/black_12" />
<stroke android:width="0.2dp" android:color="#color/white" />
<corners android:radius="3dp" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<solid android:color="#android:color/transparent" />
<stroke android:width="0.2dp" android:color="#color/white" />
<corners android:radius="3dp" />
</shape>
</item>
</selector>

Ripples on a shape with a transparent background

I am using the following shape in my app
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape android:shape="oval" >
<solid android:color="#color/primary_light"/>
<size android:height="80dp" android:width="80dp"/>
</shape>
</item>
<item android:state_focused="true">
<shape android:shape="oval">
<stroke android:color="#color/primary_light" android:width="5dp"/>
<size android:height="80dp" android:width="80dp"/>
</shape>
</item>
<item>
<shape android:shape="oval">
<solid android:color="#android:color/transparent"/>
<size android:height="80dp" android:width="80dp"/>
</shape>
</item>
</selector>
It is there in my drawable folder. Now I am updating my app to Lollipop and I wish to give a ripple feedback on the circular button that I used.
So in drawable-v21 folder I changed it to a ripple selector:
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#color/primary_light">
<item>
<selector>
<item android:state_focused="true">
<shape android:shape="oval">
<stroke android:color="#color/primary_light" android:width="5dp"/>
<size android:height="80dp" android:width="80dp"/>
</shape>
</item>
<item android:id="#android:id/mask">
<shape android:shape="oval">
<solid android:color="#android:color/transparent"/>
<size android:height="80dp" android:width="80dp"/>
</shape>
</item>
</selector>
</item>
</ripple>
But unfortunately the ripple effect is not generated by using the above drawable in Lollipop. Is it because of the <solid android:color="#android:color/transparent"/>?
Can anyone show me where I have go wrong? Thanks
After a few trial and errors it seems I have misunderstood the hierarchy of the selector and item.
The following works perfectly.
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#color/primary_light">
<item android:id="#android:id/mask">
<shape android:shape="oval">
<solid android:color="#android:color/white"/>
<size android:height="80dp" android:width="80dp"/>
</shape>
</item>
</ripple>
This works with a transparent background:
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:colorControlHighlight"
android:exitFadeDuration="#android:integer/config_shortAnimTime">
<!-- Use this to define the shape of the ripple effect (rectangle, oval, ring or line). The color specified here isn't used anyway -->
<item android:id="#android:id/mask">
<shape android:shape="rectangle">
<corners android:radius="3dp"/>
<!-- This color is needed to be set - doesn't affect anything -->
<solid android:color="#android:color/white"/>
</shape>
</item>
<!-- This is the background for your button -->
<item>
<!-- Use the shape you want here -->
<shape android:shape="rectangle">
<!-- Use the solid tag to define the background color you want -->
<solid android:color="#android:color/transparent"/>
</shape>
</item>
</ripple>
This was modified a bit from this answer: Transparent ripple
You can have a ripple effect on transparent or semi-transparent background when the ripple mask is specified. The ripple mask is a Drawable but only the alpha value is used setting the ripple alpha per pixel. Here is an example for creating programmatically.
var background = new android.graphics.drawable.GradientDrawable();
background.setShape(android.graphics.drawable.GradientDrawable.RECTANGLE);
background.setColor(0x77FFFFFF); // semi-transparent background
background.setCornerRadius(10);
background.setStroke(3, 0xFF1144FF); // border color
var mask = new android.graphics.drawable.GradientDrawable();
mask.setShape(android.graphics.drawable.GradientDrawable.RECTANGLE);
mask.setColor(0xFF000000); // the color is irrelevant here, only the alpha
mask.setCornerRadius(5); // you probably want the same corner as the background
var rippleColorLst = android.content.res.ColorStateList.valueOf(
android.graphics.Color.argb(255,50,150,255)
);
// aaaand
var ripple = new android.graphics.drawable.RippleDrawable(rippleColorLst,background,mask);
yourButton.setBackground(ripple);
(Sorry for the JavaScript/NativeScript code, probably one can understand it)
Following code works for custom shape with ripple effect for transparent button -
main_activity_buttons_ripple_with_background.xml
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:color="#888888"
tools:targetApi="lollipop">
<!-- Use this to define the shape of the ripple effect (rectangle, oval, ring or line). The color specified here isn't used anyway -->
<item android:id="#android:id/mask">
<shape android:shape="rectangle">
<corners android:radius="25dp" />
<!-- This color is needed to be set - doesn't affect anything -->
<solid android:color="#000" />
</shape>
</item>
<!-- This is the background for your button -->
<item>
<!-- Use the shape you want here -->
<shape android:shape="rectangle">
<!-- Use the solid tag to define the background color you want -->
<solid android:color="#android:color/transparent" />
<stroke
android:width="1dp"
android:color="#FFF" />
<corners android:radius="25dp" />
</shape>
</item>
</ripple>
styles.xml
<style name="MainActivityButtonsStyle">
<item name="android:background">#drawable/main_activity_buttons_ripple_with_background</item>
<item name="android:textAllCaps">false</item>
<item name="android:layout_margin">15dp</item>
<item name="android:paddingLeft">20dp</item>
<item name="android:paddingRight">20dp</item>
<item name="android:layout_centerHorizontal">true</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">50dp</item>
<item name="android:textColor">#FFF</item>
<item name="android:textSize">18sp</item>
</style>
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<Button
android:layout_alignParentTop="true"
style="#style/MainActivityButtonsStyle"
android:text="#string/button_search_by_id" />
</RelativeLayout>
</LinearLayout>

Selector, Layer-list and shape/bitmap in the same xml

I have this code in an xml inside the drawable folder:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<layer-list>
<item>
<shape android:shape="rectangle">
<size android:width="90dp" android:height="90dp" />
<solid android:color="#9933CC" />
</shape>
</item>
<item>
<bitmap android:gravity="center" android:src="#drawable/main_achievements_synthesis" />
</item>
</layer-list>
</item>
<item>
<layer-list>
<item>
<shape android:shape="rectangle">
<size android:width="90dp" android:height="90dp" />
<solid android:color="#AA66CC" />
</shape>
</item>
<item>
<bitmap android:gravity="center" android:src="#drawable/main_achievements_synthesis" />
</item>
</layer-list>
</item>
</selector>
I use it to have an image with 2 states (to use as a button). Everything works as expected on emulator and devices.
I know that I can create different drawable xml and make a reference to achieve the same result.
I just switched to Android Studio and it shows me this mesage: Element XXX is not allowed here. It warns me about Layer-list and all the tags inside it. However, as I said, this code works just fine.
Should I switch my code into separated XMLs (knowing that I will only use them once) or is an "error" in the Android Studio's Inspector Code?
NOTE: I think my code can be optimized, but I haven't figured out how yet.
This is an error in Android Studio's linting tool. Please file a bug at https://code.google.com/p/android/issues/list.
Also, you can optimize a little by using:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<selector>
<item android:state_pressed="true">
<shape android:shape="rectangle">
<size android:width="90dp" android:height="90dp" />
<solid android:color="#9933CC" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<size android:width="90dp" android:height="90dp" />
<solid android:color="#AA66CC" />
</shape>
</item>
</selector>
</item>
<item>
<bitmap
android:gravity="center"
android:src="#drawable/main_achievements_synthesis" />
</item>
</layer-list>

Android Using layer-list for button selector

How do we use a layer-list as a drawable for a button.
I have a button:
<item android:state_pressed="true">
<shape>
<gradient android:endColor="#color/white"
android:startColor="#color/grey_blue_light" android:angle="90" />
<stroke android:width="1dp" android:color="#color/aqua_blue" />
<corners android:radius="3dp" />
<padding android:left="10dp" android:top="10dp"
android:right="10dp" android:bottom="10dp" />
</shape>
</item>
<item android:state_focused="true">
</item>
<item>
</item>
Now I need a layer-list to be used as a shape when say button state is pressed:
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="oval">
<solid android:color="#color/aqua_blue" />
</shape>
</item>
<item android:top="1dp" android:left="1dp" android:right="1dp" android:bottom="1dp">
<shape android:shape="oval">
<solid android:color="#color/aqua_blue" />
</shape>
</item>
How do we use this layer list in the button selector?
Step-1
create three different layer_list xml under drawable folder for three different state of button. example the name of those xml is layer1.xml, layer2.xml, layer3.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android"
>
<item>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
>
<gradient
android:angle="270"
android:startColor="#0000ff"
android:endColor="#0000dd"
android:type="linear"
/>
</shape>
</item>
</layer-list>
Step-2
create a selector xml named as btn_background.xml and pass the layer_list xml in drawable attribute
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="#drawable/layer1">
</item>
<item android:state_focused="true" android:drawable="#drawable/layer2">
</item>
<item android:drawable="#drawable/layer3">
</item>
</selector>
step-3
Set the selector xml as background of the button android:background="#drawable/btn_background"
Mygod's answer is actually better than the accepted one. It works and only needs one xml file. Here's something similar that I did. It sets the background under a drawable with transparencies. The same could be done with a background and a shape:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<layer-list>
<item android:drawable="#color/Black"/>
<item android:drawable="#drawable/notes_white"/>
</layer-list>
</item>
<item>
<layer-list>
<item android:drawable="#color/White"/>
<item android:drawable="#drawable/notes_black"/>
</layer-list>
</item>
</selector>
Just replace the shape tag with your layer-list and everything will work fine.

Categories

Resources