Android -- 貝塞爾使圓漸變為桃心


1,我們上一篇介紹了貝塞爾曲線推到原理和在Android里的簡單使用,今天就和來寫寫貝塞爾曲線的實際應用,今天實現的效果圖如下:

2,思路分析

  我們知道首先我們的view是一個圓,這里的圓其實是由四塊三階貝塞爾曲線組成的,左上、右上、左下、右下這四塊貝塞爾曲線組成,那么讓我們來開始吧

  • 准備階段

  創建一個類MyViewCircle繼承自View,重寫構造方法,重寫onDraw()方法

public class MyViewCircle extends View {
    public MyViewCircle(Context context) {
        this(context, null);
    }

    public MyViewCircle(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyViewCircle(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    @Override
    protected void onDraw(Canvas canvas) {

    }
}
  • 繪制X、Y軸

  由於我們的view是展示在屏幕的正中央的,為了我們以后標識點方便,這里我們以屏幕的正中心為(0,0)坐標繪制出來

  在onSizeChange()方法中獲得mCenterX、mCenterY的坐標,繪制X,Y軸,代碼如下:

 private int mCenterX;
 private int mCenterY;
 private Paint mPaint;
//省略代碼.........................
     //初始化畫筆
        mPaint = new Paint();
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(3);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setAntiAlias(true);
    
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        //初始化坐標系
        mCenterX = getWidth() / 2;
        mCenterY = getHeight() / 2;
}

    @Override
    protected void onDraw(Canvas canvas) {
     canvas.save();
        //繪制x,y軸坐標系
        canvas.drawLine(mCenterX, 0, mCenterX, getHeight(), mPaint);
        canvas.drawLine(0, mCenterY, getWidth(), mCenterY, mPaint);

        canvas.restore();
}

 運行效果如下:

 

  • 從三階貝塞爾曲線得到半圓的效果

  我們上一篇簡單的介紹了下三階貝塞爾,當我們的兩個控制點距離數據點為一下坐標時我們繪制出來的曲線是類似於四分之一圓弧的,效果圖如下:

  關於怎么計算出這兩個控制點相對於數據點的相對坐標的,這是一個難點,不過還好,在stackoverflow上有人計算出來了,這是鏈接,我們可以得出一個常量0.552284749831,即相對於原點坐標,半徑是mCircleRadius我們的坐標的0.55228倍,這里我為了方便,直接取了0.5,所以這就解釋了為什么我們效果圖中的圓有點癟(保持微笑)

  • 繪制數據點

  這里我們四個數據點分別是的是半徑為mCircleRadius,圓心坐標為(mCenterX,mCenterY)的圓與我們X、Y軸的焦點,繪制代碼如下:

    private Paint mPaintCircle;
    private Paint mPaintPoint;
    private int mCircleRadius;
    private List<PointF> mPointDatas; //放置四個數據點的集合
    private List<PointF> mPointControlls;//方式8個控制點的集合

    //省略代碼.....
    //初始化數據
        mPaintCircle = new Paint();
        mPaintCircle.setColor(Color.RED);
        mPaintCircle.setStrokeWidth(10);
        mPaintCircle.setStyle(Paint.Style.STROKE);
        mPaintCircle.setAntiAlias(true);

        mPaintPoint = new Paint();
        mPaintPoint.setColor(Color.BLACK);
        mPaintPoint.setStrokeWidth(5);
        mPaintPoint.setStyle(Paint.Style.FILL);
        mPaintPoint.setAntiAlias(true);

        mCircleRadius = 150;
    
    //在onSizeChange方法中初始化四個數據點
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        //初始化坐標系
        mCenterX = getWidth() / 2;
        mCenterY = getHeight() / 2;

        mPointDatas = new ArrayList<>();
        mPointControlls = new ArrayList<>();

        mPointDatas.add(new PointF(mCenterX, mCenterY - mCircleRadius));
        mPointDatas.add(new PointF(mCenterX + mCircleRadius, mCenterY));
        mPointDatas.add(new PointF(mCenterX, mCenterY + mCircleRadius));
        mPointDatas.add(new PointF(mCenterX - mCircleRadius, mCenterY));
    }
    //在onDraw方法中繪制數據點
    @Override
    protected void onDraw(Canvas canvas) {
        //繪制x,y軸坐標系
        canvas.drawLine(mCenterX, 0, mCenterX, getHeight(), mPaint);
        canvas.drawLine(0, mCenterY, getWidth(), mCenterY, mPaint);
        //繪制數據點
        for (int i = 0; i < mPointDatas.size(); i++) {
            canvas.drawPoint(mPointDatas.get(i).x, mPointDatas.get(i).y, mPaintPoint);
        }
    }    

  效果圖如下:

  • 繪制控制點

  由我們上面的的到的常量0.552284749831,這里使用的是0.5,所以,代碼如下

 @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        //初始化坐標系
        mCenterX = getWidth() / 2;
        mCenterY = getHeight() / 2;

        mPointDatas = new ArrayList<>();
        mPointControlls = new ArrayList<>();

        mPointDatas.add(new PointF(mCenterX, mCenterY - mCircleRadius));
        mPointDatas.add(new PointF(mCenterX + mCircleRadius, mCenterY));
        mPointDatas.add(new PointF(mCenterX, mCenterY + mCircleRadius));
        mPointDatas.add(new PointF(mCenterX - mCircleRadius, mCenterY));

        mPointControlls.add(new PointF(mCenterX + mCircleRadius / 2, mCenterY - mCircleRadius));
        mPointControlls.add(new PointF(mCenterX + mCircleRadius, mCenterY - mCircleRadius / 2));

        mPointControlls.add(new PointF(mCenterX + mCircleRadius, mCenterY + mCircleRadius / 2));
        mPointControlls.add(new PointF(mCenterX + mCircleRadius / 2, mCenterY + mCircleRadius));

        mPointControlls.add(new PointF(mCenterX - mCircleRadius / 2, mCenterY + mCircleRadius));
        mPointControlls.add(new PointF(mCenterX - mCircleRadius, mCenterY + mCircleRadius / 2));

        mPointControlls.add(new PointF(mCenterX - mCircleRadius, mCenterY - mCircleRadius / 2));
        mPointControlls.add(new PointF(mCenterX - mCircleRadius / 2, mCenterY - mCircleRadius));
    }

  //在onDraw方法中添加控制點的繪制
