美文网首页Android 自定义view程序猿学习
自定义View之qq气泡拖拽消失效果

自定义View之qq气泡拖拽消失效果

作者: MrAllRight_Liu | 来源:发表于2017-08-10 11:44 被阅读239次

先看效果图(本篇实现气泡拖拽消失,没有实现回弹和消失时爆炸效果)


气泡拖拽消失

观察效果图,可以发现其中有两个圆(固定圆和拖拽圆)加上中间拖拽的贝塞尔效果,其中固定圆的半径随着拉伸不断变小,小到一定值时消失,手指松开,如果固定圆还存在则回弹,不存在则拖拽圆也消失,本篇没实现的是回弹时的贝塞尔效果,已经拖拽圆消失时爆炸的效果,如果有机会实现了会再进行讲解。

先来初始化代码,我们定义为BubbleView类

public class BubbleView extends View {
 private int mFixCircleRadius = 20;//固定圆的半径,其半径会随着拉伸变小,我们设定最小是6,小于6的时候固定圆消失
    private int mDragCircleRadius = 20;//拖拽时显示的园
    private PointF mFixCirclePoint;//固定圆圆心,本文只是演示,具体使用时要自己计算view的大小,再设置合适的圆心和半径
    private PointF mDragCirclePoint;//拖拽圆的圆心,随着拖拽不断变化
    private Paint paint;//画笔
    private float distance;//两个圆心的距离
    private boolean isDrag = false;//判断手指按下时是否落在固定圆的区域
    private boolean isFixCircleShow = true;//固定圆是否显示
    private boolean isUp = false;//手指是否弹起,弹起时如果固定圆显示这时候要回弹回来,如果固定圆消失了,此时应该执行拖拽圆爆炸消失效果,本文演示只做消失效果

    public BubbleView(Context context) {
        this(context, null);
    }

    public BubbleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BubbleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    //初始化画笔,圆心
    private void init() {
        mFixCirclePoint = new PointF(300, 300);
        mDragCirclePoint = new PointF(300, 300);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setColor(getResources().getColor(R.color.red));
    }
}

接着我们监听onTouchEvent事件,在ACTION_DOWN的时候判断手指是否落在了固定圆的区域,如果没有不响应本次拖拽事件,接着在ACTION_MOVE中计算拉伸的距离,并不断的改变固定圆的大小(拉伸距离越大半径越小)以及拉伸圆的圆心(手指移动到的点),在ACTION_UP中我们恢复初始值,代码如下:

@Override
    public boolean onTouchEvent(MotionEvent event) {
        float downX = event.getX();
        float downY = event.getY();
        isUp = false;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                isDrag(downX, downY);//判断是否在固定圆区域,不在不显示拖拽效果
                break;
            case MotionEvent.ACTION_MOVE:
                if (!isDrag) return true;
                //判断是否出现拉伸效果
                if (Math.abs(event.getX() - mFixCirclePoint.x )> mFixCircleRadius || Math.abs(event.getY() - mFixCirclePoint.y) > mFixCircleRadius) {
                    mDragCirclePoint.x = event.getX();//不断改变拖拽圆的圆心
                    mDragCirclePoint.y = event.getY();
                    float dx = mFixCirclePoint.x - mDragCirclePoint.x;
                    float dy = mFixCirclePoint.y - mDragCirclePoint.y;
                    distance = (float) Math.hypot(Math.abs(dx), Math.abs(dy));//计算拖拽的距离
                    postInvalidate();
                }
                break;
            case MotionEvent.ACTION_UP:
                isDrag = false;
                isUp = true;
                mFixCircleRadius = 20;
                mDragCirclePoint.x = event.getX();
                mDragCirclePoint.y = event.getY();
                postInvalidate();
                break;
        }

        return true;
    }

    //判断手指是否落在了固定的小圆内
    private void isDrag(float x, float y) {
        if (x >= mFixCirclePoint.x - mFixCircleRadius-20 && x <= mFixCirclePoint.x + mFixCircleRadius+20 && y >= mFixCirclePoint.y - mFixCircleRadius-20 && y <= mFixCirclePoint.y + mFixCircleRadius+20)
            isDrag = true;
        else isDrag = false;
    }

接下来就是绘制,绘制过程中除了两个圆,最主要的就是中间的拉伸效果,拉伸效果实际上是一个贝塞尔曲线,如下图


本图来自 http://blog.csdn.net/z240336124/article/details/76409397

