android中贝塞尔曲线的应用示例

前言:

贝塞尔曲线又称贝兹曲线,它的主要意义在于无论是直线或曲线都能在数学上予以描述。最初由保罗·德卡斯特里奥(Paul de Casteljau)于1959年运用德卡斯特里奥演算法开发(de Casteljau Algorithm),在1962,由法国工程师皮埃尔·贝塞尔(Pierre Bézier)所广泛发表。目前广泛应用于图形绘制领域来模拟光滑曲线,为计算机矢量图形学奠定了基础。在一些图形处理软件中都能见到贝塞尔曲线,比如CorelDraw中翻译成“贝赛尔工具”;而在Fireworks中叫“画笔”;Photoshop中叫“钢笔工具”。下图为Photoshop中用钢笔绘制的贝塞尔曲线,共绘制了三条贝塞尔曲线:

数学表达

术语:数据点、控制线、控制点、德卡斯特里奥算法、一阶,二阶,三阶,n阶……

  1. 数据点:一条贝塞尔曲线的起始点和终结点都叫数据点。
  2. 控制线:在图中可以看到那条中心点为数据点的线段,每个数据点对应一条控制线
  3. 控制点:就是控制线的端点,通过控制线随着控制点的变化而变化;数据点和控制点决定一条贝塞尔曲线。
  4. 一阶贝塞尔曲线:其实是一条直线段,没有控制点。

一阶贝塞尔曲线示意图

一阶贝塞尔曲线公式

二阶贝塞尔曲线:图中第二段为二阶贝塞尔曲线,只有一个控制点,即只有一个控制点和两个数据点来决定曲线形状。

二阶贝塞尔曲线公式

二阶公式推导:


二阶贝塞尔曲线t=0.6示意图.gif
根据控制点和数据点,对贝塞尔曲线进行约束,满足的条件为

问题变为:已知P0(x0,y0), P1(x1,y1), P2(x2,y2),根据上式求P点坐标?

先求出A、B点坐标,其坐标

PA=P0+(P1-P0)·t

PB=P1+(P2-P1)·t

之后求P点坐标,其坐标

P=PA+(PB-PA)·t

P=(1-t)2P0+2t(1-t)P1+t2P2, t∈[0,1]

三阶贝塞尔曲线:图中第三段为三阶贝塞尔曲线,有两个控制点和两个数据点决定的曲线,同样满足等比条件:

三阶贝塞尔曲线

三阶贝塞尔曲线公式

德卡斯特里奥算法的思想:给定数据点和控制点P0、P1…Pn,首先将数据点和控制点连接形成一条折线,计算出每条折线上面的一点,使得初始数据点(初始控制点)到该点的距离与初始数据点(初始控制点)到终止数据点(终止控制点)的距离之比为t:1。将这些点连接起来形成新的折线(折线少了一段),用递归的算法继续计算,指导只有两个点,在这两个点形成的线段上去一点,满足以上的比例关系。随着t的从0到1的变化,该点的集合形成了贝塞尔曲线。

n阶贝塞尔曲线公式如下。

Android中的应用

对android中如何获取贝塞尔曲线上的点,如何绘制贝塞尔曲线,以及结合贝塞尔曲线的知识做出的效果进行分析。

1. 获取贝塞尔曲线上点的坐标

在android中并没有直接获取的方法,因此需要利用上面的公式进行计算,以二阶贝塞尔曲线为例,获取51个点,主要是t从0开始到1间取51项的等差数列:

public class MainActivity extends AppCompatActivity {

  private static final float mPointNum = 50f;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    init();
  }

  private void init() {
    PointF mStartPoint = new PointF(0, 0);
    PointF mEndPoint = new PointF(0, 1200);
    PointF mControlPoint = new PointF(500, 600);

    List<PointF> mPointList = new ArrayList<>();
    for (int i = 0; i <= mPointNum; i++) {
      mPointList.add(getBezierPoint(mStartPoint, mEndPoint, mControlPoint, i / mPointNum));
      Log.d("Bezier", "X:" + mPointList.get(i).x + " Y:" + mPointList.get(i).y);
    }
  }

  private PointF getBezierPoint(PointF start, PointF end, PointF control, float t) {
    PointF bezierPoint = new PointF();
    bezierPoint.x = (1 - t) * (1 - t) * start.x + 2 * t * (1 - t) * control.x + t * t * end.x;
    bezierPoint.y = (1 - t) * (1 - t) * start.y + 2 * t * (1 - t) * control.y + t * t * end.y;
    return bezierPoint;
  }
}

如果需要更高阶,可以使用递归函数来运算

