How to achieve ripple animation using support library? How to achieve ripple animation using support library? android android

How to achieve ripple animation using support library?


Basic ripple setup

  • Ripples contained within the view.
    android:background="?selectableItemBackground"

  • Ripples that extend beyond the view's bounds:
    android:background="?selectableItemBackgroundBorderless"

    Have a look here for resolving ?(attr) xml references in Java code.

Support Library

  • Using ?attr: (or the ? shorthand) instead of ?android:attr references the support library, so is available back to API 7.

Ripples with images/backgrounds

  • To have a image or background and overlaying ripple the easiest solution is to wrap the View in a FrameLayout with the ripple set with setForeground() or setBackground().

Honestly there is no clean way of doing this otherwise.


I formerly voted to close this question as off-topic but actually I changed my mind as this is quite nice visual effect which, unfortunately, is not yet part of support library. It will most likely show up in future update, but there's no time frame announced.

Luckily there are few custom implementations already available:

including Materlial themed widget sets compatible with older versions of Android:

so you can try one of these or google for other "material widgets" or so...


I made a simple class that makes ripple buttons, i never needed it in the end so its not the best, But here it is:

import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.os.Handler;import android.support.annotation.NonNull;import android.util.AttributeSet;import android.view.MotionEvent;import android.widget.Button;public class RippleView extends Button{    private float duration = 250;    private float speed = 1;    private float radius = 0;    private Paint paint = new Paint();    private float endRadius = 0;    private float rippleX = 0;    private float rippleY = 0;    private int width = 0;    private int height = 0;    private OnClickListener clickListener = null;    private Handler handler;    private int touchAction;    private RippleView thisRippleView = this;    public RippleView(Context context)    {        this(context, null, 0);    }    public RippleView(Context context, AttributeSet attrs)    {        this(context, attrs, 0);    }    public RippleView(Context context, AttributeSet attrs, int defStyleAttr)    {        super(context, attrs, defStyleAttr);        init();    }    private void init()    {        if (isInEditMode())            return;        handler = new Handler();        paint.setStyle(Paint.Style.FILL);        paint.setColor(Color.WHITE);        paint.setAntiAlias(true);    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh)    {        super.onSizeChanged(w, h, oldw, oldh);        width = w;        height = h;    }    @Override    protected void onDraw(@NonNull Canvas canvas)    {        super.onDraw(canvas);        if(radius > 0 && radius < endRadius)        {            canvas.drawCircle(rippleX, rippleY, radius, paint);            if(touchAction == MotionEvent.ACTION_UP)                invalidate();        }    }    @Override    public boolean onTouchEvent(@NonNull MotionEvent event)    {        rippleX = event.getX();        rippleY = event.getY();        switch(event.getAction())        {            case MotionEvent.ACTION_UP:            {                getParent().requestDisallowInterceptTouchEvent(false);                touchAction = MotionEvent.ACTION_UP;                radius = 1;                endRadius = Math.max(Math.max(Math.max(width - rippleX, rippleX), rippleY), height - rippleY);                speed = endRadius / duration * 10;                handler.postDelayed(new Runnable()                {                    @Override                    public void run()                    {                        if(radius < endRadius)                        {                            radius += speed;                            paint.setAlpha(90 - (int) (radius / endRadius * 90));                            handler.postDelayed(this, 1);                        }                        else                        {                            clickListener.onClick(thisRippleView);                        }                    }                }, 10);                invalidate();                break;            }            case MotionEvent.ACTION_CANCEL:            {                getParent().requestDisallowInterceptTouchEvent(false);                touchAction = MotionEvent.ACTION_CANCEL;                radius = 0;                invalidate();                break;            }            case MotionEvent.ACTION_DOWN:            {                getParent().requestDisallowInterceptTouchEvent(true);                touchAction = MotionEvent.ACTION_UP;                endRadius = Math.max(Math.max(Math.max(width - rippleX, rippleX), rippleY), height - rippleY);                paint.setAlpha(90);                radius = endRadius/4;                invalidate();                return true;            }            case MotionEvent.ACTION_MOVE:            {                if(rippleX < 0 || rippleX > width || rippleY < 0 || rippleY > height)                {                    getParent().requestDisallowInterceptTouchEvent(false);                    touchAction = MotionEvent.ACTION_CANCEL;                    radius = 0;                    invalidate();                    break;                }                else                {                    touchAction = MotionEvent.ACTION_MOVE;                    invalidate();                    return true;                }            }        }        return false;    }    @Override    public void setOnClickListener(OnClickListener l)    {        clickListener = l;    }}

