自定义view,贝塞尔曲线实现水波纹效果的动画

作为一名码农,除了用基本的姿势去搬砖,还应该get一些炫酷的技能,用高逼格的姿态去搬砖。而贝塞尔曲线无疑是炫酷技能之一。

简介:

Bézier curve(贝塞尔曲线)是应用于二维图形应用程序的数学曲线。 曲线定义:起始点、终止点(也称锚点)、控制点。通过调整控制点,贝塞尔曲线的形状会发生变化。 1962年,法国数学家Pierre Bézier第一个研究了这种矢量绘制曲线的方法,并给出了详细的计算公式,因此按照这样的公式绘制出来的曲线就用他的姓氏来命名,称为贝塞尔曲线。

贝塞尔曲线作用十分广泛,比如下面的几个场景:
  • QQ小红点拖拽效果
  • 一些炫酷的下拉刷新控件
  • 阅读软件的翻书效果
  • 一些平滑的折线图的制作
  • 炫酷的动画效果
贝塞尔曲线的原理:
  • 一阶贝塞尔曲线(线段):
    公式:
    自定义view,贝塞尔曲线实现水波纹效果的动画
    自定义view,贝塞尔曲线实现水波纹效果的动画
    原理:由 P0 至 P1 的连续点, 描述的一条线段

  • 二阶贝塞尔曲线(抛物线):
    公式:
    自定义view,贝塞尔曲线实现水波纹效果的动画
    自定义view,贝塞尔曲线实现水波纹效果的动画
    原理:
    由 P0 至 P1 的连续点 Q0,描述一条线段。
    由 P1 至 P2 的连续点 Q1,描述一条线段。
    由 Q0 至 Q1 的连续点 B(t),描述一条二次贝塞尔曲线。
    可以理解成:P1-P0为曲线在P0处的切线。

  • 三阶贝塞尔曲线
    公式:
    自定义view,贝塞尔曲线实现水波纹效果的动画
    自定义view,贝塞尔曲线实现水波纹效果的动画

接下来用贝塞尔曲线实现一个水波纹动画效果的自定义view
  • ui效果:
    自定义view,贝塞尔曲线实现水波纹效果的动画
  • 代码:
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);
        }
    }
}