//用递归获取贝塞尔曲线点的x轴坐标
private float getBezierPointX(int n, int position, float t) {
    if (n == 1) {
      return (1 - t) * mPointList.get(position).x + t * mPointList.get(position + 1).x;
    }
    return (1 - t) * getBezierPointX(n - 1, position, t) + t * getBezierPointX(n - 1, position + 1, t);
  }
//用递归获取贝塞尔曲线点的x轴坐标
private float getBezierPointX(int n, int position, float t) {
    if (n == 1) {
      return (1 - t) * mPointList.get(position).x + t * mPointList.get(position + 1).x;
    }
    return (1 - t) * getBezierPointX(n - 1, position, t) + t * getBezierPointX(n - 1, position + 1, t);
  }
private ArrayList<PointF> buildBezierPoints() {
    ArrayList<PointF> points = new ArrayList<>();
    int order = mPointList.size() - 1;
    float delta = 1.0f / POINT_NUM;
    for (float t = 0; t <= 1; t += delta) {
      // Bezier点集
      points.add(new PointF(getBezierPointX(order, 0, t), getBezierPointY(order, 0, t)));
    }
    return points;
  }

2. 绘制贝塞尔曲线

Android 中的Path类可以直接绘制一阶到三阶的贝塞尔曲线,在onDraw(Canvas canvas) 方法中使用:

绘制一阶贝塞尔曲线:

canvas.drawLine(start.x,start.y,end.x,end.y);

绘制二阶贝塞尔曲线:

mPath.moveTo(startPoint.x, startPoint.y);//起点
mPath.quadTo(controlPoint1.x, controlPoint1.y, endPoint.x, endPoint.y);
canvas.drawPath(mPath, mPaint);

绘制三阶贝塞尔曲线:

mPath.moveTo(startPoint.x, startPoint.y);//起点
mPath.cubicTo(controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, endPoint.x, endPoint.y);
canvas.drawPath(mPath, mPaint);

绘制n阶贝塞尔曲线

n阶贝塞尔曲线绘制,需要结合递归函数,设定一条曲线由多少个点组成,通过循环获取每个点的坐标进行绘制。

7阶贝塞尔曲线

8阶贝塞尔曲线

3.Demo

贝塞尔曲线在android中最常用的是做出一些动画特效,如QQ消息数拖拽形变效果,一些炫酷的下拉刷新控件,阅读软件的翻书效果,一些平滑的折线图的制作,某图片的运动轨迹等……

3.1 形状变形

只需要知道变形前和变形后图形的数据点和控制点即可,通过改变数据点和控制点使得形状发生改变。

初始化

  private float[] mData = new float[8];        // 顺时针记录绘制圆形的四个数据点
  private float[] mCtrl = new float[16];       // 顺时针记录绘制圆形的八个控制点

  private float mDuration = 1000;           // 变化总时长
  private float mCurrent = 0;             // 当前已进行时长
  private float mCount = 100;             // 将时长总共划分多少份
  private float mPiece = mDuration / mCount;      // 每一份的时长

在onDraw(Canvas canvas)方法中绘制

    path.reset();
    path.moveTo(mData[0], mData[1]);

    path.cubicTo(mCtrl[0], mCtrl[1], mCtrl[2], mCtrl[3], mData[2], mData[3]);
    path.cubicTo(mCtrl[4], mCtrl[5], mCtrl[6], mCtrl[7], mData[4], mData[5]);
    path.cubicTo(mCtrl[8], mCtrl[9], mCtrl[10], mCtrl[11], mData[6], mData[7]);
    path.cubicTo(mCtrl[12], mCtrl[13], mCtrl[14], mCtrl[15], mData[0], mData[1]);

    canvas.drawPath(path, mPaint);

    mCurrent += mPiece;
    if (mCurrent < mDuration) {

      mData[1] -= 120 / mCount;
      mCtrl[7] += 80 / mCount;
      mCtrl[9] += 80 / mCount;

      mCtrl[4] -= 20 / mCount;
      mCtrl[10] += 20 / mCount;

      postInvalidateDelayed((long) mPiece);
    }

3.2 漂浮的爱心

爱心的漂浮轨迹就是一条三阶贝塞尔曲线,结合属性动画中的估值器进行设置。

首先定义一个属性动画的估值器

public class BezierEvaluator implements TypeEvaluator<PointF> {

 private PointF mControlP1;
 private PointF mControlP2;

 public BezierEvaluator(PointF controlP1, PointF controlP2) {
   this.mControlP1 = controlP1;
   this.mControlP2 = controlP2;
 }