@Override
    protected void onDraw(Canvas canvas) {
     //繪制x,y軸坐標系
        canvas.drawLine(mCenterX, 0, mCenterX, getHeight(), mPaint);
        canvas.drawLine(0, mCenterY, getWidth(), mCenterY, mPaint);
        //繪制數據點
        canvas.save();
        for (int i = 0; i < mPointDatas.size(); i++) {
            canvas.drawPoint(mPointDatas.get(i).x, mPointDatas.get(i).y, mPaintPoint);
        }
        //繪制控制點
        for (int i = 0; i < mPointControlls.size(); i++) {
            canvas.drawPoint(mPointControlls.get(i).x, mPointControlls.get(i).y, mPaintPoint);
        }
    }

  繪制后效果圖如下:

  • 繪制三階貝塞爾曲線

  先來繪制右上角四分之一圓弧的貝塞爾曲線看看效果,在onDraw中調用如下代碼:

    //利用三階貝塞爾曲線實現畫圓
        Path path = new Path();
        path.moveTo(mPointDatas.get(0).x, mPointDatas.get(0).y);
path.cubicTo(mPointControlls.get(0).x, mPointControlls.get(0).y, mPointControlls.get(1).x, mPointControlls.get(1).y, mPointDatas.get(1)x, mPointDatas.get(1).y);

    //繪制
    canvas.drawPath(path, mPaintCircle);

  看一下效果:

  看到了吧  ,效果可以吧,然后我們繼續來把后面三條弧線繪制玩,代碼如下:

//利用三階貝塞爾曲線實現畫圓
        Path path = new Path();
        path.moveTo(mPointDatas.get(0).x, mPointDatas.get(0).y);
        for (int i = 0; i < mPointDatas.size(); i++) {
            if (i == mPointDatas.size() - 1) {
                path.cubicTo(mPointControlls.get(2 * i).x, mPointControlls.get(2 * i).y, mPointControlls.get(2 * i + 1).x, mPointControlls.get(2 * i + 1).y, mPointDatas.get(0).x, mPointDatas.get(0).y);

            } else {
                path.cubicTo(mPointControlls.get(2 * i).x, mPointControlls.get(2 * i).y, mPointControlls.get(2 * i + 1).x, mPointControlls.get(2 * i + 1).y, mPointDatas.get(i + 1).x, mPointDatas.get(i + 1).y);
            }

        }
        canvas.drawPath(path, mPaintCircle);

  效果如下:

  • 改變數據點和控制點,重繪制view

  這一步最關鍵,先來看看我們下面的動畫效果

   可以看到我們上面動畫實現的效果移動有五個點變了:一個Y正軸上數據點改變,四個Y負軸控制點改變了,ok,知道了這些了我們基本上知道了怎么實現了,代碼如下:

    private int mDuration = 1000; //動畫總時間
    private int mCurrTime = 0;  //當前已進行時間
    private int mCount = 100;//將總時間划分多少塊
    private float mPiece = mDuration / mCount; //每一塊的時間 ;

    //在onDraw()方法中動態的刷新view,有人肯定會問,120,80之類的怎么的出來的,我只能說調出來的好嘛(手動微笑),代碼如下:
    //動態改變數據點和輔助點
           mCurrTime += mPiece;
           if (mCurrTime < mDuration) {
               mPointDatas.get(0).y += 120 / mCount;
               mPointControlls.get(2).x -= 20.0 / mCount;

               mPointControlls.get(3).y -= 80.0 / mCount;
               mPointControlls.get(4).y -= 80.0 / mCount;
               mPointControlls.get(5).x += 20.0 / mCount;

               postInvalidateDelayed((long) mPiece);
           }    

  ok,這樣我們基本上全部完成了,看一下效果

  ok,這樣我們就實現這個效果了,有沒有很簡單,有需要源碼的同學可以在我的Github下載,明天繼續,See You Next Time!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM