Android -- 贝塞尔曲线公式的推导和简单使用

原文链接:http://www.cnblogs.com/wjtaigwh/p/6647114.html

1,最近看了几个不错的自定义view,发现里面都会涉及到贝塞尔曲线知识,深刻的了解到贝塞尔曲线是进阶自定义view的一座大山,so,今天先和大家来了解了解。

2,贝塞尔曲线作用十分广泛,简单举几个的栗子:

1
2
3
4
5
6
7
QQ小红点拖拽效果
360 火箭发射
加入购物车动画
一些炫酷的下拉刷新控件
阅读软件的翻书效果
一些平滑的折线图的制作
很多炫酷的动画效果

  这么多好看的效果,难道不想自己也写一个吗。。。。

  • 理解贝塞尔曲线的原理 

  贝塞尔曲线是用一系列点来控制曲线状态的,我将这些点简单分为两类:数据点、控制点。通过调整控制点,贝塞尔曲线形状会发生变化。

1
2
数据点:确定曲线的起始和结束位置
控制点:确定曲线的弯曲程度
  • 一阶曲线原理

    一阶曲线是没有控制点的,仅有两个数据点(A 和 B),最终效果一个线段。

  

  一阶公式如下:

  

  • 二阶曲线原理

  二阶曲线由两个数据点(A 和 C),一个控制点(B)来描述曲线状态,大致如下:

    

  那么ac之间的红线是怎么生成的呢,让我们了解一下

  

  在AB线段和BC线段分别去D、E两点,且满足条件

    

  连接DE,取点F,使得: ,这样获取到的点F就是贝塞尔曲线上的一个点,动态图如下:

  二阶公式如下:

  

  • 三阶曲线原理

    三阶曲线由两个数据点(A 和 D),两个控制点(B 和 C)来描述曲线状态

     

  动态图如下:

  三阶公式如下:

  

  • 四阶曲线

  • 五阶曲线

   通用公式:

  

3,公式推导

  由于博客园的编辑器无法编写高数公式,所以我这里就在纸上写了,如果有点看不到的话可以把图片下下来再放大看看(见谅)

   

  

  

 4,实现简单的小例子

  在我们Android中Path类中其实是有已经封装好了关于贝塞尔曲线的函数的

1
2
3
4
5
6
//二阶贝赛尔 
public  void  quadTo( float  x1,  float  y1,  float  x2,  float  y2) 
public  void  rQuadTo( float  dx1,  float  dy1,  float  dx2,  float  dy2) 
//三阶贝赛尔 
public  void  cubicTo( float  x1,  float  y1,  float  x2,  float  y2, float  x3,  float  y3) 
public  void  rCubicTo( float  x1,  float  y1,  float  x2,  float  y2, float  x3,  float  y3)
  • quadTo()方法

  让我们先来看一下源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
      * Add a quadratic bezier from the last point, approaching control point
      * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
      * this contour, the first point is automatically set to (0,0).
      *
      * @param x1 The x-coordinate of the control point on a quadratic curve
      * @param y1 The y-coordinate of the control point on a quadratic curve
      * @param x2 The x-coordinate of the end point on a quadratic curve
      * @param y2 The y-coordinate of the end point on a quadratic curve
      */
     public  void  quadTo( float  x1,  float  y1,  float  x2,  float  y2) {
         isSimplePath =  false ;
         native_quadTo(mNativePath, x1, y1, x2, y2);
     }

  从源码的注释上我们可以得到的信息如下:参数中(x1,y1)是控制点坐标,(x2,y2)是终点坐标 。  

  大家可能会有一个疑问:有控制点和终点坐标,那起始点是多少呢? 整条线的起始点是通过Path.moveTo(x,y)来指定的,而如果我们连续调用quadTo(),前一个quadTo()的终点,就是下一个quadTo()函数的起点;如果初始没有调用Path.moveTo(x,y)来指定起始点,则默认以控件左上角(0,0)为起始点;

  我们简单的写一下demo,通过我们点击屏幕来动态的获取点击屏幕的坐标,然后将其设置成贝二阶塞尔曲线的控制点,代码很简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package  com.qianmo.beziertest.view;
 
 
import  android.content.Context;
import  android.graphics.Canvas;
import  android.graphics.Color;
import  android.graphics.Paint;
import  android.graphics.Path;
import  android.graphics.Point;
import  android.support.annotation.Nullable;
import  android.util.AttributeSet;
import  android.view.MotionEvent;
import  android.view.View;
 
