Ripple Effect简单实现

需求

现在Material Design很火,而Ripple Effect即涟漪效果是能给用户点击确认感的重要UI效果。也就是说,对于可以点击的东西,都可以搞一个这种效果来加强反馈。

问题与分析

众所周知,MD是API19之后才正式提出来的,而比较完备要到API21也就是Lollipop。Ripple Effect也是如此。对于API>=21的,只需要设置background为ripple drawable,或者是?attr/selectableItemBackground,但是对于API<21的设备怎么处理呢?
Ripple是没办法了,不过可以退而求其次,至少按下去要变一个颜色吧。也就是可以搞一个Selector Drawable。
于是基本思路就有了:判断设备的API,如果是新的那就设置为Ripple,否则设置为Selector。
但是这样新问题来了:设置有两种途径,XML和Java,选择哪种呢?

  1. XML
    选择XML的话,就需要准备两个Drawable,一个放在/drawable文件夹,另一个放在v21的drawable文件夹,这样当设备API>=21的话就会采用那个文件夹里面的内容。两个Drawable名字要一样。
    这样有一个比较繁琐的地方就是每个Button都得自己设置颜色,包括Ripple和clicked之后的颜色。另外之后假如要改颜色,也得全部改。总而言之就是比较琐碎。
  2. Java
    可以选择在程序里面实时设置。相对难度要大一点,因为要自己根据颜色来生成Drawable然后设置,好处就是不用动XML,假如封装得好,一行代码就可以搞定。当然我说的不是实现的类。

代码

/**
 * Factory is used for creating colored elements for the whole app. <br>
 * Instantiates using the {@code Singleton} pattern, with the {@code get()} method.
 *
 * @version 1.3
 * @see ColorStateList
 * @see StateListDrawable
 * @see Color
 */
public class Coloring {

    private static final int BOUNDS = 1500;
    private static final int BRIGHTNESS_THRESHOLD = 180;
    private static final int FADE_DURATION = 200;

    private static final Object mInitializerLock;
    private static Coloring mInstance;

    static {
        mInitializerLock = new Object();
    }

    /**
     * Destroys everything related to coloring.<br>
     */
    public static synchronized void destroy() {
        mInstance = null;
    }

    /**
     * Returns the singleton factory object.
     *
     * @return The only available {@code Coloring}
     */
    public static Coloring get() {
        if (mInstance == null) {
            synchronized (mInitializerLock) {
                if (mInstance == null) {
                    mInstance = new Coloring();
                }
            }
        }
        return mInstance;
    }

    /* **********  Factory methods go below this line  ********** */

    /**
     * Converts a String hex color value to an Integer color value.<br>
     * <br>
     * <b>Supported formats:</b><br>
     * <ul>
     * <li>#aaRRggBb</li>
     * <li>0xaaRRggBb</li>
     * <li>0XaaRRggBb</li>
     * <li>#RRggBb</li>
     * <li>0xRRggBb</li>
     * <li>0XRRggBb</li>
     * </ul>
     *
     * @param colorString String value of the desired color
     * @return Integer value for the color, or gray if something goes wrong
     */
    public int decodeColor(String colorString) {
        if (colorString == null || colorString.trim().isEmpty())
            return Color.BLACK;

        if (colorString.startsWith("#"))
            colorString = colorString.replace("#", "");

        if (colorString.startsWith("0x"))
            colorString = colorString.replace("0x", "");

        if (colorString.startsWith("0X"))
            colorString = colorString.replace("0X", "");

        int alpha = -1, red = -1, green = -1, blue = -1;

        try {
            if (colorString.length() == 8) {
                alpha = Integer.parseInt(colorString.substring(0, 2), 16);
                red = Integer.parseInt(colorString.substring(2, 4), 16);
                green = Integer.parseInt(colorString.substring(4, 6), 16);
                blue = Integer.parseInt(colorString.substring(6, 8), 16);
            } else if (colorString.length() == 6) {
                alpha = 255;
                red = Integer.parseInt(colorString.substring(0, 2), 16);
                green = Integer.parseInt(colorString.substring(2, 4), 16);
                blue = Integer.parseInt(colorString.substring(4, 6), 16);
            }
            return Color.argb(alpha, red, green, blue);
        } catch (NumberFormatException e) {
            Timber.e("Error parsing color " + e);
            return Color.GRAY;
        }
    }

