貝塞爾曲線原理及應用

今天在學習貝塞爾曲線的過程中覺得很新奇,特別是之前覺得很神祕的東西一下全部融會貫通了,爲了實踐,特地寫了一個demo——波浪圖,先看效果圖:

這裏寫圖片描述

紙上得來終覺淺,絕知此事要躬行!本來覺得挺簡單的一件事結果各種坑!什麼,你說貝塞爾曲線不簡單?no,no,看看大神們是怎麼總結的?

二階貝塞爾曲線形成原理:

1.連接 A,B 形成 AB 線段,連接 B,C 形成 BC 線段。

連成AB,BC線段

2.在 AB 線段取一個點 D,BC 線段取一個點 E ,使其滿足條件: AD/AB = BE/BC,連接 D,E 形成線段 DE。

連接DE

3.在 DE 取一個點 F,使其滿足條件:AD/AB = BE/BC = DF/DE。

這裏寫圖片描述

4.而滿足這些條件的所有的F點所形成的軌跡就是二階貝塞爾曲線,動態過程如下:

這裏寫圖片描述

上面的代碼解釋得很清楚了,已知D,可以求得E,已知D、E可以求得F,如果D爲一個變量,則F在二維平面就是一條曲線,如圖四。

那麼知道了二階貝塞爾曲線的原理以後,怎麼運用呢?

方法預覽:

public void quadTo (float x1, float y1, float x2, float y2)

怎麼用:

因爲二階貝塞爾曲線需要三個點才能確定,所以quadTo方法中的四個參數分別是確定第二,第三的點的。第一個點就是path上次操作的點。
現在用一個實例來練習下這個方法:

水波紋效果:

這裏寫圖片描述

實現思路:

這裏寫圖片描述

我看見這幅圖的第一思路是首先得到波浪長度mWL、波浪中心點mCenterY。因爲圖中的波浪寬度爲整個屏幕寬度,所以我們需要得到屏幕的寬度。百度一下方法就出來了:

方法一:

       //獲取屏幕的寬度  
    public static int getScreenWidth(Context context) {  
        WindowManager manager = (WindowManager) context  
                .getSystemService(Context.WINDOW_SERVICE);  
        Display display = manager.getDefaultDisplay();  
        return display.getWidth();  
    }  
    //獲取屏幕的高度  
    public static int getScreenHeight(Context context) {  
        WindowManager manager = (WindowManager) context  
                .getSystemService(Context.WINDOW_SERVICE);  
        Display display = manager.getDefaultDisplay();  
        return display.getHeight();  
    }  

方法二:

DisplayMetrics  dm = new DisplayMetrics();      

getWindowManager().getDefaultDisplay().getMetrics(dm);      

int screenWidth = dm.widthPixels;                

int screenHeight = dm.heightPixels;    

由於第二種方法只能在Activity裏面使用,所以這裏使用第一種方法。但是在寫的過程中我又發現一個提醒:

這裏寫圖片描述

有強迫症的我馬上去官網查了一下,發現官網推薦的是另一個方法,於是馬上就改用了另一個方法:

這裏寫圖片描述

這裏寫圖片描述

於是,我們現在就可以根據波浪的寬度和中心點繪製一條靜態的波浪線,代碼如下:

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPath.reset();
        mPath.moveTo(-mWL, mCenterY); //將path操作的起點移動到(-mWL,mCenterY)

       mPath.quadTo((-mWL * 3 / 4) , mCenterY + 60, (-mWL / 2), mCenterY); //畫出第一段波紋的第一條曲線
       mPath.quadTo((-mWL / 4) , mCenterY - 60, 0, mCenterY); //畫出第一段波紋的第二條曲線
       mPath.quadTo((mWL /4) , mCenterY + 60, (mWL / 2), mCenterY); //畫出第二段波紋的第一條曲線
       mPath.quadTo((mWL * 3/ 4) , mCenterY - 60, mWL, mCenterY);  //畫出第二段波紋的第二條曲線

        mPath.lineTo(mScreenWidth, mScreenHeight);
        mPath.lineTo(0, mScreenHeight);
        mPath.close();
        canvas.drawPath(mPath,mPaint);

    }

查看加載效果的時候發現一個坑,就是榮耀的真機顯示繪製結果的時候左邊居然有一條很寬的線,這個時候爲了檢查不是貝塞爾曲線的問題,我還在貝塞爾曲線X方向起點到終點繪製了一條直線,但是依然如此。

這裏寫圖片描述

然後我用自己的小米5測試了一下,居然完全沒有任何問題:

這裏寫圖片描述

雖然後來發現是因爲使用了ConstraintLayout佈局,然後自定義控件左邊距有8dp,然後給自定義控件寫死了寬度,因此小米5的寬度剛剛合適,,,,,

修改了以後正常了,於是繼續將靜態的波浪線調整成動態的波浪線。其實邏輯是很簡單,就是給自定義View一個點擊事件,然後在時間中啓動一個屬性動畫,動態的修改波浪線水平方向右移的位置,全部代碼寫出來:




/**
 * 自定義波浪圖
 * Created by 魏興 on 2017/6/12.
 */

public class WaveView1 extends View implements View.OnClickListener{

    private static final String TAG = "WaveView";
    private Context mContext;
    /**
     * 波紋段長度
     */
    private int mWL;
    /**
     * 波浪高度中點
     */
    private float mCenterY;
    private float mScreenWidth;
    private float mScreenHeight;
    private int mOffset;
    private int mWaveCount;
    private Path mPath;
    private Paint mPaint;


