1

I have an Android project where I'm changing the color of the Ripple effect that happens in one of my views using the following approach

Although it's working, I do need to reset this color back to it's default value, based on my style. Bellow I'll show my Style file, and I'd love to have a way to set the RippleDrawable back to it's default color.

<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
    <item name="editTextColor">@android:color/white</item>
    <item name="font">@font/roboto_regular</item>

    <item name="colorControlNormal">@android:color/white</item>
    <item name="android:scrollbarThumbVertical">@drawable/nice_scrollbar</item>

    <item name="android:textColor">@color/darkGrey</item>
    <item name="android:editTextColor">@android:color/black</item>

    <item name="android:textColorHighlight">@color/colorPrimary</item>
    <item name="android:colorBackground">@color/darkerWhite</item>
    <item name="android:windowBackground">@color/darkerWhite</item>

    <item name="windowNoTitle">true</item>
    <item name="android:windowNoTitle">true</item>
</style>

As it can be noticed, I'm using MaterialComponents.

Below you'll find the current method I'm using to change color and also force the ripple effect on the view at a given x/y:

private void forceRippleAnimation(View view, int x, int y) {
    Drawable background = view.getBackground();
    if (Build.VERSION.SDK_INT >= 21 && background instanceof RippleDrawable) {
        RippleDrawable rippleDrawable = (RippleDrawable) background;
        rippleDrawable.setHotspot(x, y);
        rippleDrawable.setColor(new ColorStateList(
            new int[][]{
                new int[]{}
            },
            new int[]{
                getContext().getResources().getColor(R.color.rippleColor)
            }
        ));
        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(view.getDrawableState());
            }
        }, 650);
    }
}
Edric
  • 24,639
  • 13
  • 81
  • 91
  • Please show how you change the color - there are at least two useful answers to the linked question and I've no idea which one looked most promising in your eyes. – Bö macht Blau Jan 29 '19 at 19:58
  • Hello @0X0nosugar, I just edited my question with the description of how I'm forcing the ripple effect. A possible way would be finding the initial ColorStateList from the RippleDrawable, but there's no getColorStateList, neither a way to find the default value... – Marcelo Petrucelli Jan 30 '19 at 10:20

1 Answers1

1

Instead of trying to reverse the changes made to the original background RippleDrawable, you can keep it and apply the changes to a copy:

Let the Activity have two Drawable fields:

private RippleDrawable customRippleDrawable;
private RippleDrawable backgroundFromXml;

Create a copy of the given background and set it as the new background:

Drawable background = view.getBackground();
if (Build.VERSION.SDK_INT >= 21 && background instanceof RippleDrawable) {
    backgroundFromXml = (RippleDrawable) background;
    customRippleDrawable = (RippleDrawable) background.getConstantState().newDrawable().mutate();
    customRippleDrawable.setHotspot(x, y);
    customRippleDrawable.setColor(new ColorStateList(
            new int[][]{
                new int[]{}
            },
            new int[]{
                MainActivity.this.getResources().getColor(R.color.rippleColor)
            }
    ));
    customRippleDrawable.setState(new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled});
    view.setBackground(customRippleDrawable);
}

Since backgroundFromXml is a field, you can access it later on to reset the background to its original value:

view.setBackground(backgroundFromXml);

What does mutate() do?

All Drawables generated from one drawable resource share a common state. This helps saving resources (e.g. memory) and so will improve the performance of an android application.

Normally, if you apply for example a ColorFilter to a Drawable generated from a certain drawable resource, you will notice the effect everywhere in your app where this specific drawable resource is used.

Calling mutate() on a Drawable tells the runtime that this Drawable should have its own state. If you apply any changes afterwards, the other Drawables wil remain unchanged.

See also the documentation on mutate()

Bö macht Blau
  • 12,820
  • 5
  • 40
  • 61
  • I found another solution that doesn't work over ripples. But I may chose your answer the correct answer for what I asked. Could you further explain how .mutate() works? Seems like getResources().getColor(R.color.rippleColor) may be a way of getting this color, but I was unable to make it working. I always got a crash trying this out. – Marcelo Petrucelli Feb 05 '19 at 10:38
  • I'm sorry, one more question, where in your code you consider copying the background? I see that you get it from the view, mutates it and apply a new colorstate to it. After that you set again this same background to the view. Why would this reset the color? And where do you copy this background (all I see is the reference being picked up and changed, being reset to the view afterwards)? – Marcelo Petrucelli Feb 07 '19 at 10:23
  • 1
    @MarceloPetrucelli - there are two `Drawable`s in my example. One is created based on the other. I use one of them (*customRippleDrawable*) to show the changed ripple and the other one (*backgroundFromXml*) to show the normal ripple. – Bö macht Blau Feb 08 '19 at 19:10