类似QQ拖动气泡删除消息的气泡实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xiayong1/article/details/53672778

类似QQ拖动气泡删除消息的气泡实现

本文将介绍使用二阶贝塞尔曲线实现类似QQ拖动气泡删除消息的气泡实现,本文中的部分内容参考自Yellow5A5

1. 贝塞尔曲线简介

一阶贝塞尔曲线如下图:

一阶贝塞尔曲线的公式为: B(t)=(1t)P0+tP1, t[0,1] ;

二阶贝塞尔曲线如下图:

二阶贝塞尔曲线的公式为:
B(t)=(1t)2P0+2t(1t)P1+t2P2, t[0,1] ; ( P1 看作是控制点)
拆分:
B(t)=(1t)2P0+t(1t)P1+t(1t)P1+t2P2, t[0,1] ;
合并:
B(t)=(1t)((1t)P0+tP1)+t((1t)P1+tP2), t[0,1] ;
换算,其中 B0(t)B1(t) 分别是一阶贝塞尔曲线:
B(t)=(1t)B0(t)+tB1(t), t[0,1] ;
B0(t)=(1t)P0+tP1, t[0,1] ;
B1(t)=(1t)P1+tP2, t[0,1]

2. 气泡实现

代码原理图如下:

代码如下:

package widget;

import android.animation.ValueAnimator;
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;

/**
 * Created by cugyong on 2016/12/15.
 * 类似于qq的拖拉删除消息的圆球动画(使用二阶贝塞尔曲线)
 */

public class DragRoundView extends View {

    private final static int RADIUS = 25;
    // 固定圆的半径随着移动圆圆心和固定圆圆心之间距离变化而改变的参数因子
    private final static int FACTOR = 8;

    // 固定不动的圆的半径和移动的圆的半径的总和,应当是一个定值, 在这里为RADIUS乘以屏幕密度
    private float mRadiusSum;

    // 固定不动的圆的圆心
    private float mCenterX;
    private float mCenterY;

    // 固定不动的圆的半径
    private float mCenterRadius;

    // 移动的圆的圆心
    private float mMovingX;
    private float mMovingY;

    // 移动的圆的半径
    private float mMovingRadius;

    // 拖动结束的点
    float mEndX;
    float mEndY;

    // 移动圆圆心和固定圆圆心之间距离的最大值
    private float mLimit;

    // 两条直线和两条二阶贝塞尔曲线
    private Path mPath;

    private Paint mPaint;

    // 拖动释放时圆球恢复的动画
    private ValueAnimator mAnimator;

    public DragRoundView(Context context) {
        super(context);
        initParams();
    }

