1. 程式人生 > >貝塞爾曲線在Android中的應用

貝塞爾曲線在Android中的應用

       今天要講解的內容是Android中貝塞爾曲線的應用。可能很多人對貝塞爾曲線不甚瞭解,這裡先對它的概念做一下簡單介紹。

       貝塞爾曲線由多個點組成:起始點、終止點以及0到n個相互分離的中間點。根據中間點的不同,可以分為線性貝塞爾曲線、二階貝塞爾曲線、三階貝塞爾曲線和高階貝塞爾曲線。一般的向量圖形軟體通過它來精確畫出曲線,貝塞爾曲線由線段與節點組成,節點是可拖動的支點,線段像可伸縮的皮筋。對於三階貝塞爾曲線,它由兩個錨點P0、P3和兩個中間點P1、P2組成。曲線起始於P0走向P1,並從P2的方向來到P3。曲線一般不會經過P1和P2,這兩個點只是提供方向資訊。P0和P1之間的間距,決定了曲線在轉而趨進P3之前,走向P2方向的“長度有多長”。關於貝塞爾曲線的更多內容,可以從這裡瞭解:

http://blog.csdn.net/androidzhaoxiaogang/article/details/8680330

下面是二階、三階和四階曲線的效果圖,紅色曲線為最終繪製出來的結果,大家可以瞭解一下。

                                       

       有個網站為我們提供了工具,可以生成對應的二階貝塞爾曲線的數值: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