博主声明:
转载请在开头附加本文链接及作者信息,并标记为转载。本文由博主 威威喵 原创,请多支持与指教。
本文首发于此 博主:威威喵 | 博客主页:https://blog.csdn.net/smile_running
这几天突然发现 QQ 的消息拖拽动画效果还挺不错的,以前都没去留意它,这几看了一点关于贝塞尔曲线的知识,这不刚好沙场练兵。于是从昨天开始呢,我就已经开始补点高数的知识了。虽然我现在已经准大四了,眨眼间就快毕业了,高数的知识还是从大一开始学习的,现在基本忘了差不多了。
扯了一点关于我的学习经历,回到本篇问题的关键,QQ 消息拖拽效果是怎样的呢?于是,我在模拟器装了一个 QQ 应用,特地找了一下小号,记得这个号好像是我初中申请的账号,以前那会儿 cf、飞车、dnf 特别流行,搞了几个小号搬砖,哈哈。我们来看看消息拖拽的效果吧:
这样的效果做起来并不简单,尤其是曲线的计算方面,如果你也像我一样忘了高数的知识点的话,建议你去翻翻三角函数那部分的知识, 本文不会教你这些基本公式,也不会教你自定义 view 的基本流程,本篇目的:计算和实现拖拽的粘性效果。如果这些基本知识不具备的话,推荐你去看下我的自定义 view 相关文章。
有了上一篇(点击这里:贝塞尔曲线(Bezier)之爱心点赞曲线动画效果)对贝塞尔曲线的基本了解和写了一个小案例的铺垫,在这次写这个 QQ 消息拖拽效果的时候,显然轻松了许多。好了,废话就说这么多,下面进入重点内容。
首先,看上面的效果显示情况,可以看成两个小圆,一个比较大一点,可以拖拽出去,另一个小一点,但会随着两个圆的距离改变大小。我们的步骤:在 onDraw 里面绘制两个圆,用手指可以拖动一个大圆,并且小圆的大小会随着两圆的距离更改。这部分代码非常简单,我就不做多的介绍了,如果你对下面代码有不解之处,还请自己补充知识。直接贴代码:
package nd.no.xww.qqmessagedragview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
/**
* @author xww
* @desciption : 仿 QQ 消息拖拽消失的效果(大圆:不会消失,且大小一致。小圆:与大圆的距离协调改变大小)
* @date 2019/8/2
* @time 8:54
*/
public class QQMessageDragView extends View {
private Paint mPaint;
//大圆
private float mBigCircleX;
private float mBigCircleY;
private final int BIG_CIRCLE_RADUIS = 50;
//小圆
private float mSmallCircleX;
private float mSmallCircleY;
private int mSmallDefRaduis = 40;
private int mSmallHideRaduis = 15;
private int mSmallCircleRaduis = mSmallDefRaduis;
private Bitmap mMessageBitmap;
private void init() {
mPaint = new Paint();
mPaint.setDither(true);
mPaint.setAntiAlias(true);
mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
mMessageBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.message);
mMessageBitmap = Bitmap.createScaledBitmap(mMessageBitmap, 150, 150, false);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY ? MeasureSpec.getSize(widthMeasureSpec) : 200
, MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY ? MeasureSpec.getSize(heightMeasureSpec) : 200);
}
public QQMessageDragView(Context context) {
this(context, null);
}
public QQMessageDragView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public QQMessageDragView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@Override
protected void onDraw(Canvas canvas) {
if (mSmallCircleRaduis > mSmallHideRaduis) {
canvas.drawCircle(mSmallCircleX, mSmallCircleY, mSmallCircleRaduis, mPaint);
}
canvas.drawCircle(mBigCircleX, mBigCircleY, BIG_CIRCLE_RADUIS, mPaint);
// canvas.drawBitmap(mMessageBitmap, mBigCircleX, mBigCircleY, mPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float downX = event.getX();
float downY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mSmallCircleRaduis = mSmallDefRaduis;
mSmallCircleX = mBigCircleX = downX;
mSmallCircleY = mBigCircleY = downY;
break;
case MotionEvent.ACTION_MOVE:
mBigCircleX = event.getX();
mBigCircleY = event.getY();
int disCircle = calculateDisCircle(mSmallCircleX, mSmallCircleY, mBigCircleX, mBigCircleY);
mSmallCircleRaduis = mSmallDefRaduis - disCircle / mSmallHideRaduis;
break;
case MotionEvent.ACTION_UP:
mSmallCircleRaduis = 0;
break;
}
invalidate();
return true;
}
// 两点之间的距离公式 √(x2-x1)²+(y2-y1)²
private int calculateDisCircle(float mSmallCircleX, float mSmallCircleY, float mBigCircleX, float mBigCircleY) {
return (int) Math.sqrt(Math.pow((mSmallCircleX - mBigCircleX), 2) + Math.pow((mSmallCircleY - mBigCircleY), 2));
}
}
运行上面的代码,你就会看到和我一样的效果:
好了,上面的代码只是做了一个铺垫,也是必须实现的第一步。接下来重头戏开始,我们讲讲一些数学相关知识吧,本人高数也不怎么样,大学除了基本必修高数,也没去深入学习,不过这也不影响我们下面的操作。
首先,扔出一张草图,画的就这样,将就看吧:
这上面应该不难看懂吧,两个红色圆就相当于我们拖拽的圆一样,从上面的草图中,我们目前已知的有 c1 c2 r1 r2 这四个属性值,c1 c2 代表圆心坐标,r1 r2 是半径。
当用手指去拖拽大圆的时候,它们之间的联系就用那两根蓝色的曲线来表示,两曲线对应的在两圆上的坐标点就是 p1 p2 p3 p4 四个点,这四个点会伴随这两圆的距离发生改变,你可以想象一下效果。
那么,从上图中,我们就要去计算 p1 p2 p3 p4 这四个点的坐标,然后将四点封闭起来绘制成路径即可。可是,说的比较轻巧,从目前我们已知的条件当中,能用得上的就 c1 c2 r1 r2 四个了,如何去求呢?看接下来的这张图:
从这张图的计算过程中,我们可以求得绿色三角形的角 a 的相关方程式。因为我们已知 c1 圆心的坐标值,就可以得出 p1 点的坐标值,如上图 p1x p1y 的值。
这样的话,我们可以利用三角函数公式得出 b 边和 c 边的值,如上图,最终得到的一个方程式中,仅存在一个角 a 是我们未知的,接下来我们就要去计算角 a 的值,看下图:
来到第一张图,看上面的黄色辅助线,假设它形成的是直角。我们就可以得到这两条辅助线的边长 dy 和 dx 。又根据三角形的补角和两平行线之间的夹角相等的定理,我们得出图中的三个角 a 都是一样的大小。
这样我们可以得到一个等式:tanA = dy / dx ,最终,角 a = arctan( tanA )
这时候我们就取到了 a 相关的等式了,而 dx dy 都是可以计算出来的,所以一连串下来,相关的等式都成立了,从而就可以计算出一个点 p1,获得 p1 点后,p2 p3 p4 不就手到擒来嘛。
最后要想形成贝塞尔曲线的效果,除了 p1 p2 p3 p4 以外,我们还需要一个控制点,如图上的点 M,它是形成曲线的控制点,也是至关重要的一个点,它的坐标就是 M点 ( (c1x+c2x) / 2 , (c1y+c2y) / 2 )
那么本篇数学相关的计算部分就已经结束了,你还以为程序员不需要数学知识嘛,哈哈。下面就是该怎么写程序了,把数学公式化为程序代码,这就得看你的编程水平啦。
我写了好一会儿,都是那个坐标值正负的问题卡了我挺久的,不过最终还是把代码给搞出来了,四个点的计算方法如下:
private float p1X;
private float p1Y;
private float p2X;
private float p2Y;
private float p3X;
private float p3Y;
private float p4X;
private float p4Y;
//控制点
private float controlX;
private float controlY;
private float dx, dy;
private double angleA;
private double tanA;
private Path bezierPath;
private Path mBezierPath;
/**
* 贝塞尔 p1 p2 p3 p4 四个点坐标的计算
*
* @return
*/
private Path drawDragBezier() {
if (mSmallCircleRaduis < mSmallHideRaduis) {
return null;
}
dx = mBigCircleX - mSmallCircleX;
dy = mBigCircleY - mSmallCircleY;
tanA = dy / dx;
angleA = Math.atan(tanA);
//控制点的计算
controlX = (mSmallCircleX + mBigCircleX) / 2;
controlY = (mSmallCircleY + mBigCircleY) / 2;
p1X = (float) (mSmallCircleX + Math.sin(angleA) * mSmallCircleRaduis);
p1Y = (float) (mSmallCircleY - Math.cos(angleA) * mSmallCircleRaduis);
p2X = (float) (mBigCircleX + Math.sin(angleA) * BIG_CIRCLE_RADUIS);
p2Y = (float) (mBigCircleY - Math.cos(angleA) * BIG_CIRCLE_RADUIS);
p3X = (float) (mBigCircleX - Math.sin(angleA) * BIG_CIRCLE_RADUIS);
p3Y = (float) (mBigCircleY + Math.cos(angleA) * BIG_CIRCLE_RADUIS);
p4X = (float) (mSmallCircleX - Math.sin(angleA) * mSmallCircleRaduis);
p4Y = (float) (mSmallCircleY + Math.cos(angleA) * mSmallCircleRaduis);
//绘制路径
bezierPath = new Path();
bezierPath.moveTo(p1X, p1Y);
bezierPath.quadTo(controlX, controlY, p2X, p2Y);
bezierPath.lineTo(p3X, p3Y);
bezierPath.quadTo(controlX, controlY, p4X, p4Y);
bezierPath.close();
return bezierPath;
}
然后呢,使用就很简单了。返回一个路径,我们只要画出来就好了,修改 onDraw 代码如下:
@Override
protected void onDraw(Canvas canvas) {
mBezierPath = drawDragBezier();
if (mBezierPath != null) {
canvas.drawCircle(mSmallCircleX, mSmallCircleY, mSmallCircleRaduis, mPaint);
canvas.drawPath(mBezierPath, mPaint);
}
canvas.drawCircle(mBigCircleX, mBigCircleY, BIG_CIRCLE_RADUIS, mPaint);
}
好了吧,点击运行,你将会看到如下的效果:
最后做了一点点小优化,拖拽时没有超出范围可以回到原来的位置,若超出拖拽的极限方法,导致两个圆失去关联时,代表要摧毁那个大圆,手指松开那一刹那,要将它隐藏掉,效果如下:
那么至此,我们的QQ消息的粘性动画已经实现了,代码倒是不难,难的是通过数学公式来计算出 p1 p2 p3 p4 点的坐标值,这可能会卡住很多人,主要还是因为数学功底不足,还是抽时间补补数学,它可是个很有魅力的机灵鬼。
补充:(对上面的特效进行优化处理)
今天,8 月 8 日,早上 5 点半左右,台湾不幸遭到了地震,连我在福建中北部地带都能偶感晃动,我好像迷迷糊糊中感觉床在摇晃,是 6 点多级的地震,在此祝愿台湾人民安好。而且,受台风的影响,家里下了好大的雨,不过倒是清凉了许多。
好了,让我们来优化一下这个效果吧,博主之前还没有处理的一些细节问题,比如这个 QQ 消息拖动,如果我们没有将它拖断掉,也就是线还连着,上次的做法是将它的坐标赋值给初始按下的坐标,这导致的效果是一瞬间就回去了,动画太过生硬,体验不是特别好,接下来我们来优化一下,让它慢慢的回去,有一个过渡时间。
上次的代码是这样做的,直接回到手指起始按下的那一个点位置:
case MotionEvent.ACTION_UP:
if (!isAttached) {
//被扯断了
isShowed = false;
} else if (mSmallCircleRaduis >= mSmallHideRaduis) {//小圆的半径如果大于显示的半径,意味着没有拖段线
isShowed = true;//大圆要显示
//回到原来手指按下的位置
mBigCircleX = mSmallCircleX;
mBigCircleY = mSmallCircleY;
}
mSmallCircleRaduis = 0;//每次手松开,小圆半径规 0
break;
这个肯定不行,要对它的值进行修改,我们的思想是这个样子的,看图
我们需要慢慢的改变大圆的半径,就相当于改变被我们拉出来的那个圆的 x 坐标和 y 坐标,我们给它定一个时间段,让它们一起开始变化,这个就得使用到属性动画来处理了,我们把上部分的代码做如下修改即可
case MotionEvent.ACTION_UP:
if (!isAttached) {
//被扯断了
isShowed = false;
} else if (mSmallCircleRaduis >= mSmallHideRaduis) {//小圆的半径如果大于显示的半径,意味着没有拖段线。松开手,弹回去
isShowed = true;//大圆要显示
animatorSet = new AnimatorSet();
xAnimator = ObjectAnimator.ofFloat(mBigCircleX, mSmallCircleX);
xAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mBigCircleX = (float) animation.getAnimatedValue();
int disCircle = calculateDisCircle(mSmallCircleX, mSmallCircleY, mBigCircleX, mBigCircleY);
mSmallCircleRaduis = mSmallDefRaduis - disCircle / mSmallHideRaduis;//在拖动过程中,小圆半径一直在缩小
}
});
yAnimator = ObjectAnimator.ofFloat(mBigCircleY, mSmallCircleY);
yAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mBigCircleY = (float) animation.getAnimatedValue();
invalidate();
}
});
animatorSet.playTogether(xAnimator, yAnimator);
animatorSet.setInterpolator(new OvershootInterpolator(3f));
animatorSet.setDuration(10000);
animatorSet.start();
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//动画结束时,隐藏小圆
mSmallCircleRaduis = 0;//每次手松开,小圆半径规 0
}
});
}
break;
那么,绘制那个粘性的贝塞尔曲线也要一直绘制了,不能松开就没了吧,所以要把 onDraw 的里面的代码改为如下:
@Override
protected void onDraw(Canvas canvas) {
mBezierPath = drawDragBezier();
//两个圆还有联系
if (mBezierPath != null) {
canvas.drawPath(mBezierPath, mPaint);
}
if (isAttached) {
canvas.drawCircle(mSmallCircleX, mSmallCircleY, mSmallCircleRaduis, mPaint);
}
//如果是显示的
if (isShowed) {
canvas.drawCircle(mBigCircleX, mBigCircleY, BIG_CIRCLE_RADUIS, mPaint);
}
}
好了,一起来看看效果吧。为了使效果更加明显,我特地把缩回来的动画改为 10S,足够你看清楚了吧
我给它加了一个插值器,回来的时候有一个反弹的效果!弹弹弹,弹走鱼尾纹。。。
不过呢,还有一个地方需要优化的,就是拖断掉的时候,再松开会有一个消失的效果,我就搞的简单一点,让它慢慢的消失就好了。不过也可以学那个爆炸效果,会比较炫酷一点,我找了一下那个爆炸的图片,懒得图改成透明颜色了,需要的自己去查一查帧动画就好了。
下面是放快的效果
最后的完整代码
package nd.no.xww.qqmessagedragview;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.OvershootInterpolator;
/**
* @author xww
* @desciption : 仿 QQ 消息拖拽消失的效果(大圆:不会消失,且大小一致。小圆:与大圆的距离协调改变大小)
* @date 2019/8/2
* @time 8:54
* @博主:威威喵
*/
public class QQMessageDragView extends View {
private Paint mPaint;
//大圆
private float mBigCircleX;
private float mBigCircleY;
private float mBigCircleRaduis = 50;
//小圆
private float mSmallCircleX;
private float mSmallCircleY;
private int mSmallDefRaduis = 40;
private int mSmallHideRaduis = 15;//扯断的距离
private int mSmallCircleRaduis = mSmallDefRaduis;
private Bitmap mMessageBitmap;
private boolean isAttached;//代表两个关联
private boolean isFirst = true;//显示大圆
private void init() {
mPaint = new Paint();
mPaint.setDither(true);
mPaint.setAntiAlias(true);
mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
mPaint.setTextSize(30f);
mMessageBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.message);
mMessageBitmap = Bitmap.createScaledBitmap(mMessageBitmap, 150, 150, false);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY ? MeasureSpec.getSize(widthMeasureSpec) : 200
, MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY ? MeasureSpec.getSize(heightMeasureSpec) : 200);
}
public QQMessageDragView(Context context) {
this(context, null);
}
public QQMessageDragView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public QQMessageDragView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@Override
protected void onDraw(Canvas canvas) {
mBezierPath = drawDragBezier();
//两个圆还有联系
if (mBezierPath != null) {
canvas.drawPath(mBezierPath, mPaint);
}
if (isAttached) {
canvas.drawCircle(mSmallCircleX, mSmallCircleY, mSmallCircleRaduis, mPaint);
}
//如果第一次,不绘制圆
if (isFirst) {
return;
}
canvas.drawCircle(mBigCircleX, mBigCircleY, mBigCircleRaduis, mPaint);
}
private float raduis;
AnimatorSet animatorSet;
ValueAnimator xAnimator;
ValueAnimator yAnimator;
@Override
public boolean onTouchEvent(MotionEvent event) {
float downX = event.getX();
float downY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 两个圆关联了
mBigCircleRaduis = 50; // 大圆的初始值
isFirst = false;
isAttached = true;
mSmallCircleRaduis = mSmallDefRaduis;
mSmallCircleX = mBigCircleX = downX;
mSmallCircleY = mBigCircleY = downY;
break;
case MotionEvent.ACTION_MOVE:
mBigCircleX = event.getX();
mBigCircleY = event.getY();
int disCircle = calculateDisCircle(mSmallCircleX, mSmallCircleY, mBigCircleX, mBigCircleY);
mSmallCircleRaduis = mSmallDefRaduis - disCircle / mSmallHideRaduis;//在拖动过程中,小圆半径一直在缩小
if (mSmallCircleRaduis < mSmallHideRaduis) {//小圆的半径如果太小了,不显示了。
isAttached = false;//表示两个圆没有关联了,意味这线被拖断了
}
break;
case MotionEvent.ACTION_UP:
if (!isAttached) { // 被扯断了,两圆没有联系了
ValueAnimator raduisAnimator = ObjectAnimator.ofFloat(mBigCircleRaduis, 0);
raduisAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mBigCircleRaduis = (float) animation.getAnimatedValue();
invalidate();
}
});
raduisAnimator.setDuration(500);
raduisAnimator.start();
} else if (mSmallCircleRaduis >= mSmallHideRaduis) {//小圆的半径如果大于显示的半径,意味着没有拖段线。松开手,弹回去
animatorSet = new AnimatorSet();
xAnimator = ObjectAnimator.ofFloat(mBigCircleX, mSmallCircleX);
xAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mBigCircleX = (float) animation.getAnimatedValue();
int disCircle = calculateDisCircle(mSmallCircleX, mSmallCircleY, mBigCircleX, mBigCircleY);
mSmallCircleRaduis = mSmallDefRaduis - disCircle / mSmallHideRaduis;//在拖动过程中,小圆半径一直在缩小
}
});
yAnimator = ObjectAnimator.ofFloat(mBigCircleY, mSmallCircleY);
yAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mBigCircleY = (float) animation.getAnimatedValue();
invalidate();
}
});
animatorSet.playTogether(xAnimator, yAnimator);
animatorSet.setInterpolator(new OvershootInterpolator(2.5f));
animatorSet.setDuration(500);
animatorSet.start();
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//动画结束时,隐藏小圆
mSmallCircleRaduis = 0;//每次手松开,小圆半径规 0
}
});
}
break;
}
invalidate();
return true;
}
// 两点之间的距离公式 √(x2-x1)²+(y2-y1)²
private int calculateDisCircle(float mSmallCircleX, float mSmallCircleY, float mBigCircleX, float mBigCircleY) {
return (int) Math.sqrt(Math.pow((mSmallCircleX - mBigCircleX), 2) + Math.pow((mSmallCircleY - mBigCircleY), 2));
}
private float p1X;
private float p1Y;
private float p2X;
private float p2Y;
private float p3X;
private float p3Y;
private float p4X;
private float p4Y;
//控制点
private float controlX;
private float controlY;
private float dx, dy;
private double angleA;
private double tanA;
private Path bezierPath;
private Path mBezierPath;
/**
* 贝塞尔 p1 p2 p3 p4 四个点坐标的计算
*
* @return
*/
private Path drawDragBezier() {
if (mSmallCircleRaduis < mSmallHideRaduis || !isAttached) {
return null;
}
dx = mBigCircleX - mSmallCircleX;
dy = mBigCircleY - mSmallCircleY;
tanA = dy / dx;
angleA = Math.atan(tanA);
//控制点的计算
controlX = (mSmallCircleX + mBigCircleX) / 2;
controlY = (mSmallCircleY + mBigCircleY) / 2;
p1X = (float) (mSmallCircleX + Math.sin(angleA) * mSmallCircleRaduis);
p1Y = (float) (mSmallCircleY - Math.cos(angleA) * mSmallCircleRaduis);
p2X = (float) (mBigCircleX + Math.sin(angleA) * mBigCircleRaduis);
p2Y = (float) (mBigCircleY - Math.cos(angleA) * mBigCircleRaduis);
p3X = (float) (mBigCircleX - Math.sin(angleA) * mBigCircleRaduis);
p3Y = (float) (mBigCircleY + Math.cos(angleA) * mBigCircleRaduis);
p4X = (float) (mSmallCircleX - Math.sin(angleA) * mSmallCircleRaduis);
p4Y = (float) (mSmallCircleY + Math.cos(angleA) * mSmallCircleRaduis);
//绘制路径
bezierPath = new Path();
bezierPath.moveTo(p1X, p1Y);
bezierPath.quadTo(controlX, controlY, p2X, p2Y);
bezierPath.lineTo(p3X, p3Y);
bezierPath.quadTo(controlX, controlY, p4X, p4Y);
bezierPath.close();
return bezierPath;
}
}
最后呢,给出本效果的全部代码,期间由于隔了几天再来继续写这个效果,代码的关键处也补了一点点注释。哈哈,隔了几天没去瞧一眼,差点给我整懵逼了,还好,还好。