android-贝塞尔(bezier)曲线

引子

贝塞尔,全名-皮埃尔·贝塞尔,(1910年9月1日——1999年11月25日),法语:Pierre Bézier,法国机械和电气工程师,计算机几何建模创始人之一。

贝塞尔曲线,计算机图形学中相当重要的参数曲线--(吾等凡人的理解  ->_->简而言之就是,用路径上的几个点,做出一条光滑曲线)

 

之前写特效的时候,接触过 抛物线的计算公式,就是为了做出一个控件沿着弧形移动的动画。

其实做曲线的话,除了抛物线,还可以用这种 贝塞尔曲线,效果更好,使用更简单。

本人将粗略讲解原理(智商不够⊙︿⊙)以及详细展示用法;

 

强烈建议:

如果不知道神马是贝塞尔曲线的话,建议先看这个-

https://blog.csdn.net/yangdahuan/article/details/52174904,

里面有动态图展示,相当清楚明白; 看完了,再来看我写的具体用法,就会一目了然;

 

用法1

 

其实这种曲线算法的公式,是 完全脱离编程语言,以及完全脱离代码环境的,它纯粹就是一个数学概念,数学公式;

公式如下:

将此公式,转化为java代码,那就是:

 1 /**
 2      * N阶贝塞尔曲线
 3      *
 4      * @param t
 5      * @param ps 曲线经过的点的X或者Y坐标
 6      *           如果点数是2,则是1阶
 7      *           如果点数是3,则是2阶
 8      *           ......
 9      * @return
10      */
11     private double nOrderBezierFunction(double t, double... ps) {
12         double res = 0;
13         if (t > 1 || t < 0) return 0;// 如果参数t不是在0和1之间,也不用计算了
14         if (ps == null || ps.length < 2) return 0;// 只有1个点 或者一个点都没有,直接返回0
15         int N = ps.length - 1;// 确定阶数
16         Log.d("psTag", "" + N);
17         //现在转化公式;
18         for (int i = 0; i <= N; i++) {
19             double powRes;
20             powRes = ps[i] * Math.pow((1 - t), (N - i)) * Math.pow(t, i);//先把降幂和升幂公式转化出来
21             if (i != 0 && i != N) {//头和尾不需要诚意阶数,其他的都要乘阶数
22                 powRes = N * powRes;
23             }
24             res += powRes;
25         }
26         return res;
27     }

上面的注释写的很清楚了;

然后调用的完整代码如下:

 1 public class MainActivity extends AppCompatActivity {
 2 
 3     @Override
 4     protected void onCreate(Bundle savedInstanceState) {
 5         super.onCreate(savedInstanceState);
 6         setContentView(R.layout.activity_main);
 7         final TextView tv_test = findViewById(R.id.tv_test);
 8         tv_test.setOnClickListener(new View.OnClickListener() {
 9             @Override
10             public void onClick(View v) {
11                 anim(tv_test);
12             }
13         });
14     }
15 
16     double x = 0f;
17     double y = 0f;
18 
19     private void anim(final View view) {
20         x = 0f;
21         y = 0f;
22         final RPoint p0 = new RPoint();//原点坐标就是view的所在
23         p0.mX = view.getX();
24         p0.mY = view.getY();
25 
26         final RPoint p1 = new RPoint();//点p1
27         p1.mX = view.getX() + convertDIP2PX(30);
28         p1.mY = view.getY() + convertDIP2PX(250);
29 
30         final RPoint p2 = new RPoint();//点p2
31         p2.mX = view.getX() + convertDIP2PX(60);
32         p2.mY = view.getY() - convertDIP2PX(250);
33 
34         final RPoint p3 = new RPoint();//点p3
35         p3.mX = view.getX() + convertDIP2PX(90);
36         p3.mY = view.getY();
37 
38         ObjectAnimator goInAnim = new ObjectAnimator();
39         goInAnim.setFloatValues(0f, 1f);
40         goInAnim.setDuration(5000);
41         goInAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
42             @Override
43             public void onAnimationUpdate(ValueAnimator valueAnimator) {
44                 float t = (float) valueAnimator.getAnimatedValue();
45                 x = nOrderBezierFunction(t, p0.mX, p1.mX, p2.mX,p3.mX);
46                 y = nOrderBezierFunction(t, p0.mY, p1.mY, p2.mY,p3.mY);
47 
48                 view.setX((float) x);
49                 view.setY((float) y);
50             }
51         });
52         goInAnim.setTarget(1);
53         goInAnim.start();
54     }
55 
56     private int convertDIP2PX(float dp) {
57         return (int) (dp * this.getResources().getDisplayMetrics().density + 0.5f);
58     }
59 
60     class RPoint {
61         float mX;
62         float mY;
63     }
64 
65 
66     /**
67      * N阶贝塞尔曲线,
68      *
69      * @param t
70      * @param ps 曲线经过的点的X或者Y坐标,注意,这里的参数数量是不定的,你可以写2个,3个,或者100个。您随意。函数会自动计算贝塞尔坐标;
71      *           如果点数是2,则是1阶
72      *           如果点数是3,则是2阶
73      *           ......
74      * @return
75      */
76     private double nOrderBezierFunction(double t, double... ps) {
77         double res = 0;
78         if (t > 1 || t < 0) return 0;// 如果参数t不是在0和1之间,也不用计算了
79         if (ps == null || ps.length < 2) return 0;// 只有1个点? 或者一个点都没有,直接返回0
80         int N = ps.length - 1;// 确定阶数
81         Log.d("psTag", "" + N);
82         //现在转化公式;
83         for (int i = 0; i <= N; i++) {
84             double powRes;
85             powRes = ps[i] * Math.pow((1 - t), (N - i)) * Math.pow(t, i);//先把降幂和升幂公式转化出来
86             if (i != 0 && i != N) {//头和尾不需要诚意阶数,其他的都要乘阶数
87                 powRes = N * powRes;
88             }
89             res += powRes;
90         }
91         return res;
92     }
93 
94 }

