之前寫的一篇 《初試 貝塞爾曲線》,如果您感興趣可以看看,是關(guān)于貝塞爾曲線的基礎(chǔ)用法。
最近看了很多有關(guān)貝塞爾曲線的文章,感覺大神們都好屌,文章都寫的好好,就是有的時候注釋寫的不太夠,理解起來有點(diǎn)費(fèi)勁,所以我打算研讀過后,補(bǔ)充注釋,以便其他同學(xué)方便學(xué)習(xí)~~
效果圖
開始本文之前先查看一下目標(biāo)效果是如何的。

這個動畫的來源是優(yōu)秀網(wǎng)頁設(shè)計(jì)的一個微博,看到這個效果感覺下面的圓的動畫十分的贊,于是就打算模仿這個效果。
然后接下來看我所做的簡單效果吧。

因?yàn)闀r間緣故就簡單的模仿了自己感興趣的主要效果,并沒有做到全部模仿,等以后有時間了再完善
貝塞爾曲線知識講解
將這個圓的動畫效果拆解開看的畫,其實(shí)會分為5個狀態(tài)。





注:我這里的狀態(tài)4和狀態(tài)5其實(shí)與原設(shè)計(jì)圖是有出入的,我這里的5個狀態(tài)其實(shí)是對稱關(guān)系的,但是原設(shè)計(jì)圖并非是對稱關(guān)系,然后因?yàn)槲彝祽?,就做成了對稱設(shè)計(jì),這個以后再優(yōu)化吧~
也就是說,這個動畫效果的實(shí)現(xiàn)就是不同狀態(tài)之間的轉(zhuǎn)化加上水平位移的實(shí)現(xiàn),現(xiàn)在已經(jīng)將動畫肢解完了,那么接下來就講解下如何實(shí)現(xiàn)圓的形變吧。
開始講解具體內(nèi)容之前我們需要先了解一下如何用貝塞爾曲線畫一個圓,因?yàn)槲业淖龇ㄊ峭ㄟ^貝塞爾曲線來實(shí)現(xiàn)的。

上圖是stackoverflow上的一個答案,這個答案可能說的不是很清楚,這里還有一篇文章,這兩個的結(jié)果都是差不多,就是所需要的數(shù)值c約等于0.551915024494f,具體的論證過程可以看這兩篇文章,那么這個c的值有什么用么,我用最簡單的方法來說明,就是把圖中的1理解為圓的半徑,那么對應(yīng)的另外個值就應(yīng)該是半徑乘以0.551915024494f。
可能有朋友還是看不懂這個c到底干啥用呢,我下面畫一個圖來描述下,大概是怎么個意思。

這里的坐標(biāo)軸也就是Android中的坐標(biāo)軸了,如果我們打算用貝塞爾曲線來畫這么一個圓的話,我們需要知道這個圓的半徑,以及圖中的M的值,知道這兩個值的話就能夠知道圖中12個點(diǎn)的坐標(biāo),知道坐標(biāo)就能夠用Path的cubicTo方法來使用貝塞爾曲線畫出圓了。
這里稍微展示點(diǎn)代碼來說明如何繪制P0至P3這段圓弧。
mPath.moveTo(p0.x,p0.y);
mPath.cubicTo(p1.x, p1.y, p2.x, p2.y, p3.x,p3.y);
這樣我們就知道如何使用貝塞爾曲線來繪制一個圓了。也就是狀態(tài)1和狀態(tài)5我們都會繪制了,接下來看看狀態(tài)2如何繪制。

通過上圖大家就能很快的明白狀態(tài)2應(yīng)該如何繪制,其實(shí)就是把右邊的點(diǎn)向右移動點(diǎn)距離就行了。
其實(shí)photoshop(sketch)這些繪圖軟件中的鋼筆工具(Vector)就是用的貝塞爾曲線,然后這里推薦個網(wǎng)址給大家,輕松上手鋼筆工具的使用哦,強(qiáng)烈推薦?。?!
看完上面的講解,那么狀態(tài)3也就一點(diǎn)都不難了。