最主要的就是计算p0,p1,p2,p3的点,其中我们知道c0,c1的位置,然后根据三角函数就可以计算出这4个点,然后p0到p1是一个二阶贝塞尔曲线,p2到p3是一个贝塞尔曲线,控制点我们选为c0,c1中间的位置,代码如下:

 //绘制拉伸的效果
    private void drawBezer(Canvas canvas) {
        Path path = new Path();
        //计算4个控制点
        float dx = mFixCirclePoint.x - mDragCirclePoint.x;
        float dy = mFixCirclePoint.y - mDragCirclePoint.y;
        if (dx == 0) {
            dx = 0.001f;
        }
        float tan = dy / dx;
        // 获取角a度值
        float arcTanA = (float) Math.atan(tan);

        // 依次计算 p0 , p1 , p2 , p3 点的位置
        float P0X = (float) (mFixCirclePoint.x + mFixCircleRadius * Math.sin(arcTanA));
        float P0Y = (float) (mFixCirclePoint.y - mFixCircleRadius * Math.cos(arcTanA));

        float P1X = (float) (mDragCirclePoint.x + mDragCircleRadius * Math.sin(arcTanA));
        float P1Y = (float) (mDragCirclePoint.y - mDragCircleRadius * Math.cos(arcTanA));

        float P2X = (float) (mDragCirclePoint.x - mDragCircleRadius * Math.sin(arcTanA));
        float P2Y = (float) (mDragCirclePoint.y + mDragCircleRadius * Math.cos(arcTanA));

        float P3X = (float) (mFixCirclePoint.x - mFixCircleRadius * Math.sin(arcTanA));
        float P3Y = (float) (mFixCirclePoint.y + mFixCircleRadius * Math.cos(arcTanA));
        PointF controlPoint = new PointF(mFixCirclePoint.x + (mDragCirclePoint.x - mFixCirclePoint.x) / 2, (mFixCirclePoint.y + (mDragCirclePoint.y - mFixCirclePoint.y) / 2));
        // 整合贝塞尔曲线路径
        path.moveTo(P0X, P0Y);
        path.quadTo(controlPoint.x, controlPoint.y, P1X, P1Y);
        path.lineTo(P2X, P2Y);
        path.quadTo(controlPoint.x, controlPoint.y, P3X, P3Y);
        path.close();
        canvas.drawPath(path, paint);
    }

最后是整个的绘制过程代码:

  @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (!isUp) {
            //手指没有弹起时
            mFixCircleRadius = (int) (20 - distance / 14);
            //初始化时只画固定圆
            if (!isDrag)
                canvas.drawCircle(mFixCirclePoint.x, mFixCirclePoint.y, mFixCircleRadius, paint);
            else {
                //拖拽时不断减小固定圆半径,如果小于6时,固定圆消失
                if (mFixCircleRadius <= 6) {
                    isFixCircleShow = false;
                }
                if (mFixCircleRadius > 6 && isFixCircleShow) {
                    canvas.drawCircle(mFixCirclePoint.x, mFixCirclePoint.y, mFixCircleRadius, paint);
                    drawBezer(canvas);//绘制拖拽的效果
                }
                canvas.drawCircle(mDragCirclePoint.x, mDragCirclePoint.y, mDragCircleRadius, paint);
            }
        } else {
            //手指弹起时,如果固定圆没有消失,显示回弹效果
            if (isFixCircleShow)
                canvas.drawCircle(mFixCirclePoint.x, mFixCirclePoint.y, mFixCircleRadius, paint);
        }
    }

最后贴一下本篇的完整代码

/**
 * Created by liuyong
 * Data: 2017/8/9
 * Github:https://github.com/MrAllRight
 * qq未读消息拖拽消失
 */

public class BubbleView extends View {
    private int mFixCircleRadius = 20;//固定圆的半径,其半径会随着拉伸变小,我们设定最小是6,小于6的时候固定圆消失
    private int mDragCircleRadius = 20;//拖拽时显示的园
    private PointF mFixCirclePoint;//固定圆圆心,本文只是演示,具体使用时要自己计算view的大小,再设置合适的圆心和半径
    private PointF mDragCirclePoint;//拖拽圆的圆心,随着拖拽不断变化
    private Paint paint;//画笔
    private float distance;//两个圆心的距离
    private boolean isDrag = false;//判断手指按下时是否落在固定圆的区域
    private boolean isFixCircleShow = true;//固定圆是否显示
    private boolean isUp = false;//手指是否弹起,弹起时如果固定圆显示这时候要回弹回来,如果固定圆消失了,此时应该执行拖拽圆爆炸消失效果,本文演示只做消失效果

    public BubbleView(Context context) {
        this(context, null);
    }

