一张图带你彻底了解二阶贝塞尔曲线

简介: *本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布上一篇自定义View中,贝塞尔曲线出现的频率很高,有小伙伴就问到关于贝塞尔曲线控制点坐标怎么计算的问题。

*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

上一篇自定义View中,贝塞尔曲线出现的频率很高,有小伙伴就问到关于贝塞尔曲线控制点坐标怎么计算的问题。一阶贝塞尔曲线是一条直线,确定起点终点即可,三阶贝塞尔曲线有两个控制点,相对比较复杂,不容易控制。二阶贝塞尔曲线只有一个控制点,在实际开发中应用的也是最多的。今天讨论的就是关于二阶贝塞尔曲线的控制点坐标计算问题。

到底怎样一张图就能够彻底了解二阶贝塞尔曲线呢,往下看就知道了:

这里写图片描述

设置二阶贝塞尔曲线的方法:
moveTo(float x,float y)
其中x,y的坐标代表图中曲线左边起点的位置坐标
quadTo(float x1, float y1, float x2, float y2 )
其中x1,y1的坐标就是图中小圆点的位置,也就是控制点的坐标
x2,y2的坐标就是图中曲线右边终点的位置坐标

代码中怎么实现的,一起看一下:


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(10);
        path.reset();
        //绘制二阶贝塞尔曲线
        path.moveTo(mWidth * 1 / 8, mHeight * 1 / 5);
        path.quadTo(xWidth, yHeight, mWidth * 7 / 8, mHeight * 1 / 5);
        canvas.drawPath(path, paint);
        paint.setStyle(Paint.Style.FILL);
        //绘制控制点
        canvas.drawCircle(xWidth, yHeight, 10, paint);
    }


    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        int x = (int) ev.getX();
        int y = (int) ev.getY();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                xWidth = x;
                yHeight = y;
                postInvalidate();
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return true;
    }

其实就是重写了onTouchEvent(MotionEvent ev)方法,在action为MotionEvent.ACTION_MOVE的时候,得到滑动时候的x与y坐标,然后分别赋值给path.quadTo(float x1, float y1, float x2, float y2 )方法中的前两个参数,也就是二阶贝塞尔曲线的控制点坐标。然后调用postInvalidate()来重新进行绘制即可。最后记得在onTouchEvent(MotionEvent ev)方法后返回true,表示View消耗当前滑动事件。

哈哈,看完是不是觉得很简单了。基于这张原理图,实际开发中有很多应用。我后来做了一些改进,算是小小拓展一下。看看下面的效果图:

这里写图片描述

是不是一种很熟悉的赶脚,各大手机卫士的清理小火箭不就出来了嘛,哈哈。这里关于自定义View的一些初始工作就不详细介绍了,前几篇博客说的很清楚。这里就主要贴onDraw()的代码了,上代码:

1.重写onDraw()方法

        //确定小火箭控制点的范围
        if (xWidth < xSize) {
            xWidth = xSize;
        }
        if (xWidth > mWidth * 9 / 10) {
            xWidth = mWidth * 9 / 10;
        }
        if (yHeight > mHeight * 8 / 10) {
            yHeight = mHeight * 8 / 10;
        }
        if (yHeight > mHeight * 7 / 10 && xWidth < mWidth * 4 / 10) {
            yHeight = mHeight * 7 / 10;
        }
        if (yHeight > mHeight * 7 / 10 && xWidth > mWidth * 6 / 10) {
            yHeight = mHeight * 7 / 10;
        }

xSize与ySize分别代表整体View宽度与高度的1/10,xWidth与yHeight是在action为MotionEvent.ACTION_MOVE时拿到的x与y坐标。后面绘制小火箭以及发射台的坐标都是基于这两个点进行改变的。仔细观察示例图效果,你会发现,小火箭是无法超出这个设置的区域。

        paint.setStrokeWidth(5);
        paint.setStyle(Paint.Style.STROKE);
        path.reset();
        //绘制小火箭
        path.moveTo(xWidth - xSize * 1 / 2, yHeight - ySize * 3 / 5);
        path.lineTo(xWidth, yHeight - ySize);
        path.lineTo(xWidth + xSize * 1 / 2, yHeight - ySize * 3 / 5);
        path.moveTo(xWidth - xSize * 1 / 4, yHeight - ySize * 4 / 5);
        path.lineTo(xWidth - xSize * 1 / 4, yHeight);
        path.lineTo(xWidth + xSize * 1 / 4, yHeight);
        path.lineTo(xWidth + xSize * 1 / 4, yHeight - ySize * 4 / 5);
        canvas.drawPath(path, paint);

一阶贝塞尔曲线的应用

        //绘制发射台
        paint.setStrokeWidth(10);
        arcPath.reset();
        arcPath.moveTo(mWidth * 1 / 10, mHeight * 7 / 10);
        if (yHeight > mHeight * 7 / 10 && xWidth > mWidth * 4 / 10 && xWidth < mWidth * 6 / 10) {
            arcHeight = yHeight + yHeight - mHeight * 7 / 10;
        } else {
            arcHeight = mHeight * 7 / 10;
        }
        arcPath.quadTo(mWidth * 5 / 10, arcHeight, mWidth * 9 / 10, mHeight * 7 / 10);
        canvas.drawPath(arcPath, paint);