    /**
     * Blends given color with white background. This means that a full color<br>
     * with transparency (alpha) will be lightened to make it look like it is<br>
     * rendered over a white background. Resulting color will be non-transparent.
     *
     * @param color Color to use for blending
     * @return Lightened color to match a white underlay render
     */
    public int alphaBlendWithWhite(int color) {
        float alpha = Color.alpha(color) / 255f;
        int origR = Color.red(color);
        int origG = Color.green(color);
        int origB = Color.blue(color);
        int white = 255;

        // rule: outputRed = (foregroundRed * foregroundAlpha) + (backgroundRed * (1.0 - foregroundAlpha))
        int r = (int) ((origR * alpha) + (white * (1.0 - alpha)));
        if (r > 255)
            r = 255;
        int g = (int) ((origG * alpha) + (white * (1.0 - alpha)));
        if (g > 255)
            g = 255;
        int b = (int) ((origB * alpha) + (white * (1.0 - alpha)));
        if (b > 255)
            b = 255;

        return Color.argb(255, r, g, b);
    }

    /**
     * Makes the given color a little bit darker.
     *
     * @param color Original color that needs to be darker
     * @return Darkened original color
     */
    public int darkenColor(int color) {
        int amount = 30;

        int r = Color.red(color);
        int g = Color.green(color);
        int b = Color.blue(color);
        int a = Color.alpha(color);

        if (r - amount >= 0) {
            r -= amount;
        } else {
            r = 0;
        }

        if (g - amount >= 0) {
            g -= amount;
        } else {
            g = 0;
        }

        if (b - amount >= 0) {
            b -= amount;
        } else {
            b = 0;
        }

        return Color.argb(a, r, g, b);
    }

    /**
     * Makes the given color a little bit lighter.
     *
     * @param color Original color that needs to be lighter
     * @return Lightened original color
     */
    public int lightenColor(int color) {
        int amount = 60;

        int r = Color.red(color);
        int g = Color.green(color);
        int b = Color.blue(color);
        int a = Color.alpha(color);

        if (r + amount <= 255) {
            r += amount;
        } else {
            r = 255;
        }

        if (g + amount <= 255) {
            g += amount;
        } else {
            g = 255;
        }

        if (b + amount <= 255) {
            b += amount;
        } else {
            b = 255;
        }

        return Color.argb(a, r, g, b);
    }

    /**
     * Creates a new drawable (implementation of the Drawable object may vary depending on OS version).<br>
     * Drawable will be colored with given color, and clipped to match given boundaries.
     *
     * @param color  Integer color used to color the output drawable
     * @param bounds Four-dimensional vector bounds
     * @return Colored and clipped drawable object
     */
    @SuppressWarnings("UnusedDeclaration")
    public Drawable createDrawable(int color, Rect bounds) {
        // init normal state drawable
        Drawable drawable = new GradientDrawable(Orientation.BOTTOM_TOP, new int[]{
                color, color
        }).mutate();
        if (color == Color.TRANSPARENT) {
            drawable.setAlpha(0);
        }
        drawable.setBounds(bounds);
        return drawable;
    }

