貝塞爾曲線在Android中的應用
今天要講解的內容是Android中貝塞爾曲線的應用。可能很多人對貝塞爾曲線不甚瞭解,這裡先對它的概念做一下簡單介紹。
貝塞爾曲線由多個點組成:起始點、終止點以及0到n個相互分離的中間點。根據中間點的不同,可以分為線性貝塞爾曲線、二階貝塞爾曲線、三階貝塞爾曲線和高階貝塞爾曲線。一般的向量圖形軟體通過它來精確畫出曲線,貝塞爾曲線由線段與節點組成,節點是可拖動的支點,線段像可伸縮的皮筋。對於三階貝塞爾曲線,它由兩個錨點P0、P3和兩個中間點P1、P2組成。曲線起始於P0走向P1,並從P2的方向來到P3。曲線一般不會經過P1和P2,這兩個點只是提供方向資訊。P0和P1之間的間距,決定了曲線在轉而趨進P3之前,走向P2方向的“長度有多長”。關於貝塞爾曲線的更多內容,可以從這裡瞭解:
下面是二階、三階和四階曲線的效果圖,紅色曲線為最終繪製出來的結果,大家可以瞭解一下。
有個網站為我們提供了工具,可以生成對應的二階貝塞爾曲線的數值:cubic-bezier.com。拖拽左邊影象中的2箇中間點,就會在右邊顯示兩個中間點歸一化的座標值。我們還可以點選SAVE按鈕將曲線儲存下來,然後點選GO就可以檢視通過當前曲線計算出來的差值器的效果。具體功能看下面的圖。
瞭解了貝塞爾曲線的基本概念,下面來看一下貝塞爾曲線在Android中的具體應用。貝塞爾曲線在Android中主要有三個用途:
動畫差值器
我們知道Android動畫可以設定差值器Interpolator來修飾動畫效果,其作用是把0到1的浮點值變化對映到另一個浮點值變化,然後將這個值作為動畫的變化率。定義差值器需要實現Interpolator介面,然後根據三階貝塞爾曲線的公式,得到下面的差值器。其中p1、p2是兩個中間點的座標,需要呼叫者傳入來得到不同的曲線。如果我們要得到曲線橫軸方向上的變化率,就將兩個中間點的y座標帶入公式;否則,就將兩個中間點的x座標帶入公式。
繪製動畫軌跡public class BezierInterpolator implements Interpolator { float p1; float p2; BezierInterpolator(float p1, float p2) { this.p1 = p1; this.p2 = p2; } @Override public float getInterpolation(float t) { return 0 * (1 - t) * (1 - t) * (1 - t) + 3 * p1 * t * (1 - t) * (1 - t) + 3 * p2 * t * t * (1 - t) + 1 * t * t * t; } }
說明這個問題之前,必須先了解屬性動畫中的TypeEvaluator。TypeEvaluator有什麼作用呢?簡單來說,就是告訴系統動畫如何從初始值過度到結束值。其實這個TypeEvaluator也可以理解為差值器,只是它的定義是泛型的,我們可以將泛型引數設定為PointF,就能得到當前運動軌跡的座標位置。而Interpolator只是一個線性的差值器,只能得到運動速率的變化,不能得到運動軌跡。如果還不理解,就看下面的程式碼吧。
public class BezierEvaluator implements TypeEvaluator<PointF> {
PointF mPointF1;
PointF mPointF2;
public BezierEvaluator(PointF mPointF1, PointF mPointF2) {
this.mPointF1 = mPointF1;
this.mPointF2 = mPointF2;
}
@Override
public PointF evaluate(float t, PointF point0, PointF point3) {
//t 百分比 0~1
PointF pointF = new PointF();
pointF.x = point0.x * (1 - t) * (1 - t) * (1 - t)
+ 3 * mPointF1.x * t * (1 - t) * (1 - t)
+ 3 * mPointF2.x * t * t * (1 - t)
+ point3.x * t * t * t;
pointF.y = point0.y * (1 - t) * (1 - t) * (1 - t)
+ 3 * mPointF1.y * t * (1 - t) * (1 - t)
+ 3 * mPointF2.y * t * t * (1 - t)
+ point3.y * t * t * t;
return pointF;
}
}
private ValueAnimator getBezierValueAnimator() {
PointF pointf0 = new PointF(0, 0);
PointF pointf1 = new PointF(width / 8, height * 7 / 8);
PointF pointf2 = new PointF(width * 7 / 8, height / 8);
PointF pointf3 = new PointF(width - 100, height - 100);
//通過貝塞爾曲線公式,自定義估值器
final BezierEvaluator evaluator = new BezierEvaluator(pointf1, pointf2);
//將估值器傳入屬性動畫,不斷的修改控制元件的座標
ValueAnimator animator = ValueAnimator.ofObject(evaluator, pointf0, pointf3);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF pointf = (PointF) animation.getAnimatedValue();
mImageView.setX(pointf.x);
mImageView.setY(pointf.y);
}
});
animator.setTarget(mImageView);
animator.setDuration(3000);
animator.setInterpolator(new BezierInterpolator(1.61f, -0.26f));
return animator;
}
實現平滑繪圖
實現一個繪圖功能,基本思路就是處理Touch事件,通過呼叫lineTo去更新Path,然後通過Canvas的drawPath不斷地重繪。然而,這樣繪出來的曲線在彎曲處不平滑。如果要繪製出平滑的曲線,就需要用到貝塞爾曲線。Android的Path類的quadTo方法恰恰就封裝了二階貝塞爾曲線的功能。下面看具體的實現程式碼。
public class BezierDrawView extends View {
private float mX;
private float mY;
private final Paint mGesturePaint = new Paint();
private final Path mPath = new Path();
public BezierDrawView(Context context) {
this(context, null);
}
public BezierDrawView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BezierDrawView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mGesturePaint.setAntiAlias(true);
mGesturePaint.setStyle(Paint.Style.STROKE);
mGesturePaint.setStrokeWidth(8);
mGesturePaint.setColor(Color.RED);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
handleDown(event);
break;
case MotionEvent.ACTION_MOVE:
handleMove(event);
}
invalidate();
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(mPath, mGesturePaint);
}
private void handleDown(MotionEvent event) {
mPath.reset();
float x = event.getX();
float y = event.getY();
mX = x;
mY = y;
mPath.moveTo(x, y);
}
private void handleMove(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final float preX = mX;
final float preY = mY;
final float dx = Math.abs(x - preX);
final float dy = Math.abs(y - preY);
//兩點之間的距離大於等於3時,生成貝塞爾繪製曲線
if (dx >= 3 || dy >= 3) {
//設定貝塞爾曲線的操作點為起點和終點的一半
float cX = (x + preX) / 2;
float cY = (y + preY) / 2;
//二次貝塞爾,實現平滑曲線;previousX, previousY為操作點,cX, cY為終點
mPath.quadTo(preX, preY, cX, cY);
// mPath.lineTo(x, y);
mX = x;
mY = y;
}
}
}
對比一下通過quadTo和lineTo繪製出來曲線的不同,左邊的曲線比右邊的曲線要平滑一些,仔細看右邊的圖會發現曲線上有明顯的拐角。
下面是貝塞爾曲線在Android中應用的最終動效圖,我們把三個功能整合到了一個頁面中。
參考文章:
http://www.cnblogs.com/benhero/p/4377374.html?utm_source=tuicool&utm_medium=referral
http://blog.csdn.net/androidzhaoxiaogang/article/details/8680330