import  com.qianmo.beziertest.R;
 
/**
  * Created by Administrator on 2017/3/30 0030.
  * E-Mail:543441727@qq.com
  */
 
public  class  MyView  extends  View {
 
     private  Point controlPoint =  new  Point( 200 200 );
 
     public  MyView(Context context) {
         super (context);
     }
 
     public  MyView(Context context,  @Nullable  AttributeSet attrs) {
         super (context, attrs);
     }
 
     public  MyView(Context context,  @Nullable  AttributeSet attrs,  int  defStyleAttr) {
         super (context, attrs, defStyleAttr);
     }
 
     @Override
     protected  void  onDraw(Canvas canvas) {
         super .onDraw(canvas);
 
         Paint paint =  new  Paint();
         paint.setStyle(Paint.Style.STROKE);
         paint.setColor(Color.BLACK);
         paint.setStrokeWidth( 10 );
 
         Path path =  new  Path();
         path.moveTo( 100 500 );
         path.quadTo(controlPoint.x, controlPoint.y,  700 500 );
         //绘制路径
         canvas.drawPath(path, paint);
         //绘制辅助点
         canvas.drawPoint(controlPoint.x,controlPoint.y,paint);
 
     }
 
     @Override
     public  boolean  onTouchEvent(MotionEvent event) {
         switch  (event.getAction()) {
             case  MotionEvent.ACTION_MOVE:
                 controlPoint.x = ( int ) event.getX();
                 controlPoint.y = ( int ) event.getY();
                 invalidate();
                 break ;
         }
         return  true ;
     }
}

  效果图如下:

  • rQuadTo()

  先来看一下它的源码,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
      * Same as quadTo, but the coordinates are considered relative to the last
      * point on this contour. If there is no previous point, then a moveTo(0,0)
      * is inserted automatically.
      *
      * @param dx1 The amount to add to the x-coordinate of the last point on
      *            this contour, for the control point of a quadratic curve
      * @param dy1 The amount to add to the y-coordinate of the last point on
      *            this contour, for the control point of a quadratic curve
      * @param dx2 The amount to add to the x-coordinate of the last point on
      *            this contour, for the end point of a quadratic curve
      * @param dy2 The amount to add to the y-coordinate of the last point on
      *            this contour, for the end point of a quadratic curve
      */
     public  void  rQuadTo( float  dx1,  float  dy1,  float  dx2,  float  dy2) {
         isSimplePath =  false ;
         native_rQuadTo(mNativePath, dx1, dy1, dx2, dy2);
     }

  从上面的方法注释我们可以看到,这是一个相对坐标,具体参数意思如下:

1
2
3
4
dx1:控制点X坐标,表示相对上一个终点X坐标的位移坐标,可为负值,正值表示相加,负值表示相减;
dy1:控制点Y坐标,相对上一个终点Y坐标的位移坐标。同样可为负值,正值表示相加,负值表示相减;
dx2:终点X坐标,同样是一个相对坐标,相对上一个终点X坐标的位移值,可为负值,正值表示相加,负值表示相减;
dy2:终点Y坐标,同样是一个相对,相对上一个终点Y坐标的位移值。可为负值,正值表示相加,负值表示相减;

  这里举个例子,假如我们上一个终点坐标是(300,400)那么利用rQuadTo(100,-100,200,100); 得到的控制点坐标是(300+100,400-100)即(500,300) 同样,得到的终点坐标是(300+200,400+100)即(500,500),这个方法和quadTo()方法没什么区别,所以就不写Demo写了

  • cubicTo()

  这是Android的三阶贝塞尔曲线方法,先来看一下每一参数的意义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
      * Add a cubic bezier from the last point, approaching control points
      * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
      * made for this contour, the first point is automatically set to (0,0).
      *
      * @param x1 The x-coordinate of the 1st control point on a cubic curve
      * @param y1 The y-coordinate of the 1st control point on a cubic curve
      * @param x2 The x-coordinate of the 2nd control point on a cubic curve
      * @param y2 The y-coordinate of the 2nd control point on a cubic curve
      * @param x3 The x-coordinate of the end point on a cubic curve
      * @param y3 The y-coordinate of the end point on a cubic curve
      */
     public  void  cubicTo( float  x1,  float  y1,  float  x2,  float  y2,
                         float  x3,  float  y3) {
         isSimplePath =  false ;
         native_cubicTo(mNativePath, x1, y1, x2, y2, x3, y3);
     }

  从上面源码可以看到参数(x1,y1)是第一个控制点坐标,(x2,y2)是第二个控制点坐标,(x3,y3)是终点坐标。

  整条线的起始点是通过Path.moveTo(x,y)来指定的,而如果我们连续调用cubicTo(),前一个cubicTo()的终点,就是下一个cubicTo()函数的起点;如果初始没有调用Path.moveTo(x,y)来指定起始点,则默认以控件左上角(0,0)为起始点;和我们的quadTo()方法一样。

  下面也是通过一个小例子给大家看一下效果,和二阶贝塞尔曲线一样,就是多了一个控制点代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package  com.qianmo.beziertest.view;
 
 
