Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle the Ripple effect on 9-patch and CardView, and have control over the states of the selector?

Tags:

Background

I wish to add a simple ripple effect for listView items from Android Lollipop and above.

First I'd like to set it for simple rows, and then to 9-patch rows and even CardView.

The problem

I was sure this one is going to be very easy, as it doesn't even require me to define the normal selector. I failed to do so even for simple rows. For some reason, the ripple effect goes beyond the row's boundaries:

enter image description here

Not only that, but on some cases, the background of the item gets stuck on the color I've set it to be.

The code

This is what I've tried:

MainActivity.java

public class MainActivity extends ActionBarActivity {      @Override     protected void onCreate(final Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);          final ListView listView = (ListView) findViewById(android.R.id.list);          final LayoutInflater inflater = LayoutInflater.from(this);         listView.setAdapter(new BaseAdapter() {              @Override             public View getView(final int position, final View convertView, final ViewGroup parent) {                 View rootView = convertView;                 if (rootView == null) {                     rootView = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);                     ((TextView) rootView.findViewById(android.R.id.text1)).setText("Test");                 }                 return rootView;             }              @Override             public long getItemId(final int position) {                 return 0;             }              @Override             public Object getItem(final int position) {                 return null;             }              @Override             public int getCount() {                 return 2;             }         });     }  } 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?> <ListView xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:tools="http://schemas.android.com/tools"     android:id="@android:id/list"     android:layout_width="match_parent"     android:layout_height="match_parent"     android:cacheColorHint="@android:color/transparent"     android:divider="@null"     android:dividerHeight="0px"     android:fadeScrollbars="false"     android:fastScrollEnabled="true"     android:listSelector="@drawable/listview_selector"     android:scrollingCache="false"     android:verticalScrollbarPosition="right" /> 

res/drawable-v21/listview_selector.xml (I have a normal selector for other Android versions)

<?xml version="1.0" encoding="utf-8"?> <ripple xmlns:android="http://schemas.android.com/apk/res/android" /> 

What I've tried

Aside from the simple code above, I've also tried setting the selector per item's background property, instead of using "listSelector" on the ListView, but it didn't help.

Another thing I've tried is to set the foreground of the items, but it also had the same result.

The question

How do I fix this issue? Why does it occur? What did I do wrong?

How do I go further, to support 9-patch and even CardView ?

Also, how can I set a state for the new background, like being checked/selected ?


Update: The drawing outside of the view is fixed using something like this:

<ripple xmlns:android="http://schemas.android.com/apk/res/android"     android:color="?attr/colorControlHighlight" >      <item android:id="@android:id/mask">         <color android:color="@color/listview_pressed" />     </item>  </ripple> 

Still, it has the issue of background being stuck, and I can't find how to handle the rest of the missing features (9-patch, cardView,...) .

I think the color-being-stuck has something to do with using it as the foreground of views.


EDIT: I see some people don't understand what the question here is about.

It's about handling the new ripple effect, while still having the older selector/CardView.

For example, here's a selector-drawble:

<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android">     <item android:drawable="..." android:state_selected="true"/>     <item android:drawable="..." android:state_activated="true"/>     <item android:drawable="..." android:state_focused="true" android:state_pressed="true"/>     <item android:drawable="..." android:state_pressed="true"/>     <item android:drawable="..."/> </selector> 

This can be used as a list-selector or a background of a single view.

However, I can't find how to use it along with the ripple drawable.

I know that the ripple already takes care of some of the states, but for some, it doesn't. Plus, I can't find out how to make it handle 9-patch and CardView.

I hope now it's easier to understand the problem I have.


About the issue of the color of the ripple gets "stucked", I think it's because of how I made the layout. I wanted a layout which can be checked (when I decide to) and also have the effect of clicking, so this is what I made (based on this website and another that I can't find) :