 @Override
 public PointF evaluate(float time, PointF start, PointF end) {

   float timeLeft = 1.0f - time;
   PointF point = new PointF();

   point.x = timeLeft * timeLeft * timeLeft * (start.x) + 3 * timeLeft * timeLeft * time *
       (mControlP1.x) + 3 * timeLeft * time *
       time * (mControlP2.x) + time * time * time * (end.x);

   point.y = timeLeft * timeLeft * timeLeft * (start.y) + 3 * timeLeft * timeLeft * time *
       (mControlP1.y) + 3 * timeLeft * time *
       time * (mControlP2.y) + time * time * time * (end.y);
   return point;
 }
}

之后自定义一个view可以生成爱心,添加透明度,缩放等动画和根据贝塞尔曲线改变其位置的属性动画。

初始化爱心图片和多个插值器等,到时随即选取

 private void init() {

    // 初始化显示的图片
    drawables = new Drawable[3];
    drawables[0] = getResources().getDrawable(R.drawable.red);
    drawables[1] = getResources().getDrawable(R.drawable.yellow);
    drawables[2] = getResources().getDrawable(R.drawable.green);

    // 初始化插补器
    mInterpolators = new Interpolator[4];
    mInterpolators[0] = new LinearInterpolator();// 线性
    mInterpolators[1] = new AccelerateInterpolator();// 加速
    mInterpolators[2] = new DecelerateInterpolator();// 减速
    mInterpolators[3] = new AccelerateDecelerateInterpolator();// 先加速后减速

    // 底部 并且 水平居中
    dWidth = drawables[0].getIntrinsicWidth();
    dHeight = drawables[0].getIntrinsicHeight();
    lp = new LayoutParams(dWidth, dHeight);
    lp.addRule(CENTER_HORIZONTAL, TRUE);// 这里的TRUE 要注意 不是true
    lp.addRule(ALIGN_PARENT_BOTTOM, TRUE);

  }

入场动画

private AnimatorSet getEnterAnimator(final View target) {
    ObjectAnimator alpha = ObjectAnimator.ofFloat(target, View.ALPHA, 0.2f, 1f);
    ObjectAnimator scaleX = ObjectAnimator.ofFloat(target, View.SCALE_X, 0.2f, 1f);
    ObjectAnimator scaleY = ObjectAnimator.ofFloat(target, View.SCALE_Y, 0.2f, 1f);
    AnimatorSet enter = new AnimatorSet();
    enter.setTarget(target);
    enter.setInterpolator(new LinearInterpolator());
    enter.setDuration(500).playTogether(alpha, scaleX, scaleY);
    return enter;
  }

贝塞尔曲线动画

  private ValueAnimator getBezierValueAnimator(final View target) {
    // 初始化贝塞尔估值器
    BezierEvaluator evaluator = new BezierEvaluator(getPointF(2), getPointF(1));
    // 起点在底部中心位置,终点在底部随机一个位置
    ValueAnimator animator = ValueAnimator.ofObject(evaluator, new PointF((mWidth - dWidth) /
        2, mHeight - dHeight), new PointF(random.nextInt(getWidth()), 0));
    animator.setTarget(target);
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override
      public void onAnimationUpdate(ValueAnimator valueAnimator) {
        // 这里获取到贝塞尔曲线计算出来的的x y值 赋值给view 这样就能让爱心随着曲线走啦
        PointF pointF = (PointF) valueAnimator.getAnimatedValue();
        target.setX(pointF.x);
        target.setY(pointF.y);
        // alpha动画
        target.setAlpha(1 - valueAnimator.getAnimatedFraction());
      }
    });

    animator.setDuration(3000);
    return animator;
  }

结合动画添加爱心

public void addHeart() {

    final ImageView imageView = new ImageView(getContext());
    // 随机选一个爱心
    imageView.setImageDrawable(drawables[random.nextInt(3)]);
    imageView.setLayoutParams(lp);
    addView(imageView);

    AnimatorSet finalSet = new AnimatorSet();

    AnimatorSet enterAnimatorSet = getEnterAnimator(imageView);//入场动画
    ValueAnimator bezierValueAnimator = getBezierValueAnimator(imageView);//贝塞尔曲线路径动画

    finalSet.playSequentially(enterAnimatorSet, bezierValueAnimator);
    finalSet.setInterpolator(mInterpolators[random.nextInt(4)]);
    finalSet.setTarget(imageView);

    finalSet.addListener(new AnimatorListenerAdapter() {
      @Override
      public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
        removeView((imageView));//删除爱心
      }
    });
    finalSet.start();

  }

