博主聲明:
轉載請在開頭附加本文鏈接及作者信息,并標記為轉載。本文由博主 威威喵 原創,請多支持與指教。
這幾天突然發現 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)2+(y2-y1)2
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)2+(y2-y1)2
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;
}
}
最后呢,給出本效果的全部代碼,期間由于隔了幾天再來繼續寫這個效果,代碼的關鍵處也補了一點點注釋。哈哈,隔了幾天沒去瞧一眼,差點給我整懵逼了,還好,還好。