How to trigger ripple effect on Android Lollipop, in specific location within the view, without triggering touches events? How to trigger ripple effect on Android Lollipop, in specific location within the view, without triggering touches events? android android

How to trigger ripple effect on Android Lollipop, in specific location within the view, without triggering touches events?


Yes there is! In order to trigger a ripple programatically you have to set the state of the RippleDrawable with setState(). Calling setVisible() does NOT work!


The Solution

To show the ripple you have to set the state to pressed and enabled at the same time:

rippleDrawable.setState(new int[] { android.R.attr.state_pressed, android.R.attr.state_enabled });

The ripple will be shown as long as those states are set. When you want to hide the ripple again set the state to an empty int[]:

rippleDrawable.setState(new int[] {  });

You can set the point from which the ripple emanates by calling setHotspot().


How it works

I have debugged a lot and studied the source code of RippleDrawable up and down until I realised that the ripple is actually triggered in onStateChange(). Calling setVisible() has no effect and never causes any ripple to actually appear.

The relevant part of the source code of RippleDrawable is this:

@Overrideprotected boolean onStateChange(int[] stateSet) {    final boolean changed = super.onStateChange(stateSet);    boolean enabled = false;    boolean pressed = false;    boolean focused = false;    for (int state : stateSet) {        if (state == R.attr.state_enabled) {            enabled = true;        }        if (state == R.attr.state_focused) {            focused = true;        }        if (state == R.attr.state_pressed) {            pressed = true;        }    }    setRippleActive(enabled && pressed);    setBackgroundActive(focused || (enabled && pressed));    return changed;}

As you can see if both the enabled and pressed attribute are set then both the ripple and background will be activated and the ripple will be displayed. Additionally as long as you set the focused state the background will be activated as well. With this you can trigger the ripple and have the background change color independently.

If you are interested you can view the entire source code of RippleDrawable here.


I incorporated/combined the answers from @Xaver Kapeller and @Nikola Despotoski above:

protected void forceRippleAnimation(View view){    Drawable background = view.getBackground();    if(Build.VERSION.SDK_INT >= 21 && background instanceof RippleDrawable)    {        final RippleDrawable rippleDrawable = (RippleDrawable) background;        rippleDrawable.setState(new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled});        Handler handler = new Handler();        handler.postDelayed(new Runnable()        {            @Override public void run()            {                rippleDrawable.setState(new int[]{});            }        }, 200);    }}

To programmatically force a ripple effect on command, simply call forceRippleAnimation(), passing the View you want to ripple as a parameter.


Here is combination of Nikola's setHotSpot() and https://stackoverflow.com/a/25415471/1474113

private void forceRipple(View view, int x, int y) {    Drawable background = view.getBackground();    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && background instanceof RippleDrawable) {        background.setHotspot(x, y);    }    view.setPressed(true);    // For a quick ripple, you can immediately set false.    view.setPressed(false);}