    /**
     * Colors the given drawable to a specified color. Uses mode SRC_ATOP.
     *
     * @param context  Which context to use
     * @param drawable Which drawable to color
     * @param color    Which color to use
     * @return A colored drawable ready for use
     */
    public Drawable colorDrawable(Context context, Drawable drawable, int color) {
        if (!(drawable instanceof BitmapDrawable)) {
            Timber.w("Original drawable is not a bitmap! Trying with constant state cloning.");
            return colorUnknownDrawable(drawable, color);
        }

        Bitmap original = ((BitmapDrawable) drawable).getBitmap();
        Bitmap copy = Bitmap.createBitmap(original.getWidth(), original.getHeight(), original.getConfig());

        Paint paint = new Paint();
        Canvas c = new Canvas(copy);
        paint.setColorFilter(new PorterDuffColorFilter(color, SRC_ATOP));
        c.drawBitmap(original, 0, 0, paint);

        return new BitmapDrawable(context.getResources(), copy);
    }

    /**
     * Colors the given drawable to a specified color set using the drawable wrapping technique.
     *
     * @param drawable    Which drawable to color
     * @param colorStates Which color set to use
     * @return A colored drawable ready to use
     */
    public Drawable colorDrawableWrap(Drawable drawable, ColorStateList colorStates) {
        if (drawable != null) {
            drawable = DrawableCompat.wrap(drawable);
            DrawableCompat.setTintList(drawable, colorStates);
            DrawableCompat.setTintMode(drawable, PorterDuff.Mode.SRC_ATOP);
            drawable = DrawableCompat.unwrap(drawable);
            return drawable;
        }
        return null;
    }

    /**
     * Colors the given drawable to a specified color using the drawable wrapping technique.
     *
     * @param drawable Which drawable to color
     * @param color    Which color to use
     * @return A colored drawable ready to use
     */
    public Drawable colorDrawableWrap(Drawable drawable, int color) {
        if (drawable != null) {
            Drawable wrapped = DrawableCompat.wrap(drawable);
            DrawableCompat.setTint(wrapped, color);
            DrawableCompat.setTintMode(wrapped, PorterDuff.Mode.SRC_ATOP);
            return DrawableCompat.unwrap(wrapped);
        }
        return null;
    }

    /**
     * Tries to clone and just color filter the drawable. Uses mode SRC_ATOP.
     *
     * @param drawable Which drawable to color
     * @param color    Which color to use
     * @return A colored drawable ready for use
     */
    @SuppressWarnings("RedundantCast")
    public Drawable colorUnknownDrawable(Drawable drawable, int color) {
        if (drawable instanceof DrawableWrapper || drawable instanceof android.support.v7.graphics.drawable.DrawableWrapper) {
            drawable = DrawableCompat.wrap(drawable);
            DrawableCompat.setTint(drawable, color);
            DrawableCompat.setTintMode(drawable, PorterDuff.Mode.SRC_ATOP);
            drawable = DrawableCompat.unwrap(drawable);
            return drawable;
        } else {
            try {
                Drawable copy = drawable.getConstantState().newDrawable();
                copy.mutate();
                copy.setColorFilter(color, SRC_ATOP);
                return copy;
            } catch (Exception e) {
                Timber.d("Failed to color unknown drawable: " + drawable.getClass().getSimpleName());
                return drawable;
            }
        }
    }

    /**
     * Colors the given drawable to a specified color. Uses mode SRC_ATOP.<br>
     * Automatically loads a good quality bitmap from the {@code resourceId} if it is valid.
     *
     * @param context    Which context to use
     * @param resourceId Which drawable resource to load
     * @param color      Which color to use
     * @return A colored {@link Drawable} ready for use
     */
    public Drawable colorDrawable(Context context, int resourceId, int color) {
        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inDither = false; // disable dithering
        //noinspection deprecation
        opts.inPurgeable = true; // allocate pixels that could be freed by the system
        //noinspection deprecation
        opts.inInputShareable = true; // see javadoc
        opts.inTempStorage = new byte[32 * 1024]; // temp storage - advice is to use 16K
        opts.inPreferQualityOverSpeed = false;

        Bitmap original = BitmapFactory.decodeResource(context.getResources(), resourceId, opts);
        return colorDrawable(context, new BitmapDrawable(context.getResources(), original), color);
    }

