欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2023-11-17 12:14:46
前言: 贝塞尔曲线又称贝兹曲线,它的主要意义在于无论是直线或曲线都能在数学上予以描述。最初由保罗·德卡斯特里奥(paul de casteljau)于1959年运用德卡斯...

前言:

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

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

数学表达

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

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

一阶贝塞尔曲线示意图

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

一阶贝塞尔曲线公式android中贝塞尔曲线的应用示例

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

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

二阶贝塞尔曲线公式android中贝塞尔曲线的应用示例

二阶公式推导:

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

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

问题变为:已知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]

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

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

三阶贝塞尔曲线

三阶贝塞尔曲线公式android中贝塞尔曲线的应用示例

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

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

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

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

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

7阶贝塞尔曲线

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

8阶贝塞尔曲线

3.demo

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

3.1 形状变形

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

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

初始化

  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 漂浮的爱心

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

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

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

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

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