    private Paint mCyclePaint;


    public WaveView1(Context context) {
        super(context);
        this.mContext = context;
//        init();
    }

    public WaveView1(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
//        init();
    }

    public WaveView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
//        init();
    }

    private void init() {
        WindowManager manager = (WindowManager) mContext .getSystemService(Context.WINDOW_SERVICE);
        Display display = manager.getDefaultDisplay();

        Point po = new Point();
        display.getSize(po);
        mWL = po.x;

        mCyclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mCyclePaint.setColor(Color.RED);
        mCyclePaint.setStyle(Paint.Style.FILL_AND_STROKE);
    }

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

        mPaint = new Paint();       // 創建畫筆
        mPaint.setColor(Color.BLUE);  // 畫筆顏色 - 黑色
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);  // 填充模式 - 填充

        mPath = new Path();

        mWL = w*3/4;

        mCenterY = h/2f;
        mScreenHeight = h;
        mScreenWidth = w;
        //加1.5:至少保證波紋有2個,至少2個才能實現平移效果
        mWaveCount = (int) Math.round(mScreenWidth / mWL + 1.5);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPath.reset();
        //移到屏幕外最左邊
        mPath.moveTo(-mWL + mOffset, mCenterY);

        for (int i = 0; i < mWaveCount; i++) {
            //正弦曲線
            mPath.quadTo((-mWL * 3 / 4) + (i * mWL) + mOffset, mCenterY + 60, (-mWL / 2) + (i * mWL) + mOffset, mCenterY);
            mPath.quadTo((-mWL / 4) + (i * mWL) + mOffset, mCenterY - 60, i * mWL + mOffset, mCenterY);
            //測試紅點
//            canvas.drawCircle((-mWL * 3 / 4) + (i * mWL) + mOffset, mCenterY + 60, 5, mCyclePaint);
//            canvas.drawCircle((-mWL / 2) + (i * mWL) + mOffset, mCenterY, 5, mCyclePaint);
//            canvas.drawCircle((-mWL / 4) + (i * mWL) + mOffset, mCenterY - 60, 5, mCyclePaint);
//            canvas.drawCircle(i * mWL + mOffset, mCenterY, 5, mCyclePaint);
        }
        //填充矩形
        mPath.lineTo(mScreenWidth, mScreenHeight);
        mPath.lineTo(0, mScreenHeight);
        mPath.close();
        canvas.drawPath(mPath, mPaint);
    }

    @Override
    public void onClick(View view) {
        try {

            ValueAnimator animator = ValueAnimator.ofInt(-mWL, mWL);
            animator.setDuration(1000);
            animator.setRepeatCount(ValueAnimator.INFINITE);
            animator.setInterpolator(new LinearInterpolator());
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mOffset = (int) animation.getAnimatedValue();
                    postInvalidate();
                }
            });
            animator.start();
        } catch (Exception e) {
            e.printStackTrace();
        }


    }


}

然後在實例化控件以後調用Click方法(不能直接點擊View,因爲Click方法此處並沒有響應,需要重寫boolean onTouchEvent(MotionEvent event)方法來定義Click事件——我猜的哈,反正我直接點擊的時候此處方法並沒有響應)。於是我簡單申明控件以後直接調用了Click方法。大坑出現了!

這裏寫圖片描述

此處波浪圖並沒有動起來,而且屬性方法裏面打印的日誌顯示設置的ofint()方法參數(這個是成員變量,即波浪寬度)爲900,偏移量爲0。上網百度“屬性動畫失效”找不到資料,後來查了半天發現是因爲Activity裏面聲明控件並調用了Click方法,這個時候寬度默認爲0,因此屬性動畫的偏移量一直爲0。
於是修改調用代碼:

public class WaveActivity extends AppCompatActivity {

    private WaveView1 mWave;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_wave);

        mWave = (WaveView1) findViewById(R.id.waveView);
        final Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        mWave.onClick(null);
                    }
                });
            }
        },500);

    }


}

續三階貝塞爾曲線形成原理:

1.連接 A,B 形成 AB 線段,連接 B,C 形成 BC 線段,連接 C,D 形成 CD 線段。

這裏寫圖片描述

2.在AB線段取一個點 E,BC 線段取一個點 F,CD 線段取一個點 G,使其滿足條件: AE/AB = BF/BE = CG/CD。連接 E,F 形成線段 EF,連接 F,G 形成線段 FG。

這裏寫圖片描述

3.在EF線段取一個點 H,FG 線段取一個點 I,使其滿足條件: AE/AB = BF/BE = CG/CD = EH/EF = FI/FG。連接 H,I 形成線段 HI。

這裏寫圖片描述

4.在 HI 線段取一個點 J,使其滿足條件: AE/AB = BF/BE = CG/CD = EH/EF = FI/FG = HJ/HI。

這裏寫圖片描述

5.而滿足這些條件的所有的J點所形成的軌跡就是三階貝塞爾曲線,動態過程如下:

這裏寫圖片描述

方法預覽:
public void quadTo (float x1, float y1, float x2, float y2)
繪製圓形:
這裏寫圖片描述
修改座標:
這裏寫圖片描述
最終效果:
這裏寫圖片描述

最後再來一張效果圖:

這裏寫圖片描述

參考資料:

Path從懵逼到精通(2)——貝塞爾曲線

Android自定義View——貝塞爾曲線實現水波紋效果

發佈了61 篇原創文章 · 獲贊 28 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章