    /**
     * Creates a new {@code StateListDrawable} drawable. States that should be provided are "normal",<br>
     * "clicked" (pressed) and "checked" (selected). All states are actually integer colors.<br>
     * Optionally, {@code shouldFade} can be set to false to avoid the fading effect.<br>
     * <br>
     * Note: <i>{@link Color#TRANSPARENT} can be used to supply a transparent state.</i>
     *
     * @param normal     Color for the idle state
     * @param clicked    Color for the clicked/pressed state
     * @param checked    Color for the checked/selected state
     * @param shouldFade Set to true to enable the fading effect, false otherwise
     * @return A {@link StateListDrawable} drawable object ready for use
     */
    @SuppressLint({
            "InlinedApi", "NewApi"
    })
    public Drawable createStateDrawable(int normal, int clicked, int checked, boolean shouldFade) {
        // init state arrays
        int[] selectedState = new int[]{
                android.R.attr.state_selected
        };
        int[] pressedState = new int[]{
                android.R.attr.state_pressed
        };
        int[] checkedState = new int[]{
                android.R.attr.state_checked
        };
        int[] focusedState = new int[]{
                android.R.attr.state_focused
        };
        int[] activatedState = new int[]{};

        activatedState = new int[]{
                android.R.attr.state_activated
        };


        // init normal state drawable
        Drawable normalDrawable = new GradientDrawable(Orientation.BOTTOM_TOP, new int[]{
                normal, normal
        }).mutate();
        if (normal == Color.TRANSPARENT)
            normalDrawable.setAlpha(0);
        else
            normalDrawable.setBounds(BOUNDS, BOUNDS, BOUNDS, BOUNDS);

        // init clicked state drawable
        Drawable clickedDrawable = new GradientDrawable(Orientation.BOTTOM_TOP, new int[]{
                clicked, clicked
        }).mutate();
        if (clicked == Color.TRANSPARENT)
            clickedDrawable.setAlpha(0);
        else
            clickedDrawable.setBounds(BOUNDS, BOUNDS, BOUNDS, BOUNDS);

        // init checked state drawable
        Drawable checkedDrawable = new GradientDrawable(Orientation.BOTTOM_TOP, new int[]{
                checked, checked
        }).mutate();
        if (checked == Color.TRANSPARENT)
            checkedDrawable.setAlpha(0);
        else
            checkedDrawable.setBounds(BOUNDS, BOUNDS, BOUNDS, BOUNDS);

        // init focused state drawable (use normal color)
        Drawable focusedDrawable = new GradientDrawable(Orientation.BOTTOM_TOP, new int[]{
                normal, normal
        }).mutate();
        if (normal == Color.TRANSPARENT)
            focusedDrawable.setAlpha(0);
        else
            focusedDrawable.setBounds(BOUNDS, BOUNDS, BOUNDS, BOUNDS);

        // prepare state list (order of adding states is important!)
        StateListDrawable states = new StateListDrawable();
        states.addState(pressedState, clickedDrawable);
        if (!shouldFade) {
            states.addState(selectedState, clickedDrawable);
            states.addState(focusedState, focusedDrawable);
            states.addState(checkedState, checkedDrawable);
        }

        // add fade effect if applicable

        if (shouldFade) {
            states.addState(new int[]{}, normalDrawable);
            states.setEnterFadeDuration(0);
            states.setExitFadeDuration(FADE_DURATION);
        } else {
            states.addState(activatedState, clickedDrawable);
            states.addState(new int[]{}, normalDrawable);
        }


        return states;
    }