EDIT

Since many people are looking for something like this i made a class that can make other views have the ripple effect:

import android.content.Context;import android.graphics.Canvas;import android.graphics.Paint;import android.os.Handler;import android.support.annotation.NonNull;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;import android.widget.FrameLayout;public class RippleViewCreator extends FrameLayout{    private float duration = 150;    private int frameRate = 15;    private float speed = 1;    private float radius = 0;    private Paint paint = new Paint();    private float endRadius = 0;    private float rippleX = 0;    private float rippleY = 0;    private int width = 0;    private int height = 0;    private Handler handler = new Handler();    private int touchAction;    public RippleViewCreator(Context context)    {        this(context, null, 0);    }    public RippleViewCreator(Context context, AttributeSet attrs)    {        this(context, attrs, 0);    }    public RippleViewCreator(Context context, AttributeSet attrs, int defStyleAttr)    {        super(context, attrs, defStyleAttr);        init();    }    private void init()    {        if (isInEditMode())            return;        paint.setStyle(Paint.Style.FILL);        paint.setColor(getResources().getColor(R.color.control_highlight_color));        paint.setAntiAlias(true);        setWillNotDraw(true);        setDrawingCacheEnabled(true);        setClickable(true);    }    public static void addRippleToView(View v)    {        ViewGroup parent = (ViewGroup)v.getParent();        int index = -1;        if(parent != null)        {            index = parent.indexOfChild(v);            parent.removeView(v);        }        RippleViewCreator rippleViewCreator = new RippleViewCreator(v.getContext());        rippleViewCreator.setLayoutParams(v.getLayoutParams());        if(index == -1)            parent.addView(rippleViewCreator, index);        else            parent.addView(rippleViewCreator);        rippleViewCreator.addView(v);    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh)    {        super.onSizeChanged(w, h, oldw, oldh);        width = w;        height = h;    }    @Override    protected void dispatchDraw(@NonNull Canvas canvas)    {        super.dispatchDraw(canvas);        if(radius > 0 && radius < endRadius)        {            canvas.drawCircle(rippleX, rippleY, radius, paint);            if(touchAction == MotionEvent.ACTION_UP)                invalidate();        }    }    @Override    public boolean onInterceptTouchEvent(MotionEvent event)    {        return true;    }    @Override    public boolean onTouchEvent(@NonNull MotionEvent event)    {        rippleX = event.getX();        rippleY = event.getY();        touchAction = event.getAction();        switch(event.getAction())        {            case MotionEvent.ACTION_UP:            {                getParent().requestDisallowInterceptTouchEvent(false);                radius = 1;                endRadius = Math.max(Math.max(Math.max(width - rippleX, rippleX), rippleY), height - rippleY);                speed = endRadius / duration * frameRate;                handler.postDelayed(new Runnable()                {                    @Override                    public void run()                    {                        if(radius < endRadius)                        {                            radius += speed;                            paint.setAlpha(90 - (int) (radius / endRadius * 90));                            handler.postDelayed(this, frameRate);                        }                        else if(getChildAt(0) != null)                        {                            getChildAt(0).performClick();                        }                    }                }, frameRate);                break;            }            case MotionEvent.ACTION_CANCEL:            {                getParent().requestDisallowInterceptTouchEvent(false);                break;            }            case MotionEvent.ACTION_DOWN:            {                getParent().requestDisallowInterceptTouchEvent(true);                endRadius = Math.max(Math.max(Math.max(width - rippleX, rippleX), rippleY), height - rippleY);                paint.setAlpha(90);                radius = endRadius/3;                invalidate();                return true;            }            case MotionEvent.ACTION_MOVE:            {                if(rippleX < 0 || rippleX > width || rippleY < 0 || rippleY > height)                {                    getParent().requestDisallowInterceptTouchEvent(false);                    touchAction = MotionEvent.ACTION_CANCEL;                    break;                }                else                {                    invalidate();                    return true;                }            }        }        invalidate();        return false;    }    @Override    public final void addView(@NonNull View child, int index, ViewGroup.LayoutParams params)    {        //limit one view        if (getChildCount() > 0)        {            throw new IllegalStateException(this.getClass().toString()+" can only have one child.");        }        super.addView(child, index, params);    }}