    public BubbleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BubbleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    //初始化画笔,圆心
    private void init() {
        mFixCirclePoint = new PointF(300, 300);
        mDragCirclePoint = new PointF(300, 300);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setColor(getResources().getColor(R.color.red));
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float downX = event.getX();
        float downY = event.getY();
        isUp = false;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                isDrag(downX, downY);//判断是否在固定圆区域,不在不显示拖拽效果
                break;
            case MotionEvent.ACTION_MOVE:
                if (!isDrag) return true;
                //判断是否出现拉伸效果
                if (Math.abs(event.getX() - mFixCirclePoint.x )> mFixCircleRadius || Math.abs(event.getY() - mFixCirclePoint.y) > mFixCircleRadius) {
                    mDragCirclePoint.x = event.getX();//不断改变拖拽圆的圆心
                    mDragCirclePoint.y = event.getY();
                    float dx = mFixCirclePoint.x - mDragCirclePoint.x;
                    float dy = mFixCirclePoint.y - mDragCirclePoint.y;
                    distance = (float) Math.hypot(Math.abs(dx), Math.abs(dy));//计算拖拽的距离
                    postInvalidate();
                }
                break;
            case MotionEvent.ACTION_UP:
                isDrag = false;
                isUp = true;
                mFixCircleRadius = 20;
                mDragCirclePoint.x = event.getX();
                mDragCirclePoint.y = event.getY();
                postInvalidate();
                break;
        }

        return true;
    }

    //判断手指是否落在了固定的小圆内
    private void isDrag(float x, float y) {
        if (x >= mFixCirclePoint.x - mFixCircleRadius-20 && x <= mFixCirclePoint.x + mFixCircleRadius+20 && y >= mFixCirclePoint.y - mFixCircleRadius-20 && y <= mFixCirclePoint.y + mFixCircleRadius+20)
            isDrag = true;
        else isDrag = false;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (!isUp) {
            //手指没有弹起时
            mFixCircleRadius = (int) (20 - distance / 14);
            //初始化时只画固定圆
            if (!isDrag)
                canvas.drawCircle(mFixCirclePoint.x, mFixCirclePoint.y, mFixCircleRadius, paint);
            else {
                //拖拽时不断减小固定圆半径,如果小于6时,固定圆消失
                if (mFixCircleRadius <= 6) {
                    isFixCircleShow = false;
                }
                if (mFixCircleRadius > 6 && isFixCircleShow) {
                    canvas.drawCircle(mFixCirclePoint.x, mFixCirclePoint.y, mFixCircleRadius, paint);
                    drawBezer(canvas);//绘制拖拽的效果
                }
                canvas.drawCircle(mDragCirclePoint.x, mDragCirclePoint.y, mDragCircleRadius, paint);
            }
        } else {
            //手指弹起时,如果固定圆没有消失,显示回弹效果
            if (isFixCircleShow)
                canvas.drawCircle(mFixCirclePoint.x, mFixCirclePoint.y, mFixCircleRadius, paint);
        }
    }
   //绘制拉伸的效果
    private void drawBezer(Canvas canvas) {
        Path path = new Path();
        //计算4个控制点
        float dx = mFixCirclePoint.x - mDragCirclePoint.x;
        float dy = mFixCirclePoint.y - mDragCirclePoint.y;
        if (dx == 0) {
            dx = 0.001f;
        }
        float tan = dy / dx;
        // 获取角a度值
        float arcTanA = (float) Math.atan(tan);

        // 依次计算 p0 , p1 , p2 , p3 点的位置
        float P0X = (float) (mFixCirclePoint.x + mFixCircleRadius * Math.sin(arcTanA));
        float P0Y = (float) (mFixCirclePoint.y - mFixCircleRadius * Math.cos(arcTanA));

        float P1X = (float) (mDragCirclePoint.x + mDragCircleRadius * Math.sin(arcTanA));
        float P1Y = (float) (mDragCirclePoint.y - mDragCircleRadius * Math.cos(arcTanA));

        float P2X = (float) (mDragCirclePoint.x - mDragCircleRadius * Math.sin(arcTanA));
        float P2Y = (float) (mDragCirclePoint.y + mDragCircleRadius * Math.cos(arcTanA));

        float P3X = (float) (mFixCirclePoint.x - mFixCircleRadius * Math.sin(arcTanA));
        float P3Y = (float) (mFixCirclePoint.y + mFixCircleRadius * Math.cos(arcTanA));
        PointF controlPoint = new PointF(mFixCirclePoint.x + (mDragCirclePoint.x - mFixCirclePoint.x) / 2, (mFixCirclePoint.y + (mDragCirclePoint.y - mFixCirclePoint.y) / 2));
        // 整合贝塞尔曲线路径
        path.moveTo(P0X, P0Y);
        path.quadTo(controlPoint.x, controlPoint.y, P1X, P1Y);
        path.lineTo(P2X, P2Y);
        path.quadTo(controlPoint.x, controlPoint.y, P3X, P3Y);
        path.close();
        canvas.drawPath(path, paint);
    }
}

也可到github上下载整个贝塞尔曲线的代码,其中有水波纹,直播点赞,还有本篇qq气泡拖拽效果,地址:https://github.com/MrAllRight/BezierView

写完发现这个还是参考红橙Darren的文章,大家可以去他的简书看一下,里面有自定义view的系列,讲解的都不错,而且还有视频讲解。

相关文章

网友评论

    本文标题:自定义View之qq气泡拖拽消失效果

    本文链接:https://www.haomeiwen.com/subject/lifhrxtx.html