示例图中,只有火箭到达指定区域,才会引起发射台弯曲。这里进行了一下判断,只有在控制点坐标大于整体高度的7/10,并且宽度在指定范围内的时候,才会让quadTo(float x1, float y1, float x2, float y2 )中的第二个参数进行改变,否则保持不变。

        //绘制成功后的文字
        if (isSuccess && yHeight < 0) {
            txtPaint.setTextSize(80);
            txtPaint.setColor(color);
            txtPaint.getTextBounds(text, 0, text.length(), mRect);
            canvas.drawText(text, mWidth * 1 / 2 - mRect.width() / 2, mHeight * 1 / 2 + mRect.height() * 1 / 2, txtPaint);
        }

这里的文字是在动画效果完成以后,并且控制点y的值小于0,也就是消失在视野中的时候,才让发射成功的文字出现。

2.重写onTouchEvent(MotionEvent ev)方法

   @Override
    public boolean onTouchEvent(MotionEvent ev) {

        int action = ev.getAction();
        int x = (int) ev.getX();
        int y = (int) ev.getY();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                isSuccess = false;
                break;
            case MotionEvent.ACTION_MOVE:
                xWidth = x;
                yHeight = y;
                postInvalidate();
                break;
            case MotionEvent.ACTION_UP:
                if (yHeight > mHeight * 7 / 10 && xWidth > mWidth * 4 / 10 && xWidth < mWidth * 6 / 10) {
                    startAnim();
                }
                break;
        }
        return true;
    }

MotionEvent.ACTION_DOWN的时候,发射成功设置为false
MotionEvent.ACTION_MOVE的时候,获取触摸点的坐标,并赋值给控制点
MotionEvent.ACTION_UP的时候,进行判断,符合发射条件就开启动画

返回true,表示消费当前滑动事件

3.动画实现

    private void startAnim() {
        //动画实现
        ValueAnimator animator = ValueAnimator.ofInt(yHeight, -ySize);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                yHeight = (int) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        animSet.setDuration(1200);
        animSet.play(animator);
        animSet.start();
        isSuccess = true;
    }

设置好开始值与结束值,添加一个动画的监听,就能够得到变化的值,再使用postInvalidate()方法,从而调用onDraw()方法来进行数值的改变并重新绘制。最后开启动画,并且将发射成功设置为true即可。

OK,相信看到这里,你对二阶贝塞尔曲线有了更加全面的认识。下一篇自定义View再见~~

源码地址:

https://github.com/18722527635/MyCustomView

欢迎star,fork,提issues,一起进步!

目录
相关文章
|
6月前
|
算法 计算机视觉
OpenCV(三十七):拟合直线、三角形和圆形
OpenCV(三十七):拟合直线、三角形和圆形
308 0
|
3月前
|
前端开发 小程序 JavaScript
贝塞尔曲线的切线及其AABB问题
贝塞尔曲线的切线及其AABB问题
|
5月前
|
算法 前端开发 计算机视觉
一篇文章讲明白Harris角点
一篇文章讲明白Harris角点
39 3
|
5月前
高等数学II-知识点(3)——广义积分、定积分几何应用、定积分求曲线弧长、常微分方程、可分离变量的微分方程、一阶微分方程-齐次方程、一阶线性微分方程
高等数学II-知识点(3)——广义积分、定积分几何应用、定积分求曲线弧长、常微分方程、可分离变量的微分方程、一阶微分方程-齐次方程、一阶线性微分方程
47 0
|
6月前
|
图形学
【计算机图形学】期末复习Bezier曲线与曲面篇
【计算机图形学】期末复习Bezier曲线与曲面篇
|
算法 图形学
计算机图形学 之 DDA直线算法(数值微分法)
计算机图形学 之 DDA直线算法(数值微分法)
413 0
|
算法
秒懂算法 | 计算几何:圆
计算几何的基础是点积和叉积,它们定义了向量的大小和方向的关系,是其他计算几何概念和算法的出发点。在点积和叉积的基础上,本篇重点介绍圆覆盖。 计算几何题目的代码大多繁琐冗长,因此掌握模板代码是学习计算几何的关键。本篇精心组织了经典的几何模板
18124 1
秒懂算法 | 计算几何:圆
|
机器学习/深度学习
深度之眼(十八)——偏导数与矩阵的求导
深度之眼(十八)——偏导数与矩阵的求导
255 0
深度之眼(十八)——偏导数与矩阵的求导
|
编解码 监控 关系型数据库
用PostgreSQL描绘人生的高潮、尿点、低谷 - 窗口/帧 or 斜率/导数/曲率/微积分?
标签 PostgreSQL , 曲线拐点 , 窗口查询 , 帧 , 窗口 , 导数 , 曲率 , 微积分 , 斜率 背景 人生就像一场戏,有高潮,有尿点,有低谷。如果用曲线来描述漫漫人生路的话,怎么找出高潮、尿点、低谷呢? 其实类似的场景还有很多,比如来自传感器(比如人身上可以探测的指标就有很多)的监控数据;服务器的监控数据;温度,湿度的变化数据;等等,都可以数字化,用曲线来表示。
1579 0