35

I have the following code for my ripple:

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

        <shape android:shape="oval">
            <solid android:color="?android:colorAccent"/>
        </shape>
    </item>
</ripple>

Now I want to give the user the possibility to choose own colors, so I need to create the ripple programmatically.
I found this and I think this is the right way to do it, but I don't know how to handle with this.

The ripple will be used here:

<ImageButton
    android:id="@+id/add_button"
    android:layout_width="@dimen/diameter"
    android:layout_height="@dimen/diameter"
    android:layout_gravity="end|bottom"
    android:layout_marginBottom="@dimen/add_button_margin"
    android:layout_marginEnd="@dimen/add_button_margin"
    android:layout_alignParentBottom="true"
    android:layout_alignParentEnd="true"
    android:src="@drawable/ic_action_add_person"
    android:tint="@android:color/white"
    android:background="@drawable/oval_ripple"
    android:elevation="@dimen/elevation_low"
    android:stateListAnimator="@anim/button_elevation"
    android:contentDescription="Neuer Spieler" />

I need to set the background to a RippleDrawable like this:

addButton.setBackground(ripple);
JJD
  • 50,076
  • 60
  • 203
  • 339
ich5003
  • 838
  • 1
  • 9
  • 24

4 Answers4

68

This is how I was able to achieve this.

Note that this is Api 21+ only so you will have to fallback to a normal Drawable if you support lower versions.

public static RippleDrawable getPressedColorRippleDrawable(int normalColor, int pressedColor)
{
    return new RippleDrawable(getPressedColorSelector(normalColor, pressedColor), getColorDrawableFromColor(normalColor), null);
}

public static ColorStateList getPressedColorSelector(int normalColor, int pressedColor)
{
    return new ColorStateList(
        new int[][]
            {
                new int[]{android.R.attr.state_pressed},
                new int[]{android.R.attr.state_focused},
                new int[]{android.R.attr.state_activated},
                new int[]{}
            },
        new int[]
            {
                pressedColor,
                pressedColor,
                pressedColor,
                normalColor
            }
    );
}

public static ColorDrawable getColorDrawableFromColor(int color)
{
    return new ColorDrawable(color);
}

Edit: I tinkered with this some more and discovered that the ColorStateList doesn't need to be nearly as complex as the above solution. I have simplified it to the below snippet. (Everything else in the above code block is the same. I only changed the ColorStateList creation.) I will leave the above block as the original, in case this method doesn't work for someone.

new ColorStateList(
    new int[][]
        {
            new int[]{}
        },
    new int[]
        {
            pressedColor
        }
);
kdenney
  • 18,343
  • 4
  • 31
  • 27
  • 6
    Thank you VERY much for you answer :) (Can't give you an upvote, not enough reputations). – ich5003 Feb 07 '15 at 11:50
  • 1
    Sure thing; it was a pain to figure out so I'm happy to share. :) Did it work for you? I tinkered with it some more and learned that I could greatly simplify the ColorStateList and get the same result so I edited my answer to include that as well. – kdenney Feb 09 '15 at 17:00
  • 20
    You can also do it using `ColorStateList.valueOf(rippleColor);` – milosmns Apr 22 '15 at 17:36
  • 2
    Please note that this solution adds a mask. So if you have, let's say a blue button and set the pressedColor as yellow, then the ripple will be purple-ish. – Joaquin Iurchuk Jan 07 '20 at 22:30
  • @JoaquinIurchuk do you how to fix it ? `rippleDrawable.setId(index, android.R.id.mask)` didn't work. – Levon Petrosyan Jul 13 '20 at 07:04
25
public static Drawable getAdaptiveRippleDrawable(
    int normalColor, int pressedColor) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        return new RippleDrawable(ColorStateList.valueOf(pressedColor),
                null, getRippleMask(normalColor));
    } else {
        return getStateListDrawable(normalColor, pressedColor);
    }
}

private static Drawable getRippleMask(int color) {
    float[] outerRadii = new float[8];
    // 3 is radius of final ripple, 
    // instead of 3 you can give required final radius
    Arrays.fill(outerRadii, 3);

    RoundRectShape r = new RoundRectShape(outerRadii, null, null);
    ShapeDrawable shapeDrawable = new ShapeDrawable(r);
    shapeDrawable.getPaint().setColor(color);
    return shapeDrawable;
}

public static StateListDrawable getStateListDrawable(
    int normalColor, int pressedColor) {
    StateListDrawable states = new StateListDrawable();
    states.addState(new int[]{android.R.attr.state_pressed}, 
        new ColorDrawable(pressedColor));
    states.addState(new int[]{android.R.attr.state_focused}, 
        new ColorDrawable(pressedColor));
    states.addState(new int[]{android.R.attr.state_activated}, 
        new ColorDrawable(pressedColor));
    states.addState(new int[]{}, 
        new ColorDrawable(normalColor));
    return states;
}

You can get the drawable and apply to any view using view.setDrawable.
For Lollipop+ devices you will get ripple else it will change the color of view.

JJD
  • 50,076
  • 60
  • 203
  • 339
Ashok Varma
  • 3,489
  • 3
  • 28
  • 43
  • This is nice. A drawback, I think, is that it might loose the rounded corners when applied on a pre-lollipop device, because the `ColorDrawable`s in the `StateListDrawable` have no shape. We could provide colored `ShapeDrawable` instead to keep the shape. But maybe this would start to get a little heavy. In that case, yet another solution would be to create a custom "ColorStateDrawable" with a "setColor" method avoiding you to re-instantiate all the objects needed for a StateListDrawable. – John Sep 26 '15 at 11:21
  • Found something close here : https://github.com/gabrielemariotti/colorpickercollection/blob/master/ColorPicker/src/it/gmariotti/android/example/colorpicker/calendarstock/ColorStateDrawable.java – John Sep 26 '15 at 11:23
  • 1
    "normalColor" does not work! it's always transparent – ucMedia May 31 '22 at 12:15
2

Basically, you need to create a new RippleDrawable object. For pre-Lollipop devices, you want a StateListDrawable (as others already suggested). I've written a somewhat resourceful GIST with a bunch of useful methods related to Drawables and coloring: https://gist.github.com/milosmns/6566ca9e3b756d922aa5

Most likely you will want to use #getBackgroundDrawable() from that singleton.

milosmns
  • 3,595
  • 4
  • 36
  • 48
  • It's super huge, I'm not sure it is relevant to post a chain of ten 50-line methods here. It's my gist so it's being kept alive intentionally :) – milosmns Jan 30 '19 at 10:37
0

You can assign it as foreground, in Kotlin its:

view.foreground = ContextCompat.getDrawable(context, R.drawable.rippleRes)