android中贝塞尔曲线的应用示例
前言:
贝塞尔曲线又称贝兹曲线,它的主要意义在于无论是直线或曲线都能在数学上予以描述。最初由保罗·德卡斯特里奥(paul de casteljau)于1959年运用德卡斯特里奥演算法开发(de casteljau algorithm),在1962,由法国工程师皮埃尔·贝塞尔(pierre bézier)所广泛发表。目前广泛应用于图形绘制领域来模拟光滑曲线,为计算机矢量图形学奠定了基础。在一些图形处理软件中都能见到贝塞尔曲线,比如coreldraw中翻译成“贝赛尔工具”;而在fireworks中叫“画笔”;photoshop中叫“钢笔工具”。下图为photoshop中用钢笔绘制的贝塞尔曲线,共绘制了三条贝塞尔曲线:
数学表达
术语:数据点、控制线、控制点、德卡斯特里奥算法、一阶,二阶,三阶,n阶……
- 数据点:一条贝塞尔曲线的起始点和终结点都叫数据点。
- 控制线:在图中可以看到那条中心点为数据点的线段,每个数据点对应一条控制线
- 控制点:就是控制线的端点,通过控制线随着控制点的变化而变化;数据点和控制点决定一条贝塞尔曲线。
- 一阶贝塞尔曲线:其实是一条直线段,没有控制点。
一阶贝塞尔曲线示意图
一阶贝塞尔曲线公式
二阶贝塞尔曲线:图中第二段为二阶贝塞尔曲线,只有一个控制点,即只有一个控制点和两个数据点来决定曲线形状。
二阶贝塞尔曲线公式
二阶公式推导:
二阶贝塞尔曲线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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。