    /**
     * Creates a new {@code RippleDrawable} used in Lollipop's ListView.
     *
     * @param context     Which context to use
     * @param rippleColor Color for the clicked, pressed and focused ripple states
     * @return A fully colored RippleDrawable instance
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public Drawable createListViewRipple(Context context, int rippleColor) {
        RippleDrawable ripple;
        ripple = (RippleDrawable) context.getResources().getDrawable(R.drawable.list_selectors, null);
        if (ripple != null) {
            ripple.setColor(ColorStateList.valueOf(rippleColor));
        }
        return ripple;
    }

    /**
     * Creates a new {@code RippleDrawable} used in Lollipop and later.
     *
     * @param normalColor Color for the idle ripple state
     * @param rippleColor Color for the clicked, pressed and focused ripple states
     * @param bounds      Clip/mask drawable to these rectangle bounds
     * @return A fully colored RippleDrawable instance
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public Drawable createRippleDrawable(int normalColor, int rippleColor, Rect bounds) {
        ColorDrawable maskDrawable = null;
        if (bounds != null) {
            maskDrawable = new ColorDrawable(Color.WHITE);
            maskDrawable.setBounds(bounds);
        }

        if (normalColor == Color.TRANSPARENT) {
            return new RippleDrawable(ColorStateList.valueOf(rippleColor), null, maskDrawable);
        } else {
            return new RippleDrawable(ColorStateList.valueOf(rippleColor), new ColorDrawable(normalColor), maskDrawable);
        }
    }

    /**
     * Creates a new drawable using given parameters. States that should be provided are "normal",<br>
     * "clicked" (pressed) and "checked" (selected). All states are actually integer colors.<br>
     * Optionally, {@code shouldFade} can be set to false to avoid the fading effect.<br>
     * Depending on API level, Drawable instance will be a Ripple drawable (Lollipop) or StateListDrawable.<br>
     * <br>
     * Note: <i>{@link Color#TRANSPARENT} can be used to supply a transparent state.</i>
     *
     * @param normal     Color for the idle state
     * @param clicked    Color for the clicked/pressed state
     * @param checked    Color for the checked/selected state
     * @param shouldFade Set to true to enable the fading effect, false otherwise
     * @return A {@link StateListDrawable} drawable object ready for use
     */
    public Drawable createBackgroundDrawable(int normal, int clicked, int checked, boolean shouldFade) {
        return createBackgroundDrawable(normal, clicked, checked, shouldFade, null);
    }