public class CheckableRelativeLayout extends RelativeLayout implements Checkable {     private boolean mChecked;     private static final String TAG = CheckableRelativeLayout.class.getCanonicalName();     private static final int[] CHECKED_STATE_SET = { android.R.attr.state_checked };     private Drawable mForegroundDrawable;      public CheckableRelativeLayout(final Context context) {         this(context, null, 0);     }      public CheckableRelativeLayout(final Context context, final AttributeSet attrs) {         this(context, attrs, 0);     }      public CheckableRelativeLayout(final Context context, final AttributeSet attrs, final int defStyle) {         super(context, attrs, defStyle);         final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CheckableRelativeLayout, defStyle,                 0);         setForeground(a.getDrawable(R.styleable.CheckableRelativeLayout_foreground));         a.recycle();     }      public void setForeground(final Drawable drawable) {         this.mForegroundDrawable = drawable;     }      public Drawable getForeground() {         return this.mForegroundDrawable;     }      @Override     protected int[] onCreateDrawableState(final int extraSpace) {         final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);         if (isChecked()) {             mergeDrawableStates(drawableState, CHECKED_STATE_SET);         }         return drawableState;     }      @Override     protected void drawableStateChanged() {         super.drawableStateChanged();         final Drawable drawable = getBackground();         boolean needRedraw = false;         final int[] myDrawableState = getDrawableState();         if (drawable != null) {             drawable.setState(myDrawableState);             needRedraw = true;         }         if (mForegroundDrawable != null) {             mForegroundDrawable.setState(myDrawableState);             needRedraw = true;         }         if (needRedraw)             invalidate();     }      @Override     protected void onSizeChanged(final int width, final int height, final int oldwidth, final int oldheight) {         super.onSizeChanged(width, height, oldwidth, oldheight);         if (mForegroundDrawable != null)             mForegroundDrawable.setBounds(0, 0, width, height);     }      @Override     protected void dispatchDraw(final Canvas canvas) {         super.dispatchDraw(canvas);         if (mForegroundDrawable != null)             mForegroundDrawable.draw(canvas);     }      @Override     public boolean isChecked() {         return mChecked;     }      @Override     public void setChecked(final boolean checked) {         setChecked(checked, true);     }      public void setChecked(final boolean checked, final boolean alsoRecursively) {         mChecked = checked;         refreshDrawableState();         if (alsoRecursively)             ViewUtil.setCheckedRecursively(this, checked);     }      @Override     public void toggle() {         setChecked(!mChecked);     }      @Override     public Parcelable onSaveInstanceState() {         // Force our ancestor class to save its state         final Parcelable superState = super.onSaveInstanceState();         final SavedState savedState = new SavedState(superState);         savedState.checked = isChecked();         return savedState;     }      @TargetApi(Build.VERSION_CODES.LOLLIPOP)     @Override     public void drawableHotspotChanged(final float x, final float y) {         super.drawableHotspotChanged(x, y);         if (mForegroundDrawable != null) {             mForegroundDrawable.setHotspot(x, y);         }     }      @Override     public void onRestoreInstanceState(final Parcelable state) {         final SavedState savedState = (SavedState) state;         super.onRestoreInstanceState(savedState.getSuperState());         setChecked(savedState.checked);         requestLayout();     }      // /////////////     // SavedState //     // /////////////      private static class SavedState extends BaseSavedState {         boolean checked;          SavedState(final Parcelable superState) {             super(superState);         }          private SavedState(final Parcel in) {             super(in);             checked = (Boolean) in.readValue(null);         }          @Override         public void writeToParcel(final Parcel out, final int flags) {             super.writeToParcel(out, flags);             out.writeValue(checked);         }          @Override         public String toString() {             return TAG + ".SavedState{" + Integer.toHexString(System.identityHashCode(this)) + " checked=" + checked                     + "}";         }          @SuppressWarnings("unused")         public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {             @Override             public SavedState createFromParcel(final Parcel in) {                 return new SavedState(in);             }              @Override             public SavedState[] newArray(final int size) {                 return new SavedState[size];             }         };     } } 

EDIT: the fix was to add the next lines for the layout I've made:

@SuppressLint("ClickableViewAccessibility") @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public boolean onTouchEvent(final MotionEvent e) {     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && //             e.getActionMasked() == MotionEvent.ACTION_DOWN && //             mForeground != null)         mForeground.setHotspot(e.getX(), e.getY());     return super.onTouchEvent(e); } 
like image 970
android developer Avatar asked Nov 27 '14 12:11

android developer


People also ask

How do I add ripple effect to CardView?

Adding Ripple Effect To enable this behavior, add the following attributes to your CardView . Using the android:foreground="? android:attr/selectableItemBackground" property on a CardView enables the ripple effect to originate from the touch origin.

What is ripple effect in Android?

The touch feedback in Android is a must whenever the user clicks on the item or button ripple effect when clicking on the same, gives confidence to the user that the button has been clicked so that they can wait for the next interaction of the app.


2 Answers

RippleDrawable extends LayerDrawable. Touch feedback drawable may contain multiple child layers, including a special mask layer that is not drawn to the screen. A single layer may be set as the mask by specifying its android:id value as mask. The second layer can be StateListDrawable.

For example, here is our StateListDrawable resource with name item_selectable.xml:

<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android">     <item android:drawable="..." android:state_selected="true"/>     <item android:drawable="..." android:state_activated="true"/>     <item android:drawable="..." android:state_focused="true" android:state_pressed="true"/>     <item android:drawable="..." android:state_pressed="true"/>     <item android:drawable="..."/> </selector> 

To achieve ripple effect along with selectors we can set drawable above as a layer of RippleDrawable with name list_selector_ripple.xml:

<?xml version="1.0" encoding="utf-8"?> <ripple xmlns:android="http://schemas.android.com/apk/res/android"         android:color="@color/colorControlHighlight">     <item android:id="@android:id/mask">         <color android:color="@android:color/white"/>     </item>     <item android:drawable="@drawable/item_selectable"/> </ripple> 

UPD:

1) To use this drawable with CardView just set it as android:foreground, like this:

<android.support.v7.widget.CardView     ...     android:foreground="@drawable/list_selector_ripple"     /> 

2) To make the ripple effect works within the bounds of the 9-patch we should set this 9-patch drawable as mask of ripple drawable (list_selector_ripple_nine_patch.xml):

<?xml version="1.0" encoding="utf-8"?> <ripple xmlns:android="http://schemas.android.com/apk/res/android"         android:color="@color/colorControlHighlight">     <item android:id="@android:id/mask" android:drawable="@drawable/your_nine_patch" />     <item android:drawable="@drawable/your_nine_patch" /> </ripple> 

Then set the background of view:

<LinearLayout     ...     android:background="@drawable/list_selector_ripple_nine_patch"     /> 
like image 196
erakitin Avatar answered Oct 24 '22 10:10

erakitin


Simple way to create a ripple make a xml in drawable-v21 folder and use this code for xml.

android:backgroung="@drawable/ripple_xyz" 

And if, Through java / dynamically use.

View.setBackgroundResource(R.drawable.ripple_xyz); 

Here is the ripple_xyz.xml.

<?xml version="1.0" encoding="utf-8"?> <ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="#228B22" > // ^ THIS IS THE COLOR FOR RIPPLE <item>     <shape         android:shape="rectangle"         android:useLevel="false" >         <solid android:color="#CCFFFFFF" /> // ^ THIS IS THE COLOR FOR BACK GROUND     </shape> </item> 

like image 28
Hemant Shori Avatar answered Oct 24 '22 11:10

Hemant Shori