Android Ripple 按钮水波纹效果(二)优化

上一篇中我们讲了自定义ripple 水波纹效果,先来回顾一下效果吧!
技术分享

看了以后感觉没甚么问题,我一开始也觉得很满意了,那好,我们拿Android 5.0自带的效果来对比一下
技术分享

发现了不同之处没?点击中间的时候是看不出什么区别,但是点击两边的时候,就很明显了,我们自定义的效果,波纹向两边同速度的扩散,所以就会出现,如果点击点不在中心的时候,距离短的一边波纹先到达,而距离长的一边后到达,不能同时到达边缘!而系统自带的则不存在这种情况,所以这是一个优化点;另一个优化点是:我们自定义的效果,在波纹全部覆盖以后,按钮的选中效果没了。

有两处需要优化的
1、实现不管是否点击中间点都能实现波纹同步到达边缘
2、当手指未松开时,选中效果不消失

实现:
第二点好实现,我们主要讲一下第一点,
第一点我们观察系统的效果,看似两边速度不一致导致的,其实我们知道实现原理的话,很容易想到,它是不断改变圆的圆心来实现,我们上一篇中的实现方法是圆形固定,就是在我们手指按下的位置,而不断改变半径来实现,
很明显,这里也需要改变半径来实现,我记得我们上一篇中半径的最大值是需要计算,而这种效果是不需要计算的,因为其最大值是固定的,就是按钮对角线的一半!
确定了半径的最大值,我们还需要确定圆心X、Y的偏移量,相当于步长吧,其圆心从按下的点到按钮正中间的时间因该是和半径从0到最大值的时间保持一致,所以我们可以通过一下代码来获取圆心的偏移量和最大半径。

/*最大半径*/
mRadius = (float) Math.sqrt(mRect.width() / 2 * mRect.width() / 2 + mRect.height() / 2 * mRect.height() / 2);
/*半径的偏移量*/
mStepRadius = mRadius / mCycle;
/*圆心X的偏移量*/
mStepOriginX = (mRect.width() / 2 - mInitX) / mCycle;
/*圆心Y的偏移量*/
mStepOriginY = (mRect.height() / 2 - mInitY) / mCycle;

全部实现代码

package eyeclip.myapplication;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.LinearLayout;

/**
 * Created by moon.zhong on 2015/4/27.
 */
public class RippleLinearLayout extends LinearLayout {

    /*起始点*/
    private int mInitX;
    private int mInitY;

    private float mCurrentX;
    private float mCurrentY;

    /*高度和宽度*/
    private int mWidth;
    private int mHeight;

    /*绘制的半径*/
    private float mRadius;
    private float mStepRadius;
    private float mStepOriginX;
    private float mStepOriginY;
    private float mDrawRadius;

    private boolean mDrawFinish;

    private final int DURATION = 150;
    private final int FREQUENCY = 10;
    private float mCycle;
    private final Rect mRect = new Rect();

    private boolean mPressUp = false;


    private Paint mRevealPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    public RippleLinearLayout(Context context) {
        super(context);
        initView(context);
    }

    public RippleLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

    public RippleLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context);
    }

    private void initView(Context context) {
        mRevealPaint.setColor(0x25000000);
        mCycle = DURATION / FREQUENCY;
        final float density = getResources().getDisplayMetrics().density ;
        mCycle = (density*mCycle);
        mDrawFinish = true;

    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mDrawFinish) {
            super.onDraw(canvas);
            return;
        }
        canvas.drawColor(0x15000000);
        super.onDraw(canvas);
        if (mStepRadius == 0) {
            return;
        }
        mDrawRadius = mDrawRadius + mStepRadius;
        mCurrentX = mCurrentX + mStepOriginX;
        mCurrentY = mCurrentY + mStepOriginY;
        if (mDrawRadius > mRadius) {
            mDrawRadius = 0;
            canvas.drawCircle(mRect.width() / 2, mRect.height() / 2, mRadius, mRevealPaint);
            mDrawFinish = true;
            if (mPressUp)
                invalidate();
            return;
        }

        canvas.drawCircle(mCurrentX, mCurrentY, mDrawRadius, mRevealPaint);
        ViewCompat.postInvalidateOnAnimation(this);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
    }

    private void updateDrawData() {
//        int radiusLeftTop = (int) Math.sqrt((mRect.left - mInitX) * (mRect.left - mInitX) +
//                (mRect.top - mInitY) * (mRect.top - mInitY));
//        int radiusRightTop = (int) Math.sqrt((mRect.right - mInitX) * (mRect.right - mInitX) +
//                (mRect.top - mInitY) * (mRect.top - mInitY));
//        int radiusLeftBottom = (int) Math.sqrt((mRect.left - mInitX) * (mRect.left - mInitX) +
//                (mRect.bottom - mInitY) * (mRect.bottom - mInitY));
//        int radiusRightBottom = (int) Math.sqrt((mRect.right - mInitX) * (mRect.right - mInitX) +
//                (mRect.bottom - mInitY) * (mRect.bottom - mInitY));
//        mRadius = getMax(radiusLeftTop, radiusRightTop, radiusLeftBottom, radiusRightBottom);
        /*最大半径*/
        mRadius = (float) Math.sqrt(mRect.width() / 2 * mRect.width() / 2 + mRect.height() / 2 * mRect.height() / 2);
        ;
        /*半径的偏移量*/
        mStepRadius = mRadius / mCycle;
        /*圆心X的偏移量*/
        mStepOriginX = (mRect.width() / 2 - mInitX) / mCycle;
        /*圆心Y的偏移量*/
        mStepOriginY = (mRect.height() / 2 - mInitY) / mCycle;

        mCurrentX = mInitX;
        mCurrentY = mInitY;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final int action = MotionEventCompat.getActionMasked(event);
        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                mPressUp = false;
                mDrawFinish = false;
                int index = MotionEventCompat.getActionIndex(event);
                int eventId = MotionEventCompat.getPointerId(event, index);
                if (eventId != -1) {
                    mInitX = (int) MotionEventCompat.getX(event, index);
                    mInitY = (int) MotionEventCompat.getY(event, index);
                    updateDrawData();
                    invalidate();
                }
                break;
            }
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                mStepRadius = (int) (5 * mStepRadius);
                mStepOriginX = (int) (5 * mStepOriginX);
                mStepOriginY = (int) (5 * mStepOriginY);
                mPressUp = true;
                invalidate();
                break;
        }
        return super.onTouchEvent(event);
    }

    private int getMax(int... radius) {
        if (radius.length == 0) {
            return 0;
        }
        int max = radius[0];
        for (int m : radius) {
            if (m > max) {
                max = m;
            }
        }
        return max;
    }

    @Override
    public boolean performClick() {

        postDelayed(new Runnable() {
            @Override
            public void run() {
                RippleLinearLayout.super.performClick();
            }
        }, 150);
        return true;

    }
}

效果图对比
技术分享

这篇主要是对上一篇的内容进行优化,当然你觉得不优化也行!

demo下载

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。