Search code examples
javaandroidmaterial-components-androidrippledrawable

Change RippleEffect Color back to it's Styled original value


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);
    }
}

Solution

  • 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()