看到上圖就明白狀態(tài)3的實(shí)現(xiàn)就是在狀態(tài)2的基礎(chǔ)上修改了個值,一個是M的值加大,讓圓看起來跟肥一點(diǎn),還有就是圈住的那些點(diǎn)向右移動,做到居中。
至此,這個動畫效果的分解也就完成了,其實(shí)一點(diǎn)都不難。最后還剩一個回彈,我的做法就是加個sin函數(shù)來控制,比較簡單 。
示例代碼
代碼中的四個點(diǎn),方便理解

/**
* Created by liuboyu on 17/1/12.
*/
public class MagicCircleView extends View implements View.OnClickListener {
private Path mPath;
private Paint mFillCirclePaint;
/**
* View的寬度
**/
private int width;
/**
* View的高度,這里View應(yīng)該是正方形,所以寬高是一樣的
**/
private int height;
/**
* View的中心坐標(biāo)x
**/
private int centerX;
/**
* View的中心坐標(biāo)y
**/
private int centerY;
private float maxLength;
/**
* 平移動化時間(值總在0-1之間)
*/
private float mInterpolatedTime;
/**
* 拉伸長度
*/
private float stretchDistance;
/**
* 圓球半徑
*/
private float radius;
private float c;
private float blackMagic = 0.551915024494f;
private VPoint p2, p4;
private HPoint p1, p3;
public MagicCircleView(Context context) {
super(context);
init();
}
public MagicCircleView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MagicCircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* 初始化
*/
private void init() {
mFillCirclePaint = new Paint();
mFillCirclePaint.setColor(0xFFfe626d);
mFillCirclePaint.setStyle(Paint.Style.FILL);
mFillCirclePaint.setStrokeWidth(1);
mFillCirclePaint.setAntiAlias(true);
mPath = new Path();
p2 = new VPoint();
p4 = new VPoint();
p1 = new HPoint();
p3 = new HPoint();
}
/**
* 初始化圓的各個點(diǎn)
*/
private void initCirclePoint() {
p1.setY(radius);
p3.setY(-radius);
p3.x = p1.x = 0;
p3.left.x = p1.left.x = -c;
p3.right.x = p1.right.x = c;
p2.setX(radius);
p4.setX(-radius);
p2.y = p4.y = 0;
p2.top.y = p4.top.y = -c;
p2.bottom.y = p4.bottom.y = c;
setOnClickListener(this);
}
/**
* 繪圖
*/
private void drawCircle(float interpolatedTime) {
if (mInterpolatedTime >= 0 && mInterpolatedTime <= 0.2) {
drawCircleStatus_1(interpolatedTime);
} else if (mInterpolatedTime > 0.2 && mInterpolatedTime <= 0.5) {
drawCircleStatus_2(interpolatedTime);
} else if (mInterpolatedTime > 0.5 && mInterpolatedTime <= 0.8) {
drawCircleStatus_3(interpolatedTime);
} else if (mInterpolatedTime > 0.8 && mInterpolatedTime <= 0.9) {
drawCircleStatus_4(interpolatedTime);
} else if (mInterpolatedTime > 0.9 && mInterpolatedTime <= 1) {
drawCircleStatus_5(interpolatedTime);
}
}
/**
* 狀態(tài)1
*
* @param interpolatedTime 為當(dāng)前動畫幀對應(yīng)的相對時間,值總在0-1之間
*/
private void drawCircleStatus_1(float interpolatedTime) {//0~0.2
initCirclePoint();
p2.adjustAllX(stretchDistance * interpolatedTime * 5);
}
/**
* 狀態(tài)2
*
* @param interpolatedTime 為當(dāng)前動畫幀對應(yīng)的相對時間,值總在0-1之間
*/
private void drawCircleStatus_2(float interpolatedTime) {//0.2~0.5
drawCircleStatus_1(0.20f);
p4.adjustAllX(-stretchDistance * (interpolatedTime - 0.2f) * (10f / 3));
}
/**
* 狀態(tài)3
*
* @param interpolatedTime 為當(dāng)前動畫幀對應(yīng)的相對時間,值總在0-1之間
*/
private void drawCircleStatus_3(float interpolatedTime) {//0.5~0.8
drawCircleStatus_2(0.5f);
p2.adjustAllX(-stretchDistance * (interpolatedTime - 0.5f) * (10f / 3));
}
/**
* 狀態(tài)4
*
* @param interpolatedTime 為當(dāng)前動畫幀對應(yīng)的相對時間,值總在0-1之間
*/
private void drawCircleStatus_4(float interpolatedTime) {//0.8~0.9
drawCircleStatus_3(0.8f);
p4.adjustAllX(stretchDistance * (interpolatedTime - 0.8f) * 10);
}
/**
* 狀態(tài)5
*
* @param interpolatedTime 為當(dāng)前動畫幀對應(yīng)的相對時間,值總在0-1之間
*/
private void drawCircleStatus_5(float interpolatedTime) {
drawCircleStatus_4(0.9f);
interpolatedTime = interpolatedTime - 0.9f;
p4.adjustAllX((float) (Math.sin(Math.PI * interpolatedTime * 10f) * (2 / 10f * radius)));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPath.reset();
canvas.translate(2 * radius, radius);
drawCircle(mInterpolatedTime);
// 平移
float offset = maxLength * (mInterpolatedTime - 0.2f);
offset = offset > 0 ? offset : 0;
p1.adjustAllX(offset);
p2.adjustAllX(offset);
p3.adjustAllX(offset);
p4.adjustAllX(offset);
// 圓球效果
mPath.moveTo(p1.x, p1.y);
mPath.cubicTo(p1.right.x, p1.right.y, p2.bottom.x, p2.bottom.y, p2.x, p2.y);
mPath.cubicTo(p2.top.x, p2.top.y, p3.right.x, p3.right.y, p3.x, p3.y);
mPath.cubicTo(p3.left.x, p3.left.y, p4.top.x, p4.top.y, p4.x, p4.y);
mPath.cubicTo(p4.bottom.x, p4.bottom.y, p1.left.x, p1.left.y, p1.x, p1.y);
canvas.drawPath(mPath, mFillCirclePaint);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
centerX = width / 2;
centerY = height / 2;
radius = 100;
c = radius * blackMagic;
stretchDistance = radius;
maxLength = width - radius - radius;
}
@Override
public void onClick(View v) {
startAnimation();
}
class VPoint {
public float x;
public float y;
public PointF top = new PointF();
public PointF bottom = new PointF();
public void setX(float x) {
this.x = x;
top.x = x;
bottom.x = x;
}
public void adjustY(float offset) {
top.y -= offset;
bottom.y += offset;
}
public void adjustAllX(float offset) {
this.x += offset;
top.x += offset;
bottom.x += offset;
}
}
class HPoint {
public float x;
public float y;
public PointF left = new PointF();
public PointF right = new PointF();
public void setY(float y) {
this.y = y;
left.y = y;
right.y = y;
}
public void adjustAllX(float offset) {
this.x += offset;
left.x += offset;
right.x += offset;
}
}
/**
* 重寫動畫
*/
private class MoveAnimation extends Animation {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
// interpolatedTime 為當(dāng)前動畫幀對應(yīng)的相對時間,值總在0-1之間
mInterpolatedTime = interpolatedTime;
invalidate();
}
}
public void startAnimation() {
mPath.reset();
mInterpolatedTime = 0;
MoveAnimation move = new MoveAnimation();
move.setDuration(3000);
move.setInterpolator(new AccelerateDecelerateInterpolator());
startAnimation(move);
}
}
在看代碼的時候,可能會比較蒙,分狀態(tài)畫圓球的時候,有的*10,有的*5,有的又* (10f / 3),其實(shí)很好懂,只是為了把時間變化值調(diào)整為0-1,以便相乘來操作距離的變化
拿狀態(tài)1舉例吧
/**
* 狀態(tài)1
*
* @param interpolatedTime 為當(dāng)前動畫幀對應(yīng)的相對時間,值總在0-1之間
*/
private void drawCircleStatus_1(float interpolatedTime) {//0~0.2
initCirclePoint();
p2.adjustAllX(stretchDistance * interpolatedTime * 5);
}
p2.adjustAllX(stretchDistance * interpolatedTime * 5);
狀態(tài)1 中,在時間后面乘了個 5,因?yàn)闋顟B(tài)一,時間上是 0~0.2,為了拉伸一整個stretchDistance,所以乘了個5,其他狀態(tài)以此類推。
行了,后續(xù)還會更新相關(guān)貝塞爾曲線的文章,如果您喜歡,請關(guān)注~~