1. 程式人生 > >自定義view,貝塞爾曲線實現水波紋效果的動畫

自定義view,貝塞爾曲線實現水波紋效果的動畫

作為一名碼農,除了用基本的姿勢去搬磚,還應該get一些炫酷的技能,用高逼格的姿態去搬磚。而貝塞爾曲線無疑是炫酷技能之一。

簡介:

Bézier curve(貝塞爾曲線)是應用於二維圖形應用程式的數學曲線。 曲線定義:起始點、終止點(也稱錨點)、控制點。通過調整控制點,貝塞爾曲線的形狀會發生變化。 1962年,法國數學家Pierre Bézier第一個研究了這種向量繪製曲線的方法,並給出了詳細的計算公式,因此按照這樣的公式繪製出來的曲線就用他的姓氏來命名,稱為貝塞爾曲線。

貝塞爾曲線作用十分廣泛,比如下面的幾個場景:
  • QQ小紅點拖拽效果
  • 一些炫酷的下拉重新整理控制元件
  • 閱讀軟體的翻書效果
  • 一些平滑的折線圖的製作
  • 炫酷的動畫效果
貝塞爾曲線的原理:
  • 一階貝塞爾曲線(線段): 公式: 在這裡插入圖片描述 在這裡插入圖片描述 原理:由 P0 至 P1 的連續點, 描述的一條線段

  • 二階貝塞爾曲線(拋物線): 公式: 在這裡插入圖片描述 在這裡插入圖片描述 原理: 由 P0 至 P1 的連續點 Q0,描述一條線段。 由 P1 至 P2 的連續點 Q1,描述一條線段。 由 Q0 至 Q1 的連續點 B(t),描述一條二次貝塞爾曲線。 可以理解成:P1-P0為曲線在P0處的切線。

  • 三階貝塞爾曲線 公式: 在這裡插入圖片描述 在這裡插入圖片描述

接下來用貝塞爾曲線實現一個水波紋動畫效果的自定義view
  • ui效果: 在這裡插入圖片描述
  • 程式碼:
public class WaveView extends View {

    private Paint mPaint;
    private Path mPath;
    private int viewWidth, viewHeight; //控制元件的寬和高
    private float commandX, commandY; //控制點的座標
    private float waterHeight;  //水位高度
    private boolean isInc;// 判斷控制點是該右移還是左移

    public WaveView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    /**
     * 初始化畫筆 路徑
     */
    private void init() {
        //畫筆
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.parseColor("#AFDEE4"));
        //路徑
        mPath = new Path();
    }

    /**
     * 獲取控制元件的寬和高
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        viewWidth = w;
        viewHeight = h;
        // 控制點 開始時的Y座標
        commandY = 7 / 8f * viewHeight;
        //終點一開始的Y座標 ,也就是水位水平高度 , 紅色輔助線
        waterHeight = 15 / 16F * viewHeight;
    }

    /**
     * 繪製
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPath.moveTo(-1 / 4F * viewWidth, waterHeight);// 起始點位置
        //繪製水波浪
        mPath.quadTo(commandX, commandY, viewWidth + 1 / 4F * viewWidth, waterHeight);//二階貝塞爾曲線

        //繪製波浪下方閉合區域
        mPath.lineTo(viewWidth + 1 / 4F * viewWidth, viewHeight);//一階貝塞爾曲線
        mPath.lineTo(-1 / 4F * viewWidth, viewHeight);//一階貝塞爾曲線
        mPath.close();
        //繪製路徑
        canvas.drawPath(mPath, mPaint);
        //產生波浪左右湧動的感覺
        if (commandX >= viewWidth + 1 / 4F * viewWidth) {//控制點座標大於等於終點座標改標識
            isInc = false;
        } else if (commandX <= -1 / 4F * viewWidth) {//控制點座標小於等於起點座標改標識
            isInc = true;
        }
        commandX = isInc ? commandX + 20 : commandX - 20;
        //水位不斷加高  當距離控制元件頂端還有1/8的高度時,不再上升
        if (commandY >= 1 / 8f * viewHeight) {
            commandY -= 2;
            waterHeight -= 2;
        }
        //路徑重置
        mPath.reset();
        // 重繪
        invalidate();
    }

    /**
     * 測量
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int wSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int wSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int hSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(300, 300);
        } else if (wSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(300, hSpecSize);
        } else if (hSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(wSpecSize, 300);
        }
    }
}