自定义Button实现按钮点击水波纹效果

Andoid 5.0推出了特有的按钮水波纹点击效果,看上去非常炫酷,但是由于要5.0才有这个新特效,并不支持5.0以下的系统,因此我们只能通过自定义控件在4.0实现这一效果,效果图如下:


主要代码就是自定义一个Button控件,重写dispatchDraw()方法和onTouchEvent()方法,分别处理点击事件和绘制控件效果,下面贴上代码:


/**
 * Created by Sentox on 2016/3/16.
 * 水波纹点击按钮
 */
public class RevealButton extends Button {

    private static final int INVALIDATE_DURATION = 10; //每次刷新的时间间隔
    private static int DIFFUSE_GAP = 35;                  //扩散半径增量

    private Paint paint;
    private MotionEvent motionEvent;

    private float maxRadio = 0;//最大半径
    private float radio = 0;//绘制半径
    private float pointX = 0;//被点击的坐标点x
    private float pointY = 0;//被点击的坐标点y
    private int viewHeight;//View高度
    private int viewWidth;//View宽度

    private boolean actionUpFlag = false;//actionUp标志位,标识是否可以相应actionUp事件
    private boolean actionDownFlag = false;//手指依然点击本view的标志位
    private boolean actionCancelFlag = false;//点击取消标志位

    public RevealButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }

    public RevealButton(Context context) {
        super(context);
        initPaint();
    }

    public void initPaint() {
        paint = new Paint();
        paint.setColor(Color.BLACK);
        paint.setAlpha(50);
    }

    /**
     * 设置水波纹画笔
     * setColor控制颜色
     * setAlpha控制透明度
     * **/
    public void setPaint(Paint p){
        paint = p;
    }

    //布局发生变化时回调
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        this.viewWidth = w;
        this.viewHeight = h;
    }

    //计算波纹最大半径
    private void countMaxRadio() {
        if (viewWidth >= viewHeight) {
            if (pointX <= viewWidth / 2) {
                maxRadio = viewWidth - pointX;
            } else {
                maxRadio = pointX;
            }
        } else {
            if (pointY <= viewHeight / 2) {
                maxRadio = viewHeight - pointY;
            } else {
                maxRadio = pointY;
            }
        }
        maxRadio = maxRadio + 20;
    }

    @Override
    public boolean onTouchEvent(final MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                pointX = event.getX();
                pointY = event.getY();
                actionDownFlag = true;
                countMaxRadio();
                postInvalidateDelayed(INVALIDATE_DURATION);
                break;
            case MotionEvent.ACTION_UP:
                //flag用做判断是否适合触发点击事件,false则不适合,true则适合
                if (!actionUpFlag && !actionCancelFlag) {
                    actionUpFlag = true;
                    actionCancelFlag = false;
                    motionEvent = event;
                    return true;
                }
                actionUpFlag = false;
                clearData();
                postInvalidate();
                break;
            case MotionEvent.ACTION_CANCEL:
                actionCancelFlag = true;
                clearData();
                postInvalidate();
                break;
        }
        return super.onTouchEvent(event);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        if (!actionDownFlag) {
            return;
        }

        canvas.save();
        canvas.clipRect(0, 0, viewWidth, viewHeight);
        canvas.drawCircle(pointX, pointY, radio, paint);
        canvas.restore();

        if (radio <= maxRadio) {
            //播放动画
            radio = radio + DIFFUSE_GAP;
            postInvalidateDelayed(INVALIDATE_DURATION);
        } else {
            //动画播放完成
            if (actionUpFlag) {
                //如果为true,则说明此时actionUp已经被触发过,则重新触发
                RevealButton.this.dispatchTouchEvent(motionEvent);
            } else {
                //为false则说明actionUp未被触发,手指仍保留在点击状态,设flag为true,当actionUp被触发时,直接清空动画,响应点击事件
                actionUpFlag = true;
            }
//            clearData();
        }
    }

    private void clearData() {
        actionDownFlag = false;
        maxRadio = 0;//最大半径
        radio = 0;//绘制半径
        pointX = 0;//被点击的坐标点x
        pointY = 0;//被点击的坐标点y
    }
}

基本的逻辑已经在代码中有了注释,这里再简单说一下对点击事件的处理,当OnTouchEvent()检测到ACTION_DOWN的时候,让标志位actionDownFlag设为true,然后开始计算水波纹的最大半径,并通知dispatchDraw()进行重绘,水波纹的绘制原理是以点击位置为圆心,不断重绘半径越来越大的半透明实心圆,直到覆盖整个按钮。当半径大于前面计算好的最大半径,则清空所有计算数据,将actionDownFlag设为false,再通知重绘。这里需要对点击状态进行判断,并以标志位actionUpFlag作为判断标志,如果当动画播放完成的时候,依然没有检测到ACTION_UP,则将标志位设为true,当检测到ACTION_UP后将立即分发点击事件;如果动画完成之前已经检测到ACTION_UP,那么僵标志位设为TRUE,返回TRUE,拦截点击事件,记录此时的motionEvent,等到播放动画完成后,再调用dispatchTouchEvent(motionEvent),分发点击事件,这样,水波纹点击效果就成功了。

你可能感兴趣的:(android,布局,控件,5.0)