布局文件activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_test"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

具体效果:

 

OK,以上就是直接使用公式的用法;代码可复用,喜欢的大佬们,欢迎拷贝。有错误请留言。

 

用法2

在android.graphics.Path类中,有直接绘制贝塞尔曲线的方法,

目前我发现的有

/**
    绘制二阶贝塞尔曲线路径
* 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; nQuadTo(mNativePath, x1, y1, x2, y2); }

* 添加一条二阶(或者二次)贝塞尔曲线,从上一个点,到达控制点(x1,y1),然后结束于 (x2,y2);
* 如果不在这个轮廓调用moveTo,第一个点将会默认为(0,0);调用这个quadTo之前,首先要先moveTo一个点,作为二阶贝塞尔曲线的起点;不然这个函数就会以原点(0,0)作为起点;


/**  绘制三阶贝塞尔曲线路径
     * 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;
        nCubicTo(mNativePath, x1, y1, x2, y2, x3, y3);
    }
*  添加一条立体的(三阶)贝塞尔曲线,从上一个点,依据 两个控制点(x1,y1) 和(x2,y2),以及终点(x3,y3)。
* 如果调用这个之前,没有调用moveTo,那么上一个点就会被设置成原点(0,0)

 

应该没有别的了,更高阶贝塞尔曲线的api,这里没有提供;

用法如下:

  1 import android.content.Context;
  2 import android.graphics.Canvas;
  3 import android.graphics.Color;
  4 import android.graphics.Paint;
  5 import android.graphics.Path;
  6 import android.graphics.Point;
  7 import android.support.annotation.Nullable;
  8 import android.util.AttributeSet;
  9 import android.view.View;
 10 
 11 public class BezierView extends View {
 12     public BezierView(Context context) {
 13         this(context, null);
 14     }
 15 
 16     public BezierView(Context context, @Nullable AttributeSet attrs) {
 17         this(context, attrs, 0);
 18     }
 19 
 20     public BezierView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
 21         super(context, attrs, defStyleAttr);
 22         initPoints();
 23         initPaint();
 24     }
 25 
 26     @Override
 27     protected void onDraw(Canvas canvas) {
 28         super.onDraw(canvas);
 29 //        drawBezier_2(canvas);//绘制二阶贝塞尔曲线
 30         drawBezier_3(canvas);//绘制三阶贝塞尔曲线
 31     }
 32 
 33     Paint mPaint, mPaintForPoint, mPaintForControlPoint;
 34     Path mPath;
 35     float lineWidth = 5, pointWidth = 10;
 36 
 37     Point p1, p2, p3;// 途经点
 38     Point pControl_1, pControl_2;// 控制点,用于控制曲线的曲度
 39 
 40     private void initPaint() {
 41         mPaint = new Paint();
 42         mPath = new Path();
 43         mPaint.setStrokeWidth(lineWidth);
 44         mPaint.setColor(Color.BLACK);
 45         mPaint.setAntiAlias(true);
 46         mPaint.setStyle(Paint.Style.STROKE);
 47 
 48         mPaintForPoint = new Paint();
 49         mPaintForPoint.setStrokeWidth(pointWidth);
 50         mPaintForPoint.setColor(Color.GREEN);
 51         mPaintForPoint.setAntiAlias(true);
 52         mPaintForPoint.setStyle(Paint.Style.FILL);
 53 
 54         mPaintForControlPoint = new Paint();
 55         mPaintForControlPoint.setStrokeWidth(pointWidth);
 56         mPaintForControlPoint.setColor(Color.BLUE);
 57         mPaintForControlPoint.setAntiAlias(true);
 58         mPaintForControlPoint.setStyle(Paint.Style.FILL);
 59     }
 60 
 61     private void initPoints() {
 62         p1 = new Point(20, 20);
 63         p2 = new Point(300, 300);
 64         p3 = new Point(500, 20);
 65         pControl_1 = new Point(20, 300);
 66         pControl_2 = new Point(300, 20);
 67     }
 68 
 69     /**
 70      * 绘制二阶贝塞尔曲线,顺便把途径的点,和控制点都画出来
 71      */
 72     private void drawBezier_2(Canvas canvas) {
 73         //途径点以及控制点
 74         canvas.drawPoint(p1.x, p1.y, mPaintForPoint);
 75         canvas.drawPoint(p2.x, p2.y, mPaintForPoint);
 76         canvas.drawPoint(pControl_1.x, pControl_1.y, mPaintForControlPoint);
 77 
 78         // 重置路径
 79         mPath.reset();
 80         mPath.moveTo(p1.x, p1.y);// 绘制贝塞尔曲线之前,要指定一个起点,如果你不指定,他就会以原点为起点
 81         mPath.quadTo(pControl_1.x, pControl_1.y, p2.x, p2.y);//绘制二阶贝塞尔曲线,需要提供一个控制点的坐标,一个终点坐标,(还有一个起点坐标,只不过是在上一步设置的)
 82 
 83         canvas.drawPath(mPath, mPaint);
 84     }
 85 
 86     /**
 87      * 绘制三阶贝塞尔曲线,顺便把途径的点,和控制点都画出来
 88      */
 89     private void drawBezier_3(Canvas canvas) {
 90         canvas.drawPoint(p1.x, p1.y, mPaintForPoint);
 91         canvas.drawPoint(p3.x, p3.y, mPaintForPoint);
 92 
 93         canvas.drawPoint(pControl_1.x, pControl_1.y, mPaintForControlPoint);
 94         canvas.drawPoint(pControl_2.x, pControl_2.y, mPaintForControlPoint);
 95         // 重置路径
 96         mPath.reset();
 97         mPath.moveTo(p1.x, p1.y);// 绘制贝塞尔曲线之前,要指定一个起点,如果你不指定,他就会以原点为起点
 98         mPath.cubicTo(pControl_1.x, pControl_1.y, pControl_2.x, pControl_2.y, p3.x, p3.y);
 99 
100         canvas.drawPath(mPath, mPaint);
101     }
102 
103 }

绘制的三阶贝塞尔曲线如图:

  

绘制的二阶贝塞尔曲线如下:

蓝色的点,是控制点,可以看到,曲线在绘制的过程中在尽量靠近控制点;

绿色的点是 途经点(在这里也可以理解为 起点 和终点,因为 这两个api只需要起点和终点,其他的都是控制点)

 

======================================================================================

结语

利用贝塞尔曲线做特效,上面的两种方式各有千秋,但是基本上脱离不了 数学公式原理,只不过 后者将公式写进了API,前者我们可以自由发挥。

写得很基础,毕竟万丈高楼平地起。

新技能get··以后要做曲线不用愁了,比如,移动的波浪,双重移动波浪等·······

我已经看到吊炸天的特效在向我招手了,(●´∀`●)·~~~~~~~~~~~~~

 

posted @ 2018-08-23 17:05  波澜不惊x  阅读(1619)  评论(0编辑  收藏  举报