    public DragRoundView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initParams();
    }

    public DragRoundView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initParams();
    }

    private void initParams(){
        // 屏幕密度
        float density =  getResources().getDisplayMetrics().density;
        mRadiusSum = density * RADIUS;
        int screenWidth = getResources().getDisplayMetrics().widthPixels;
        int screenHeight = getResources().getDisplayMetrics().heightPixels;
        // 固定不动的圆的圆心初始化为屏幕的中心
        mCenterX = screenWidth / 2;
        mCenterY = screenHeight / 2;
        // 未拖动的时候移动的圆的圆心和固定不动的圆的圆心在同一位置
        mMovingX = mCenterX;
        mMovingY = mCenterY;
        // 未拖动的时候移动的圆的半径为0,固定不动的圆的半径为mRadiusSum
        mCenterRadius = mRadiusSum;
        mMovingRadius = 0;

        mLimit = (screenWidth > screenHeight?screenHeight:screenWidth) / 2.2f;

        mPath = new Path();
        mPaint = new Paint();
        mPaint.setColor(Color.parseColor("#ff0000"));

        mAnimator = ValueAnimator.ofFloat(1, 0).setDuration(500);
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float animationValue = (float) animation.getAnimatedValue();
                mMovingX = mCenterX + (mEndX - mCenterX) * animationValue;
                mMovingY = mCenterY + (mEndY - mCenterY) * animationValue;
                float distance = calculateDistanceBetweenPoints(mCenterX, mCenterY, mMovingX, mMovingY);
                mCenterRadius = mRadiusSum - distance / FACTOR;
                mMovingRadius = mRadiusSum - mCenterRadius;
                updatePath();
                invalidate();
            }
        });
    }

    // 更新画贝塞尔曲线的五个点的坐标以及其构成的相应路径
    private void updatePath(){
        if (mMovingX == mCenterX && mMovingY == mCenterY){
            return;
        }
        // theta角度
        double corner = Math.atan((mMovingY - mCenterY)/(mMovingX - mCenterX));

        // 计算点P1和P4
        float offsetX = (float)(mMovingRadius * Math.sin(corner));
        float offsetY = (float)(mMovingRadius * Math.cos(corner));
        // 点P1
        float xP1 = mMovingX - offsetX;
        float yP1 = mMovingY + offsetY;
        // 点P4
        float xP4 = mMovingX + offsetX;
        float yP4 = mMovingY - offsetY;

        // 计算点P2和P3
        offsetX = (float)(mCenterRadius * Math.sin(corner));
        offsetY = (float)(mCenterRadius * Math.cos(corner));
        // 点P2
        float xP2 = mCenterX - offsetX;
        float yP2 = mCenterY + offsetY;
        // 点P3
        float xP3 = mCenterX + offsetX;
        float yP3 = mCenterY - offsetY;

        // 移动的圆和固定不动的圆的圆心的连线的中点Mid作为贝塞尔曲线的控制点
        float midPointX = (mCenterX + mMovingX) / 2;
        float midPointY = (mCenterY + mMovingY) / 2;

        mPath.reset();
        // P1作为起点
        mPath.moveTo(xP1, yP1);
        // 点P1、Mid以及P2构成的二阶贝塞尔曲线,Mid作为贝塞尔曲线的控制点
        mPath.quadTo(midPointX, midPointY, xP2, yP2);
        // 直线连接点P2和P3
        mPath.lineTo(xP3, yP3);
        // 点P3、Mid以及P4构成的二阶贝塞尔曲线,Mid作为贝塞尔曲线的控制点
        mPath.quadTo(midPointX, midPointY, xP4, yP4);
        // 直线连接点P4和P1
        mPath.lineTo(xP1, yP1);
    }

    private float calculateDistanceBetweenPoints(float pointX1, float pointY1, float pointX2, float pointY2){
        return (float) Math.sqrt(Math.pow(pointX1 - pointX2, 2) + Math.pow(pointY1 - pointY2, 2));
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        // 当前触摸点坐标
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (action){
            case MotionEvent.ACTION_DOWN:
                // 如果按下点不在有效区域内,则不处理该事件
                if (x < mCenterX - mCenterRadius || x > mCenterX + mCenterRadius ||
                        y < mCenterY - mCenterRadius || y > mCenterY + mCenterRadius){
                    return false;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                mMovingX = x;
                mMovingY = y;
                float distance = calculateDistanceBetweenPoints(mCenterX, mCenterY, mMovingX, mMovingY);
                if (distance > mLimit){
                    float temp = mLimit / distance;
                    mMovingX = (mMovingX - mCenterX) * temp + mCenterX;
                    mMovingY = (mMovingY - mCenterY) * temp + mCenterY;
                    distance = mLimit;
                }
                mCenterRadius = mRadiusSum - distance / FACTOR;
                mMovingRadius = mRadiusSum - mCenterRadius;
                updatePath();
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                mEndX = mMovingX;
                mEndY = mMovingY;
                mAnimator.start();
                break;
        }
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(mCenterX, mCenterY, mCenterRadius, mPaint);
        canvas.drawCircle(mMovingX, mMovingY, mMovingRadius, mPaint);
        canvas.drawPath(mPath, mPaint);
    }
}

运行效果图:

猜你喜欢

转载自blog.csdn.net/xiayong1/article/details/53672778