# 这里有一份史上最详细仿QQ未读消息拖拽粘性效果的实现，快来收藏！

## 1、绘制起始圆

```    //是否可拖拽
private boolean mIsCanDrag = false;
//是否超过最大距离
private boolean isOutOfRang = false;
//最终圆是否消失
private boolean disappear = false;

//两圆相离最大距离
private float maxDistance;

//贝塞尔曲线需要的点
private PointF pointA;
private PointF pointB;
private PointF pointC;
private PointF pointD;
//控制点坐标
private PointF pointO;

//起始位置点
private PointF pointStart;
//拖拽位置点
private PointF pointEnd;

//根据滑动位置动态改变圆的半径

private Rect textRect = new Rect();
//消息数
private int msgCount = 0;```

```    /**
* 画起始小球
*
* @param canvas 画布
* @param pointF 点坐标
*/
private void drawStartBall(Canvas canvas, PointF pointF, float radius) {
}

/**
* 画拖拽结束的小球
*
* @param canvas 画布
* @param pointF 点坐标
*/
private void drawEndBall(Canvas canvas, PointF pointF, float radius) {
}```

```    @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
startX = w / 2;
startY = h / 2;
maxDistance = dp2px(100);

}```

## 2、根据贝塞尔曲线绘制连接带

tanESS1=tanA=S1E/SS1=(Ex-Sx)/(Ey-Sy)=rate，rate就是这个角的斜率，然后根据反正切得出角A，A=arctan(rate)，这是反正切公式，忘记的可以去百度百科温故一下哦。

```    /**
* 设置贝塞尔曲线的相关点坐标  计算方式参照结算图即可看明白
* （ps为了画个清楚这个图花了不少功夫哦）
*/
private void setABCDOPoint() {
//控制点坐标
pointO.set((pointStart.x + pointEnd.x) / 2.0f, (pointStart.y + pointEnd.y) / 2.0f);

float x = pointEnd.x - pointStart.x;
float y = pointEnd.y - pointStart.y;

//斜率 tanA=rate
double rate;
rate = x / y;
//角度  根据反正切函数算角度
float angle = (float) Math.atan(rate);

pointA.x = (float) (pointStart.x + Math.cos(angle) * currentRadiusStart);
pointA.y = (float) (pointStart.y - Math.sin(angle) * currentRadiusStart);

pointB.x = (float) (pointEnd.x + Math.cos(angle) * currentRadiusEnd);
pointB.y = (float) (pointEnd.y - Math.sin(angle) * currentRadiusEnd);

pointC.x = (float) (pointEnd.x - Math.cos(angle) * currentRadiusEnd);
pointC.y = (float) (pointEnd.y + Math.sin(angle) * currentRadiusEnd);

pointD.x = (float) (pointStart.x - Math.cos(angle) * currentRadiusStart);
pointD.y = (float) (pointStart.y + Math.sin(angle) * currentRadiusStart);
}```

## 3、处理onTouchEvent事件

###### 3.1、处理ACTION_DOWN事件

```            case MotionEvent.ACTION_DOWN:
setIsCanDrag(event);
break;

/**
* 判断是否可以拖拽
*
* @param event event
*/
private void setIsCanDrag(MotionEvent event) {
Rect rect = new Rect();
rect.left = (int) (startX - radiusStart);
rect.top = (int) (startY - radiusStart);
rect.right = (int) (startX + radiusStart);
rect.bottom = (int) (startY + radiusStart);

//触摸点是否在圆的坐标域内
mIsCanDrag = rect.contains((int) event.getX(), (int) event.getY());
}```
###### 3.2、处理ACTION_MOVE事件

```                if (mIsCanDrag) {
currentX = event.getX();
currentY = event.getY();
//设置拖拽圆的坐标
pointEnd.set(currentX, currentY);
}```

```    /**
* 设置当前计算的到的半径
*/
//两个圆心之间的距离
float distance = (float) Math.sqrt(Math.pow(pointStart.x - pointEnd.x, 2) + Math.pow(pointStart.y - pointEnd.y, 2));

//拖拽距离在设置的最大值范围内才绘制贝塞尔图形
if (distance <= maxDistance) {
//比例系数  控制两圆半径缩放
float percent = distance / maxDistance;

//之所以*0.6和0.2只为了设置拖拽过程圆变化的过大和过小这个系数是多次尝试的出的
//你也可以适当调整系数达到自己想要的效果

isOutOfRang = false;
} else {
isOutOfRang = true;
}
}```

###### 3.3、处理ACTION_UP事件

```                if (mIsCanDrag) {
if (isOutOfRang) {
//消失动画
disappear = true;
invalidate();
} else {
disappear = false;
//归位，重置各个点的坐标为开始状态
pointEnd.set(pointStart.x,pointStart.y);
setABCDOPoint();
invalidate();
}
}```

## 4、动画效果，锦上添花

```            case MotionEvent.ACTION_UP:
if (mIsCanDrag) {
if (isOutOfRang) {
//消失动画
disappear = true;
if (onDragBallListener != null) {
onDragBallListener.onDisappear();
}
invalidate();
} else {
disappear = false;
//回弹动画
final float a = (pointEnd.y - pointStart.y) / (pointEnd.x - pointStart.x);
ValueAnimator valueAnimator = ValueAnimator.ofFloat(pointEnd.x, pointStart.x);
valueAnimator.setDuration(500);
valueAnimator.setInterpolator(new BounceInterpolator());
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float x = (float) animation.getAnimatedValue();

float y = pointStart.y + a * (x - pointStart.x);

pointEnd.set(x, y);

setABCDOPoint();

invalidate();

}
});
valueAnimator.start();
}
}
break;```

```    @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

pointStart.set(startX, startY);
if (isOutOfRang) {
if (!disappear) {
}
} else {
if (mIsCanDrag) {
drawBezier(canvas);
}

}

if (!disappear) {
if (msgCount > 0) {
drawText(canvas, msgCount, pointEnd);
}
}
}```

275 篇文章32 人订阅

0 条评论