1

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.

Bart Bergmans
  • 4,061
  • 3
  • 28
  • 56
  • 1
    In the button XML have you tried `android:foreground="?android:attr/selectableItemBackground"` – Mark Jan 09 '17 at 20:18
  • 1
    That works, a step closer to the solution. Only problem, the ripple ignores the rounded corners.. – Bart Bergmans Jan 09 '17 at 20:22
  • That's due to the fact the ripple is going to the bounds of the button itself, not sure but is there a way to clip the bounds to the outer of the gradient stroke?? – Mark Jan 09 '17 at 20:28
  • 1
    This may help http://stackoverflow.com/a/7559233/4252352 – Mark Jan 09 '17 at 20:37
  • @MarkKeen Purrfect! I'm now going to figure out how to do this programmatically and then I'm all set, thanks! – Bart Bergmans Jan 09 '17 at 20:47

1 Answers1

5

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.

Bryan
  • 14,756
  • 10
  • 70
  • 125
  • This answer is better than my comments - should be accepted as correct answer – Mark Jan 09 '17 at 22:42
  • I think is what I want, I'll check it out soon! Thanks! – Bart Bergmans Jan 10 '17 at 11:59
  • When I use this ripple is just a transparant drawable and nothing happens when you press it. I can't figure out how the RippleDrawable works. – Bart Bergmans Jan 11 '17 at 20:11
  • @BartBergmans I found the issue, using a `GradientDrawable` as a mask was causing the `RippleDrawable` to become transparent for some reason. So I switched it up to a `ShapeDrawable`. I also found that you can use your original background `Drawable` as the content parameter for the `RippleDrawable`, and set the `RippleDrawable` as the background for your `Button`. Check my revised answer. – Bryan Jan 12 '17 at 15:15
  • @Bryan Thanks, I'll check it out later. I thought I tried it with other drawables but I'm not sure though. If you find it interesting you could check out the rest of the code so you could understand what I'm trying to achieve. https://github.com/bartbergmans/GradientButton – Bart Bergmans Jan 12 '17 at 15:21
  • 1
    @BartBergmans I created a [pull request](https://github.com/bartbergmans/GradientButton/pull/1) on your repo. I made some significant changes, so make sure to read my comment before you accept it. But the main issue was that the buttons were not clickable for some reason, so if you add an `OnClickListener` or set `android:clickable="true"` the ripple effect will be enabled. – Bryan Jan 12 '17 at 21:17