Ответы пользователя по тегу Material Design
  • Ripple эффект с произвольными формами?

    enq3
    @enq3
    Android engineer at #ITX5
    Я делал контролы с полной отрисовкой всей графики на Canvas. И есть ограничение, придется пожертвовать аппаратным ускорением, но я визуально разницу не особо заметил.
    Полный код класса:
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Path;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.view.View;
    import com.nineoldandroids.animation.Animator;
    import com.nineoldandroids.animation.ValueAnimator;
    
    public class RippleCircle extends View {
        // ===========================================================
        // Constants
        // ===========================================================
        private static final String TAG = RippleCircle.class.getSimpleName();
        private static final int RIPPLE_COLOR = 0x66000000;
        private static final int RIPPLE_FADE_COLOR = 0x22000000;
        private static final int FAB_COLOR = 0xffcccccc;
        private static final long ANIM_DURATION = 250;
        protected static int FLAGS = Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG;
        // ===========================================================
        // Fields
        // ===========================================================
        private Paint mButtonBg;
        private Paint mRippleButtonBg;
        private Path main;
        private ValueAnimator animatorRipple;
        private float selRadius;
        private float radius;
        private float centerY;
        private float centerX;
        private float rippleX;
        private float rippleY;
        private boolean isButtonTouchDown;
        private boolean isProgress;     
        // ===========================================================
        // Constructors
        // ===========================================================
        public RippleCircle(Context context) {
            super(context);
            init();
        }
    
        public RippleCircle(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        // ===========================================================
        // Getter & Setter
        // ===========================================================
    
        // ===========================================================
        // Methods for/from SuperClass/Interfaces
        // ===========================================================
        @Override
        protected void onDraw(Canvas canvas) {
            canvas.drawCircle(centerX, centerY, radius, mButtonBg);
            canvas.clipPath(main);
            canvas.drawCircle(rippleX, rippleY, selRadius, mRippleButtonBg);
        }
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            float size = Math.min(w, h);
            centerY = size / 2;
            centerX = size / 2;
            radius = size / 2;
            main.addCircle(centerX, centerY, radius, Path.Direction.CCW);
            animatorRipple.setFloatValues(0, radius);
        }
        // ===========================================================
        // Inner Methods
        // ===========================================================
        private void init() {
            main = new Path();
    
            animatorRipple = ValueAnimator.ofFloat(0, 0);
            animatorRipple.setDuration(ANIM_DURATION);
            animatorRipple.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    selRadius = (Float) animation.getAnimatedValue();
                    if (selRadius / radius <= 1 && selRadius / radius >= 0)
                        mRippleButtonBg.setColor(blendColors(RIPPLE_COLOR, RIPPLE_FADE_COLOR, 1 - selRadius / radius));
                    invalidate();
                }
            });
    
            animatorRipple.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
                }
    
                @Override
                public void onAnimationEnd(Animator animation) {
                    if (!isButtonTouchDown) {
                        selRadius = 0;
                        invalidate();
                        isProgress = false;
                    }
                }
    
                @Override
                public void onAnimationCancel(Animator animation) {
    
                }
    
                @Override
                public void onAnimationRepeat(Animator animation) {
    
                }
            });
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
                setLayerType(LAYER_TYPE_SOFTWARE, null);
            }
            mButtonBg = new Paint(FLAGS);
            mButtonBg.setColor(FAB_COLOR);
    
            mRippleButtonBg = new Paint(FLAGS);
            mRippleButtonBg.setColor(RIPPLE_COLOR);
    
            setOnTouchListener(new OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
    
                    switch (event.getAction()) {
                        case MotionEvent.ACTION_DOWN:
                            rippleX = event.getX();
                            rippleY = event.getY();
                            if (isPointInCircle(centerX, centerY, event.getX(), event.getY(), radius)) {
                                isButtonTouchDown = true;
                                if (!isProgress) {
                                    rippleX = event.getX();
                                    rippleY = event.getY();
                                    isProgress = true;
                                    float max = distance(centerX, centerY, rippleX, rippleY) + radius;
                                    animatorRipple.setFloatValues(0, max);
                                    animatorRipple.start();
                                }
                            }
                            break;
                        case MotionEvent.ACTION_UP:
                            isButtonTouchDown = false;
                            if (!animatorRipple.isRunning()) {
                                selRadius = 0;
                                invalidate();
                                isProgress = false;
                            }
                            break;
                    }
                    return true;
                }
            });
        }
    
        private boolean isPointInCircle(float centerX, float centerY, float x, float y, float radius) {
            return distance(centerX, centerY, x, y) <= radius;
        }
    
        private float distance(final float pX1, final float pY1, final float pX2, final float pY2) {
            final float dX = pX2 - pX1;
            final float dY = pY2 - pY1;
            return (float) Math.sqrt((dX * dX) + (dY * dY));
        }
    
        private int blendColors(int color1, int color2, float ratio) {
            final float inverseRation = 1f - ratio;
            float a = (Color.alpha(color1) * ratio) + (Color.alpha(color2) * inverseRation);
            float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation);
            float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation);
            float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation);
            return Color.argb((int) a, (int) r, (int) g, (int) b);
        }
        // ===========================================================
        // Inner and Anonymous Classes
        // ===========================================================
    }

    Используем в разметке:
    <yourPackage.RippleCircle
            android:layout_width="56dp"
            android:layout_height="56dp"
            android:layout_centerInParent="true"/>

    По аналогии можно добавить какую угодно форму, я делал прямоугольник со скругленными углами и окружность.
    Ответ написан