Get ripple effect on transparent custom button view

2.7k views Asked by At

I am making a custom button view that has a gradient border, rounded corners and a transparant background. I am setting the background of the button to the drawable generated by the following code:

protected Drawable getBackgroundDrawable() {
    GradientDrawable backgroundDrawable = new GradientDrawable(
            mOrientation,
            mGradientColors);
    backgroundDrawable.setCornerRadius(getHeight() / 2);
    backgroundDrawable.setShape(GradientDrawable.RECTANGLE);

    if (!mFilled) {
        Bitmap background = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
        Canvas backgroundCanvas = new Canvas(background);
        backgroundCanvas.drawARGB(0, 0, 0, 0);
        backgroundDrawable.setBounds(0, 0, getWidth(), getHeight());
        backgroundDrawable.draw(backgroundCanvas);

        Paint rectPaint = new Paint();
        rectPaint.setAntiAlias(true);
        rectPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        backgroundCanvas.drawRoundRect(new RectF(mStroke, mStroke,
                        getWidth() - mStroke,
                        getHeight() - mStroke),
                getHeight() / 2,
                getHeight() / 2,
                rectPaint);

        return new BitmapDrawable(getResources(), background);
    } else {
        return backgroundDrawable;
    }
}

The only problem is, when you click the button now, you have no feedback. How can I add a ripple effect at the back of the button when I already use the generated drawable as a background? Do I have to do something with a LayerDrawable?

My button looks like this:

enter image description here

What I really want to achieve

Create a Drawable programmatically that has the same result as this XML drawable, where the item in the ripple element is my generated Drawable in the image above:

<ripple android:color="@android:color/black" xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/background"></item>
</ripple>

What I have tried

I now tried to make a RippleDrawable in combination with a GradientDrawable to recreate the XML above in code. Very easy, I thought:

GradientDrawable gradientDrawable = new GradientDrawable();
gradientDrawable.setColor(Color.TRANSPARENT);
gradientDrawable.setStroke(mStroke, Color.GREEN);
gradientDrawable.setCornerRadius(getHeight() / 2);

ColorStateList rippleStateList = ColorStateList.valueOf(Color.BLUE);
return new RippleDrawable(rippleStateList, gradientDrawable, gradientDrawable);

This returns a simple drawable with a green stroke. However, when you click it, nothing happens. Unlike the XML where you clearly can see a ripple effect over the border. When I set the third parameter to null, same effect. When I only set the second parameter to null it only returned an empty transparant drawable without any ripple effects.

1

There are 1 answers

6
Bryan On BEST ANSWER

You should use a RippleDrawable as the background of your custom button. The RippleDrawable has a mask layer, which is what you are looking for. To do this programmatically, you can create a RippleDrawable like so:

float[] outerRadii = new float[8];
Arrays.fill(outerRadii, height / 2);

RoundRectShape shape = new RoundRectShape(outerRadii, null, null);
ShapeDrawable mask = new ShapeDrawable(shape);

ColorStateList stateList = ColorStateList.valueof(ContextCompat.getColor(getContext(), R.color.ripple));

setBackground(new RippleDrawable(stateList, getBackgroundDrawable(), mask);

Keep in mind that RippleDrawable is only available for API Level 21+, so if you are looking to support older versions as well you will need to fall back to something else; possibly android.R.attr.selectableItemBackground or a StateListDrawable, which will give your button a basic "pressed" background for older platform versions.