import  android.content.Context;
import  android.graphics.Canvas;
import  android.graphics.Color;
import  android.graphics.Paint;
import  android.graphics.Path;
import  android.graphics.Point;
import  android.support.annotation.Nullable;
import  android.util.AttributeSet;
import  android.view.MotionEvent;
import  android.view.View;
 
/**
  * Created by wangjitao on 2017/3/30 0030.
  * E-Mail:543441727@qq.com
  */
 
public  class  MyView1  extends  View {
 
     private  Point controlPointOne =  new  Point( 200 200 );
     private  Point controlPointTwo =  new  Point( 500 200 );
 
     private  boolean  isControlPointTwo ;
 
     private  Paint paintBezier;
     private  Paint paintLine;
 
     public  MyView1(Context context) {
         this (context,  null );
     }
 
     public  MyView1(Context context,  @Nullable  AttributeSet attrs) {
         this (context, attrs,  0 );
     }
 
     public  MyView1(Context context,  @Nullable  AttributeSet attrs,  int  defStyleAttr) {
         super (context, attrs, defStyleAttr);
 
         paintBezier =  new  Paint();
         paintBezier.setStyle(Paint.Style.STROKE);
         paintBezier.setColor(Color.BLACK);
         paintBezier.setStrokeWidth( 10 );
 
 
         paintLine =  new  Paint();
         paintLine.setStyle(Paint.Style.STROKE);
         paintLine.setColor(Color.RED);
         paintLine.setStrokeWidth( 3 );
 
     }
 
     @Override
     protected  void  onDraw(Canvas canvas) {
         super .onDraw(canvas);
 
 
         Path path =  new  Path();
         path.moveTo( 100 500 );
         path.cubicTo(controlPointOne.x, controlPointOne.y,controlPointTwo.x, controlPointTwo.y,  900 500 );
         //绘制路径
         canvas.drawPath(path, paintBezier);
         //绘制辅助点
         canvas.drawPoint(controlPointOne.x, controlPointOne.y, paintBezier);
         canvas.drawPoint(controlPointTwo.x, controlPointTwo.y, paintBezier);
         //绘制连线
//        canvas.drawLine(100, 500, controlPointOne.x, controlPointOne.y, paintLine);
//        canvas.drawLine(900, 500, controlPointOne.x, controlPointOne.y, paintLine);
     }
 
     @Override
     public  boolean  onTouchEvent(MotionEvent event) {
         switch  (event.getAction()) {
             case  MotionEvent.ACTION_MOVE:
                 if  (isControlPointTwo){
                     controlPointOne.x = ( int ) event.getX();
                     controlPointOne.y = ( int ) event.getY();
                 } else  {
                     controlPointTwo.x = ( int ) event.getX();
                     controlPointTwo.y = ( int ) event.getY();
                 }
                 invalidate();
                 break ;
         }
         return  true ;
     }
 
 
     public  boolean  isControlPointTwo() {
         return  isControlPointTwo;
     }
 
     public  void  setControlPointTwo( boolean  controlPointTwo) {
         isControlPointTwo = controlPointTwo;
     }
}

  效果如下:

  同理rCubicTo,这里就不给大家解释了

  ok,本篇基本上把贝塞尔的基础知识都了解完了,明天开始试着来撸撸轮子,感觉一大波好看的动画满天飞的控件在等着我们(邪恶脸)。See You Next Time。。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值