3.3 贝塞尔曲线侧边索引条

实例地址:FancyListIndexer_jb51.rar

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android贝塞尔曲线初步学习第一课

    贝塞尔曲线有一阶.二阶.三阶.N阶 一阶就是一条直线,有起点终点,没有控制点,对应方法就是 canvas.drawLine(float startX, float startY, float stopX, float stopY, @NonNull Paint paint) ; 二阶为曲线,有起点终点,一个控制点,对应方法就是 path.quadTo(float x1, float y1, float x2, float y2); 其中x1.y1为控制点坐标, x2.y2为终点坐标,效果如下:

  • Android贝塞尔曲线初步学习第二课 仿QQ未读消息气泡拖拽黏连效果

    上一节初步了解了Android端的贝塞尔曲线,这一节就举个栗子练习一下,仿QQ未读消息气泡,是最经典的练习贝塞尔曲线的东东,效果如下 附上github源码地址:https://github.com/MonkeyMushroom/DragBubbleView 欢迎star~ 大体思路就是画两个圆,一个黏连小球固定在一个点上,一个气泡小球跟随手指的滑动改变坐标.随着两个圆间距越来越大,黏连小球半径越来越小.当间距小于一定值,松开手指气泡小球会恢复原来位置:当间距超过一定值之后,黏连小球消失,气泡小球

  • Android Path绘制贝塞尔曲线实现QQ拖拽泡泡

    这两天学习了使用Path绘制贝塞尔曲线相关,然后自己动手做了一个类似QQ未读消息可拖拽的小气泡,效果图如下: 最终效果图 接下来一步一步的实现整个过程. 基本原理 其实就是使用Path绘制三点的二次方贝塞尔曲线来完成那个妖娆的曲线的.然后根据触摸点不断绘制对应的圆形,根据距离的改变改变原始固定圆形的半径大小.最后就是松手后返回或者爆裂的实现. Path介绍: 顾名思义,就是一个路径的意思,Path里面有很多的方法,本次设计主要用到的相关方法有 moveTo() 移动Path到一个指定的点 qua

  • Android贝塞尔曲线初步学习第三课 Android实现添加至购物车的运动轨迹

    不知上一节高仿QQ未读消息气泡大家还喜欢么,今天继续练习贝赛尔曲线,这一节我们通过贝赛尔曲线和属性动画估值器实现添加至购物车的运动轨迹,效果如下: 1.新建自定义View,重写构造方法,初始化Paint.Path: 2.确定起始点.终止点.控制点坐标,这里我们直接固定: @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh);

  • Android把商品添加到购物车的动画效果(贝塞尔曲线)

    当我们写商城类的项目的时候,一般都会有加入购物车的功能,加入购物车的时候会有一些抛物线动画,具体代码如下: 实现效果如图: 思路: 确定动画的起终点 在起终点之间使用二次贝塞尔曲线填充起终点之间的点的轨迹 设置属性动画,ValueAnimator插值器,获取中间点的坐标 将执行动画的控件的x.y坐标设为上面得到的中间点坐标 开启属性动画 当动画结束时的操作 难点: PathMeasure的使用 - getLength() - boolean getPosTan(float distance, f

  • Android中贝塞尔曲线的绘制方法示例代码

    贝塞尔曲线,很多人可能不太了解,什么叫做贝塞尔曲线呢?这里先做一下简单介绍:贝塞尔曲线也可以叫做贝济埃曲线或者贝兹曲线,它由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋.一般的矢量图形软件常利用贝塞尔曲线来精确画出曲线. 上面的介绍中,"线段像可伸缩的皮筋"这句话非常关键,但也特别好理解.至于贝塞尔曲线的详细内容大家可以查阅相关资料.        Android提供的贝塞尔曲线绘制接口 在Android开发中,要实现贝塞尔曲线其实还是很简单的,因为Android已经给我们提

  • android中贝塞尔曲线的应用示例

    前言: 贝塞尔曲线又称贝兹曲线,它的主要意义在于无论是直线或曲线都能在数学上予以描述.最初由保罗·德卡斯特里奥(Paul de Casteljau)于1959年运用德卡斯特里奥演算法开发(de Casteljau Algorithm),在1962,由法国工程师皮埃尔·贝塞尔(Pierre Bézier)所广泛发表.目前广泛应用于图形绘制领域来模拟光滑曲线,为计算机矢量图形学奠定了基础.在一些图形处理软件中都能见到贝塞尔曲线,比如CorelDraw中翻译成"贝赛尔工具":而在Firewo

  • Android利用贝塞尔曲线绘制动画的示例代码

    目录 彩虹系列 弹簧动画 复杂立体感动画 总结 前面我们花了几篇介绍了贝塞尔曲线的原理和绘制贝塞尔曲线,着实让我们见识到了贝塞尔曲线的美.好奇心驱使我想看看贝塞尔曲线动起来会是什么样?本篇就借由动画驱动贝塞尔曲线绘制看看动起来的贝塞尔曲线什么效果. 彩虹系列 通过动画控制绘制的结束点,就可以让贝塞尔曲线动起来.例如下面的动图展示的效果,看起来像搭了一个滑滑梯一样.实际上就是用7条贝塞尔曲线实现的,我们使用了 Animation 对象的值来控制绘制的结束点,从而实现了对应的动画效果. 具体源码如下

  • android实现贝塞尔曲线之波浪效果

    本文实例为大家分享了android实现贝塞尔曲线之波浪效果的具体代码,供大家参考,具体内容如下 1 前言 为了给我以前的博客填坑,这章讲解贝塞尔曲线的几个常用的应用: 1.波浪效果2.qq聊天列表上的沾粘体效果3.翻书页效果4.弹性球效果 大家如果把这些看懂并掌握,以后做和贝塞尔曲线相关的效果应该都能信手拈来! 2 波浪效果 原理分析: 其实这个效果应用了2个阶的贝塞尔曲线来完成的,先看一下原理分析图: 有上面的图可以看出:在屏幕的左面画出了1.5个波长,在屏幕中画出1个波长,然后让它循环的向右

  • Android使用贝塞尔曲线画心形

    本文实例为大家分享了Android使用贝塞尔曲线画心形的具体代码,供大家参考,具体内容如下 一开始我只是想画个圆,可画着画着就成了心形,那就将错就错 1. 创建一个Activity RelativeLayout container = findViewById(R.id.download_container);     DisplayMetrics metrics = new DisplayMetrics();     getWindowManager().getDefaultDisplay()

  • Android中的人脸检测的示例代码(静态和动态)

    (1)背景. Google 于2006年8月收购Neven Vision 公司 (该公司拥有10多项应用于移动设备领域的图像识别的专利),以此获得了图像识别的技术,并加入到android中.Android 中的人脸识别技术,用到的底层库:android/external/neven/,framework 层:frameworks/base/media/java/android/media/FaceDetector.java. Java 层接口的限制:A,只能接受Bitmap 格式的数据:B,只能

  • JavaScript实现的贝塞尔曲线算法简单示例

    本文实例讲述了JavaScript实现的贝塞尔曲线算法.分享给大家供大家参考,具体如下: 如果在HTML5支持好的浏览器中,可以看到用svg绘制的路径线. 在所有浏览器中,均可以看到一个小方块沿着贝塞尔曲线路径来回运动. 效果图: 主要代码: <div style="position:absolute;left:0;top:0;width:500px;height:300px;overflow:hidden;"> <svg id="root" wi

  • Android中使用protobuf的具体示例

    简介 Google Protocol Buffers 简称 Protobuf,类似 json 或 XML,是一种序列化结构数据的机制,但是比它们更小.更快.更简单.同时支持多语言,跨平台. 目前主要有两个大版本:proto2 和 proto3. 其中 proto2 支持 Java.Python. Objective-C.和 C++. proto3 增加了对Go.JavaNano.Ruby.和 C#的支持. proto例子 syntax = "proto3"; package tutor

  • Android中Fragment的基本用法示例总结

    前言 fragment 可认为是一个轻量级的Activity,但不同与Activity,它是要嵌到Activity中来使用的,它用来解决设备屏幕大小的不同,主要是充分利用界面上的空间,如平板上多余的空间.一个Activity可以插入多个Fragment,可以认为Fragment就是Activity上的一个View. 本文主要介绍了关于Android中Fragment的基本用法,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 一.fragment管理 在activity动态加载f

  • android中LinearLayoutManager一键返回顶部示例

    之前在学习RecyclerView的时候,建立了一个可以滑动的View列表,但是当滑动距离过长的时候,需要手动返回到顶部,于是加了一个一键返回顶部的按钮. 效果图 要实现这种效果,有两点需要实现: 1.控制LanyoutManager滑动距离,根据滑动状态(距离)改变去设置隐藏或者显示. 2.设置top的点击事件,点击回到顶部. 很遗憾LayoutManager只提供给了我们获取第一个可见item的高度的方法,而这里我们需要获取的是从开始到现在滑动的总距离,所以不得不动手去自己写,网上也有很多方

随机推荐