    /**
     * Very similar to {@link #createBackgroundDrawable(int, int, int, boolean)}, adding only one more parameter.
     *
     * @param bounds Clip/mask drawable to these rectangle bounds
     * @return Clipped/masked drawable instance
     */
    public Drawable createBackgroundDrawable(int normal, int clicked, int checked, boolean shouldFade, Rect bounds) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            return createRippleDrawable(normal, clicked, bounds);
        } else {
            return createStateDrawable(normal, clicked, checked, shouldFade);
        }
    }

    /**
     * Similar to {@link #createContrastStateDrawable(Context, int, int, boolean, android.graphics.drawable.Drawable)} but using colors
     * only, no drawables.
     *
     * @param normal            Color normal state to this color
     * @param clickedBackground Background color of the View that will show when view is clicked
     * @return The color state list that is in contrast with the on-click background color
     */
    @SuppressLint({
            "InlinedApi", "NewApi"
    })
    public ColorStateList createContrastStateColors(int normal, int clickedBackground) {
        // init state arrays
        int[] normalState = new int[]{};
        int[] selectedState = new int[]{
                android.R.attr.state_selected
        };
        int[] pressedState = new int[]{
                android.R.attr.state_pressed
        };
        int[] checkedState = new int[]{
                android.R.attr.state_checked
        };
        int[] activatedState = new int[]{};

        activatedState = new int[]{
                android.R.attr.state_activated
        };


        // initialize identifiers
        int[] stateColors;
        int[][] stateIdentifiers;
        int contrastColor = getContrastColor(clickedBackground);


        stateIdentifiers = new int[][]{
                selectedState, pressedState, checkedState, activatedState, normalState
        };
        stateColors = new int[]{
                contrastColor, contrastColor, contrastColor, contrastColor, normal
        };


        return new ColorStateList(stateIdentifiers, stateColors);
    }

    /**
     * Similar to {@link #createBackgroundDrawable(int, int, int, boolean)} but with additional {@code original} drawable parameter.
     *
     * @param context           Which context to use
     * @param normal            Color normal state of the drawable to this color
     * @param clickedBackground Background color of the View that will show when view is clicked
     * @param shouldFade        Set to true if the state list should have a fading effect
     * @param original          This drawable will be contrasted to the {@code clickedBackground} color on press
     * @return The state list drawable that is in contrast with the on-click background color
     */
    @SuppressLint({
            "InlinedApi", "NewApi"
    })
    public Drawable createContrastStateDrawable(Context context, int normal, int clickedBackground, boolean shouldFade, Drawable original) {
        if (original == null || original instanceof StateListDrawable) {
            if (original != null) {
                Timber.i("Original drawable is already a StateListDrawable");
                original = original.getCurrent();
            }

            // overridden in previous if clause, so check again
            if (original == null) {
                return null;
            }
        }

        // init state arrays
        int[] selectedState = new int[]{
                android.R.attr.state_selected
        };
        int[] pressedState = new int[]{
                android.R.attr.state_pressed
        };
        int[] checkedState = new int[]{
                android.R.attr.state_checked
        };
        int[] activatedState = new int[]{};

        activatedState = new int[]{
                android.R.attr.state_activated
        };


        Drawable normalStateDrawable = colorDrawable(context, original, normal);
        Drawable clickedStateDrawable = colorDrawable(context, original, getContrastColor(clickedBackground));
        Drawable checkedStateDrawable = colorDrawable(context, original, getContrastColor(clickedBackground));

        // prepare state list (order of adding states is important!)
        StateListDrawable states = new StateListDrawable();
        states.addState(pressedState, clickedStateDrawable);
        if (!shouldFade) {
            states.addState(selectedState, clickedStateDrawable);
            states.addState(checkedState, checkedStateDrawable);
        }

        // add fade effect if applicable

        if (shouldFade) {
            states.addState(new int[]{}, normalStateDrawable);
            states.setEnterFadeDuration(0);
            states.setExitFadeDuration(FADE_DURATION);
        } else {
            states.addState(activatedState, clickedStateDrawable);
            states.addState(new int[]{}, normalStateDrawable);
        }


        return states;
    }

    /**
     * Very similar to {@link #createContrastStateDrawable(Context context, int, int, boolean, android.graphics.drawable.Drawable)} but
     * creates a Ripple drawable available in Lollipop.
     *
     * @param normal            Color normal state of the drawable to this color
     * @param clickedBackground Background color of the View that will show when view is clicked
     * @param original          This drawable will be contrasted to the {@code clickedBackground} color on press
     * @return The Ripple drawable that is in contrast with the on-click background color
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public Drawable createContrastRippleDrawable(int normal, int clickedBackground, Drawable original) {
        if (original == null) {
            Timber.i("Creating a boundless drawable for contrast ripple request - original was null!");
            return createRippleDrawable(normal, clickedBackground, null);
        }

        return new RippleDrawable(ColorStateList.valueOf(clickedBackground), original, new ColorDrawable(clickedBackground));
    }

    /**
     * This basically chooses between {@link #createContrastStateDrawable(Context, int, int, boolean, android.graphics.drawable.Drawable)}
     * and {@link #createContrastRippleDrawable(int, int, android.graphics.drawable.Drawable)} depending on the available API level.
     *
     * @param context           Which context to use
     * @param normal            Color normal state of the drawable to this color
     * @param clickedBackground Background color of the View that will show when view is clicked
     * @param shouldFade        Set to true if the state list (pre-API 21) should have a fading effect
     * @param original          This drawable will be contrasted to the {@code clickedBackground} color on press (pre-API 21) or used for masking in
     *                          ripples on post-API 21
     * @return The state list drawable (< API21) or a ripple drawable (>= API21) that is in contrast with the on-click background color
     */
    public Drawable createContrastBackgroundDrawable(Context context, int normal, int clickedBackground, boolean shouldFade,
                                                     Drawable original) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            return createContrastRippleDrawable(normal, clickedBackground, original);
        } else {
            return createContrastStateDrawable(context, normal, clickedBackground, shouldFade, original);
        }
    }

    /**
     * Calculates the contrasted color from the given one. If the color darkness is under<br>
     * the {@link #BRIGHTNESS_THRESHOLD}, contrasted color is white. If the color darkness is<br>
     * over the {@link #BRIGHTNESS_THRESHOLD}, contrasted color is black.
     *
     * @param color Calculating contrasted color to this one
     * @return White or black, depending on the provided color's brightness
     */
    public int getContrastColor(int color) {
        int r = Color.red(color);
        int g = Color.green(color);
        int b = Color.blue(color);

        // human eye is least sensitive to blue, then to red, then green; calculating:
        int brightness = (b + r + r + g + g + g) / 6;

        if (brightness < BRIGHTNESS_THRESHOLD)
            return Color.WHITE;
        else
            return Color.BLACK;
    }

    /**
     * Fetch the button color for you and create drawable
     * If transparent, then set ripple or clicked state color to grey
     *
     * @param button Target you want to set ripple on
     */

    public void setButtonRipple(Button button) {
        if (button != null) {
            Drawable drawable;
            int color = this.getColorFromView(button);
            if (color == Color.TRANSPARENT) {
                drawable = this.createBackgroundDrawable(color, Color.parseColor("#FFD8D8D8"), Color.parseColor("#FFD8D8D8"), true, getRect(button));
            } else {
                drawable = this.createBackgroundDrawable(color, this.darkenColor(color), this.darkenColor(color), true, getRect(button));
            }
            button.setBackgroundDrawable(drawable);
        }
    }

    public void setLayoutRipple(ViewGroup viewGroup) {
        if (viewGroup != null) {
            Drawable drawable;
            int color = this.getColorFromView(viewGroup);
            if (color == Color.TRANSPARENT) {
                drawable = this.createBackgroundDrawable(color, Color.parseColor("#FFD8D8D8"), Color.parseColor("#FFD8D8D8"), true, getRect(viewGroup));
            } else {
                drawable = this.createBackgroundDrawable(color, this.darkenColor(color), this.darkenColor(color), true, getRect(viewGroup));
            }
            viewGroup.setBackgroundDrawable(drawable);
        }
    }

    private Rect getRect(View view) {
        int[] l = new int[2];
        view.getLocationOnScreen(l);
        return new Rect(l[0], l[1], l[0] + view.getWidth(), l[1] + view.getHeight());
    }
}

这是一个帮助类,此外还需要两个Drawable:
drawable-v21/list_selectors.xml:

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

    <item android:id="@android:id/mask">
        <shape android:shape="rectangle">
            <solid android:color="@android:color/white" />
        </shape>
    </item>

</ripple>

drawable/list_selectors.xml:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
    android:enterFadeDuration="0"
    android:exitFadeDuration="300">

    <item android:drawable="@color/gray_light" android:state_pressed="true" />
    <item android:drawable="@color/transparent" />

</selector>

还有<color name="gray_light">#FFF0F0F0</color>加到Colors.xml里面去。
这个类我大部分也是参考GitHub上的,并非原创,不过后几个函数是我加的,可以直接给button和layout加ripple。理论上只要view支持background为color,都可以这样做。
这样在代码里面找到button之后,直接:

Coloring.get().setButtonRipple(mBtnView);

就可以了。

总结

颜色值是从button里面提取的,这一步可能并不高效,因为事先就已经知道了。不过我是懒得再一个个button去检查原来是什么颜色了,而且这样做的话button颜色要改这个就得一起改。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容