此处唯有是技能完毕.,上面将通过代码来效仿二阶和3阶的贝塞尔曲线是如何绘制和决定的

GitHub地址:https://github.com/ptddqr/wpf-samsung-phone-s5-charging-ui/tree/master

一句话归纳贝塞尔曲线:将随便一条曲线转化为规范的数学公式。

先上效果图

无数制图工具中的钢笔工具,正是超人的贝塞尔曲线的选取,这里的多个网址能够在线模拟钢笔工具的使用:

图片 1

http://bezier.method.ac/

本条效能来自于三星(Samsung)S5的充电分界面,版权归Samsung全数,这里仅仅是本事达成.

图片 22.png

电池背景

因为电池内部有大多少个部分,所以本例用了二个Grid来做背景,用Clip属性剪切出四个电池的概略,那样不光展现出2个电池的概貌,还足防止止水波和气泡跑展现Grid的外面.

Clip的里边,是三个Path形状.具体画法就不多说了,从前写过.风乐趣的同班看这里:http://www.cnblogs.com/tsliwei/p/5609035.html

图片 3

贝塞尔曲线中有一点点相比关键的名词,解释如下:

表示电量的液体效果

整套液体分两有个别,上边是波浪,下边是矩形.进程值实际决定的是矩形的高度.三个控件放到StackPanel中,让上边包车型地铁矩形往上顶.最终给波浪底部Margin值为-1,使其看起来未有间隙.

图片 4

波浪是用贝塞尔曲线达成的,首先介绍下贝塞尔曲线

图片 5

贝塞尔曲线有6个点,源点终点和八个调控点.(此括号里的能够不看:上画画的并不标准,因为调控点并不一定在曲线上).通过三个调整点决定曲线的路线.

综上说述上海体育场地那本人就是个波浪形.使用点动画PointAnimation调节多个点上下移动就有了波浪的动态效果.注意八个卡通时间毫无同样,不然看起来动画太假.七个时辰错开一丝丝就好了.

图片 6

波浪部分宽度是50,高度是五

图片 7

  • 数分部:平时指一条路径的起首点和终止点
  • 调节点:调控点决定了一条渠道的波折轨迹,依据调节点的个数,贝塞尔曲线被分为一阶贝塞尔曲线、二阶贝塞尔曲线、三阶贝塞尔曲线等等。

气泡效果

此间的血泡效果正是个典型的粒子效果,而且是最简易的这种,并不涉及到何以复杂的公式计算.

简轻易单介绍下原理:这里的血泡能够看成是圆遵照一定的进度不断的上升(退换Y轴坐标).所以规定1个速率,规定2个相差,使用帧动画CompositionTarget.Rendering,在每1帧都在Y轴上加那个速率在一帧移动的距离.然后决断又没到达规定的距离.若是到达,移除这么些圈子,不然继续上涨.

气泡能够分成多个部分:

一.电池内部的气泡.大小合适,移动速度最慢,移动距离最短.

二.显示器底边的大气泡,个头一点都不小,移动速度一点也不快,移动距离异常的短.

三.显示器底边的小气泡,个头十分小,移动速度非常快,移动距离较远.

新建多个Class,用来表示气泡音讯

图片 8

中间八个注重性质,二个是速率,二个是气泡须要活动的距离.这多少个性子决定了血泡的活动轨迹.第多个属性是用来推断气泡是否到位了重任,第6个天性是加上1个对气泡的引用,那样便于在后台调整气泡.

概念四个聚众,用来存放在三局地的血泡消息.

在帧渲染事件内,遍历多个会集.让集合里的每一种气泡都提升移动(Canvas.SetTop),推断气泡是或不是已经移动了钦点的偏离,是的话就在页面移除气泡,集结也移除该气泡音讯.推断集合的Count是还是不是紧跟于规定个个数,借使低于,就向页面增添气泡,会集增多气泡新闻.

要想对贝塞尔曲线有1个相比好的认知,能够参考WIKI上的链接:

画气泡

为了赏心悦目,小编自身画了个气泡的模型,用在了汪洋泡上.小气泡直接用的扁圆形,因为即利用模型,因为太小,也看不出来.实际上海大学气泡也多少看得出来.但是既然写了,照旧介绍下吧.

图片 9

第2那个气泡正是个View博克斯.方便缩放.

大致是个正圆,Fill给了个渐变画刷,向外不断加剧,在最外面0.85-壹的部分是最深的.四个点的奥迪Q伍都是20,B都以10,海蓝部分G依次减小,分别是240,150,100.

图片 10

右下面的月牙是个帕特h,给了个半径是十的混淆效果.Fill是半晶莹剔透的蓝灰.月牙的画法正是四个弧线,源点和终端同样,半径差异.

图片 11

左上角的亮点正是三个椭圆,和月牙一样.半径是拾的混淆效果.Fill是半晶莹剔透的深湖蓝.

 

2016-12-19更新:

发布到GitHub,地址:https://github.com/ptddqr/wpf-samsung-phone-s5-charging-ui/tree/master

源码下载: 苹果手提式有线话机电池充电效果.rar

https://en.wikipedia.org/wiki/B%C3%A9zier\_curve

图片 121.png

在Android中,一般的话,开拓者只思考二阶贝塞尔曲线和叁阶贝塞尔曲线,SDK也只提供了二阶和三阶的API调用。对于再高阶的贝塞尔曲线,平日能够将曲线拆分成几个低阶的贝塞尔曲线,约等于所谓的降阶操作。下边将透过代码来效仿二阶和三阶的贝塞尔曲线是哪些绘制和垄断(monopoly)的。

贝塞尔曲线的贰个相比较好的动态演示如下所示:

http://myst729.github.io/bezier-curve/

图片 1320.png

二阶模拟

二阶贝塞尔曲线在Android中的API为:quadTo()和rQuadTo(),那多少个API在常理上是足以相互调换的——quadTo是依靠相对坐标,而rQuadTo是基于相对坐标,所之前面小编都只以个中3个来进展疏解。

先来看下最后的功能:

图片 143.gif

以前面包车型客车牵线能够知晓,二阶贝塞尔曲线有七个数总局和贰个调节点,只须要在代码中绘制出这一个协理点和支持线就能够,同时,调控点能够经过onTouch伊夫nt来进行传递。

package com.xys.animationart.views;import android.content.Context;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.Path;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;/** * 二阶贝塞尔曲线 * <p/> * Created by xuyisheng on 16/7/11. */public class SecondOrderBezier extends View { private Paint mPaintBezier; private Paint mPaintAuxiliary; private Paint mPaintAuxiliaryText; private float mAuxiliaryX; private float mAuxiliaryY; private float mStartPointX; private float mStartPointY; private float mEndPointX; private float mEndPointY; private Path mPath = new Path(); public SecondOrderBezier(Context context) { super; } public SecondOrderBezier(Context context, AttributeSet attrs) { super(context, attrs); mPaintBezier = new Paint(Paint.ANTI_ALIAS_FLAG); mPaintBezier.setStyle(Paint.Style.STROKE); mPaintBezier.setStrokeWidth; mPaintAuxiliary = new Paint(Paint.ANTI_ALIAS_FLAG); mPaintAuxiliary.setStyle(Paint.Style.STROKE); mPaintAuxiliary.setStrokeWidth; mPaintAuxiliaryText = new Paint(Paint.ANTI_ALIAS_FLAG); mPaintAuxiliaryText.setStyle(Paint.Style.STROKE); mPaintAuxiliaryText.setTextSize; } public SecondOrderBezier(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mStartPointX = w / 4; mStartPointY = h / 2 - 200; mEndPointX = w / 4 * 3; mEndPointY = h / 2 - 200; } @Override protected void onDraw(Canvas canvas) { super.onDraw; mPath.reset(); mPath.moveTo(mStartPointX, mStartPointY); // 辅助点 canvas.drawPoint(mAuxiliaryX, mAuxiliaryY, mPaintAuxiliary); canvas.drawText("控制点", mAuxiliaryX, mAuxiliaryY, mPaintAuxiliaryText); canvas.drawText("起始点", mStartPointX, mStartPointY, mPaintAuxiliaryText); canvas.drawText("终止点", mEndPointX, mEndPointY, mPaintAuxiliaryText); // 辅助线 canvas.drawLine(mStartPointX, mStartPointY, mAuxiliaryX, mAuxiliaryY, mPaintAuxiliary); canvas.drawLine(mEndPointX, mEndPointY, mAuxiliaryX, mAuxiliaryY, mPaintAuxiliary); // 二阶贝塞尔曲线 mPath.quadTo(mAuxiliaryX, mAuxiliaryY, mEndPointX, mEndPointY); canvas.drawPath(mPath, mPaintBezier); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction { case MotionEvent.ACTION_MOVE: mAuxiliaryX = event.getX(); mAuxiliaryY = event.getY(); invalidate(); } return true; }}

三阶模拟

三阶贝塞尔曲线在Android中的API为:cubicTo()和rCubicTo(),那四个API在常理上是足以互相转换的——quadTo是依据相对坐标,而rCubicTo是依据相对坐标,所此前面作者都只以中间贰个来实行批注。

有了二阶的根基,再来模拟三阶就特别轻松了,无非是加多了贰个调节点而已,先看下效果图:

图片 154.gif

代码只需求在二阶的基础上增加一些协助点就可以,上面只交给一些关键代码,详细代码请参见Github:

 @Override protected void onDraw(Canvas canvas) { super.onDraw; mPath.reset(); mPath.moveTo(mStartPointX, mStartPointY); // 辅助点 canvas.drawPoint(mAuxiliaryOneX, mAuxiliaryOneY, mPaintAuxiliary); canvas.drawText("控制点1", mAuxiliaryOneX, mAuxiliaryOneY, mPaintAuxiliaryText); canvas.drawText("控制点2", mAuxiliaryTwoX, mAuxiliaryTwoY, mPaintAuxiliaryText); canvas.drawText("起始点", mStartPointX, mStartPointY, mPaintAuxiliaryText); canvas.drawText("终止点", mEndPointX, mEndPointY, mPaintAuxiliaryText); // 辅助线 canvas.drawLine(mStartPointX, mStartPointY, mAuxiliaryOneX, mAuxiliaryOneY, mPaintAuxiliary); canvas.drawLine(mEndPointX, mEndPointY, mAuxiliaryTwoX, mAuxiliaryTwoY, mPaintAuxiliary); canvas.drawLine(mAuxiliaryOneX, mAuxiliaryOneY, mAuxiliaryTwoX, mAuxiliaryTwoY, mPaintAuxiliary); // 三阶贝塞尔曲线 mPath.cubicTo(mAuxiliaryOneX, mAuxiliaryOneY, mAuxiliaryTwoX, mAuxiliaryTwoY, mEndPointX, mEndPointY); canvas.drawPath(mPath, mPaintBezier); }

仿照网页

正如所示的网页,模拟了3阶贝塞尔曲线的绘图,能够通过拖动曲线来获得八个调整点的坐标,而初始点分别是和。

http://cubic-bezier.com/

图片 1616.png

通过这几个网页,也得以相比方便的拿走叁阶贝塞尔曲线的主宰点坐标。

世故绘图

当在荧屏上制图路线时,举例手写板,最核心的主意是经过Path.lineTo将逐条触点连接起来,而这种办法在不少时候会意识,多少个点的连接是那多少个刚烈的,因为它究竟是通过直线来连接的,要是经过二阶贝塞尔曲线来将相继触点连接,就能油滑的多,不会产出太多的平板连接。

先来看下代码,特别简单的绘图路线代码:

package com.xys.animationart.views;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.view.ViewConfiguration;/** * 圆滑路径 * <p/> * Created by xuyisheng on 16/7/19. */public class DrawPadBezier extends View { private float mX; private float mY; private float offset = ViewConfiguration.get(getContext.getScaledTouchSlop(); private Paint mPaint; private Path mPath; public DrawPadBezier(Context context) { super; } public DrawPadBezier(Context context, AttributeSet attrs) { super(context, attrs); mPath = new Path(); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth; mPaint.setColor(Color.RED); } public DrawPadBezier(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction { case MotionEvent.ACTION_DOWN: mPath.reset(); float x = event.getX(); float y = event.getY(); mX = x; mY = y; mPath.moveTo; break; case MotionEvent.ACTION_MOVE: float x1 = event.getX(); float y1 = event.getY(); float preX = mX; float preY = mY; float dx = Math.abs(x1 - preX); float dy = Math.abs(y1 - preY); if (dx >= offset || dy >= offset) { // 贝塞尔曲线的控制点为起点和终点的中点 float cX = (x1 + preX) / 2; float cY = (y1 + preY) / 2;// mPath.quadTo(preX, preY, cX, cY); mPath.lineTo; mX = x1; mY = y1; } } invalidate(); return true; } @Override protected void onDraw(Canvas canvas) { super.onDraw; canvas.drawPath(mPath, mPaint); }}

先来看下通过mPath.lineTo来贯彻的绘图,效果如下所示:

图片 1718.png

图片中的拐点有备受关注的锯齿效果,即经过直线的再而三,再来看下通过贝塞尔曲线来连接的成效,平时状态下,贝塞尔曲线的调节点取三个再三再四点的正中:

mPath.quadTo(preX, preY, cX, cY);

透过二阶贝塞尔曲线的总是效果如图所示:

图片 1819.png

能够通晓于指标发掘,曲线变得极其狡猾了。

曲线变形

透过决定贝塞尔曲线的调整点,就足以兑现对一条路子的改造。所以,利用贝塞尔曲线,能够达成广大的路子动画,举个例子:

图片 195.gif

package com.xys.animationart;import android.animation.ValueAnimator;import android.content.Context;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.Path;import android.util.AttributeSet;import android.view.View;import android.view.animation.BounceInterpolator;/** * 曲线变形 * <p/> * Created by xuyisheng on 16/7/11. */public class PathMorphBezier extends View implements View.OnClickListener{ private Paint mPaintBezier; private Paint mPaintAuxiliary; private Paint mPaintAuxiliaryText; private float mAuxiliaryOneX; private float mAuxiliaryOneY; private float mAuxiliaryTwoX; private float mAuxiliaryTwoY; private float mStartPointX; private float mStartPointY; private float mEndPointX; private float mEndPointY; private Path mPath = new Path(); private ValueAnimator mAnimator; public PathMorphBezier(Context context) { super; } public PathMorphBezier(Context context, AttributeSet attrs) { super(context, attrs); mPaintBezier = new Paint(Paint.ANTI_ALIAS_FLAG); mPaintBezier.setStyle(Paint.Style.STROKE); mPaintBezier.setStrokeWidth; mPaintAuxiliary = new Paint(Paint.ANTI_ALIAS_FLAG); mPaintAuxiliary.setStyle(Paint.Style.STROKE); mPaintAuxiliary.setStrokeWidth; mPaintAuxiliaryText = new Paint(Paint.ANTI_ALIAS_FLAG); mPaintAuxiliaryText.setStyle(Paint.Style.STROKE); mPaintAuxiliaryText.setTextSize; setOnClickListener; } public PathMorphBezier(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mStartPointX = w / 4; mStartPointY = h / 2 - 200; mEndPointX = w / 4 * 3; mEndPointY = h / 2 - 200; mAuxiliaryOneX = mStartPointX; mAuxiliaryOneY = mStartPointY; mAuxiliaryTwoX = mEndPointX; mAuxiliaryTwoY = mEndPointY; mAnimator = ValueAnimator.ofFloat(mStartPointY, ; mAnimator.setInterpolator(new BounceInterpolator; mAnimator.setDuration; mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { mAuxiliaryOneY =  valueAnimator.getAnimatedValue(); mAuxiliaryTwoY =  valueAnimator.getAnimatedValue(); invalidate; } @Override protected void onDraw(Canvas canvas) { super.onDraw; mPath.reset(); mPath.moveTo(mStartPointX, mStartPointY); // 辅助点 canvas.drawPoint(mAuxiliaryOneX, mAuxiliaryOneY, mPaintAuxiliary); canvas.drawText("辅助点1", mAuxiliaryOneX, mAuxiliaryOneY, mPaintAuxiliaryText); canvas.drawText("辅助点2", mAuxiliaryTwoX, mAuxiliaryTwoY, mPaintAuxiliaryText); canvas.drawText("起始点", mStartPointX, mStartPointY, mPaintAuxiliaryText); canvas.drawText("终止点", mEndPointX, mEndPointY, mPaintAuxiliaryText); // 辅助线 canvas.drawLine(mStartPointX, mStartPointY, mAuxiliaryOneX, mAuxiliaryOneY, mPaintAuxiliary); canvas.drawLine(mEndPointX, mEndPointY, mAuxiliaryTwoX, mAuxiliaryTwoY, mPaintAuxiliary); canvas.drawLine(mAuxiliaryOneX, mAuxiliaryOneY, mAuxiliaryTwoX, mAuxiliaryTwoY, mPaintAuxiliary); // 三阶贝塞尔曲线 mPath.cubicTo(mAuxiliaryOneX, mAuxiliaryOneY, mAuxiliaryTwoX, mAuxiliaryTwoY, mEndPointX, mEndPointY); canvas.drawPath(mPath, mPaintBezier); } @Override public void onClick(View view) { mAnimator.start(); }}

此处正是轻易的改换二阶贝塞尔曲线的调节点来达成曲线的变形。

英特网一些相比较复杂的变形动画效果,也是基于这种完成格局,其规律都以透过改变调整点的职责,从而完毕对图纸的转移,举个例子圆形到心形的变迁、圆形到伍角星的改造,等等。

波浪效果

波浪的绘图是贝塞尔曲线三个极其轻便的选用,而让波浪进行波动,其实并没有须要对调节点实行改换,而是能够通过位移来达成,这里我们是信赖贝塞尔曲线来落到实处波浪的绘图效果,效果如图所示:

图片 206.gif

package com.xys.animationart.views;import android.animation.ValueAnimator;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.util.AttributeSet;import android.view.View;import android.view.animation.LinearInterpolator;/** * 波浪图形 * <p/> * Created by xuyisheng on 16/7/11. */public class WaveBezier extends View implements View.OnClickListener { private Paint mPaint; private Path mPath; private int mWaveLength = 1000; private int mOffset; private int mScreenHeight; private int mScreenWidth; private int mWaveCount; private int mCenterY; public WaveBezier(Context context) { super; } public WaveBezier(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public WaveBezier(Context context, AttributeSet attrs) { super(context, attrs); mPath = new Path(); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.LTGRAY); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); setOnClickListener; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mScreenHeight = h; mScreenWidth = w; mWaveCount =  Math.round(mScreenWidth / mWaveLength + 1.5); mCenterY = mScreenHeight / 2; } @Override protected void onDraw(Canvas canvas) { super.onDraw; mPath.reset(); mPath.moveTo(-mWaveLength + mOffset, mCenterY); for (int i = 0; i < mWaveCount; i++) { // + (i * mWaveLength) // + mOffset mPath.quadTo((-mWaveLength * 3 / 4) + (i * mWaveLength) + mOffset, mCenterY + 60, (-mWaveLength / 2) + (i * mWaveLength) + mOffset, mCenterY); mPath.quadTo((-mWaveLength / 4) + (i * mWaveLength) + mOffset, mCenterY - 60, i * mWaveLength + mOffset, mCenterY); } mPath.lineTo(mScreenWidth, mScreenHeight); mPath.lineTo(0, mScreenHeight); mPath.close(); canvas.drawPath(mPath, mPaint); } @Override public void onClick(View view) { ValueAnimator animator = ValueAnimator.ofInt(0, mWaveLength); animator.setDuration; animator.setRepeatCount(ValueAnimator.INFINITE); animator.setInterpolator(new LinearInterpolator; animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mOffset =  animation.getAnimatedValue(); postInvalidate; animator.start(); }}

波浪动画实际上并不复杂,但三角函数确实对一部分开拓者相比较不方便,开拓者能够通过上边包车型大巴那么些网址来模拟三角函数图像的绘图:

https://www.desmos.com/calculator

图片 2117.png

路径动画

贝塞尔曲线的另一个异平日用的功效,正是作为动画的活动轨迹,让动画片目的可以沿曲线平滑的兑现活动动画,也等于让实体沿着贝塞尔曲线运动,而不是形而上学的直线,本例实现效益如下所示:

图片 227.gif

package com.xys.animationart.views;import android.animation.ValueAnimator;import android.content.Context;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.Path;import android.graphics.PointF;import android.util.AttributeSet;import android.view.View;import android.view.animation.AccelerateDecelerateInterpolator;import com.xys.animationart.evaluator.BezierEvaluator;/** * 贝塞尔路径动画 * <p/> * Created by xuyisheng on 16/7/12. */public class PathBezier extends View implements View.OnClickListener { private Paint mPathPaint; private Paint mCirclePaint; private int mStartPointX; private int mStartPointY; private int mEndPointX; private int mEndPointY; private int mMovePointX; private int mMovePointY; private int mControlPointX; private int mControlPointY; private Path mPath; public PathBezier(Context context) { super; } public PathBezier(Context context, AttributeSet attrs) { super(context, attrs); mPathPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPathPaint.setStyle(Paint.Style.STROKE); mPathPaint.setStrokeWidth; mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG); mStartPointX = 100; mStartPointY = 100; mEndPointX = 600; mEndPointY = 600; mMovePointX = mStartPointX; mMovePointY = mStartPointY; mControlPointX = 500; mControlPointY = 0; mPath = new Path(); setOnClickListener; } public PathBezier(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onDraw(Canvas canvas) { super.onDraw; mPath.reset(); canvas.drawCircle(mStartPointX, mStartPointY, 30, mCirclePaint); canvas.drawCircle(mEndPointX, mEndPointY, 30, mCirclePaint); mPath.moveTo(mStartPointX, mStartPointY); mPath.quadTo(mControlPointX, mControlPointY, mEndPointX, mEndPointY); canvas.drawPath(mPath, mPathPaint); canvas.drawCircle(mMovePointX, mMovePointY, 30, mCirclePaint); } @Override public void onClick(View view) { BezierEvaluator bezierEvaluator = new BezierEvaluator(new PointF(mControlPointX, mControlPointY)); ValueAnimator anim = ValueAnimator.ofObject(bezierEvaluator, new PointF(mStartPointX, mStartPointY), new PointF(mEndPointX, mEndPointY)); anim.setDuration; anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { PointF point =  valueAnimator.getAnimatedValue(); mMovePointX =  point.x; mMovePointY =  point.y; invalidate; anim.setInterpolator(new AccelerateDecelerateInterpolator; anim.start(); }}

其间,用于转移运动点坐标的最首要evaluator如下所示:

package com.xys.animationart.evaluator;import android.animation.TypeEvaluator;import android.graphics.PointF;import com.xys.animationart.util.BezierUtil;public class BezierEvaluator implements TypeEvaluator<PointF> { private PointF mControlPoint; public BezierEvaluator(PointF controlPoint) { this.mControlPoint = controlPoint; } @Override public PointF evaluate(float t, PointF startValue, PointF endValue) { return BezierUtil.CalculateBezierPointForQuadratic(t, startValue, mControlPoint, endValue); }}

那边的Type伊娃luator总括用到了计算贝塞尔曲线上点的测算算法,那一个会在背后继续上课。

求贝塞尔曲线上率性一点的坐标

求贝塞尔曲线上自由一点的坐标,那一过程,正是选取了De Casteljau算法。

http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/de-casteljau.html

图片 237.png

使用那一算法,有开拓者开拓了3个演示多阶贝塞尔曲线的成效的App,其规律正是通过绘制贝塞尔曲线上的点来进行绘图的,地址如下所示:

https://github.com/venshine/BezierMaker

上边那篇文章就详细的任课了该算法的采纳,作者的代码也从这里提取而来:

http://devmag.org.za/2011/04/05/bzier-curves-a-tutorial/

计算

有了公式,只供给代码实现就OK了,大家先写七个公式:

package com.xys.animationart.util;import android.graphics.PointF;/** * 计算贝塞尔曲线上的点坐标 * <p/> * Created by xuyisheng on 16/7/13. */public class BezierUtil { /** * B = ^2 * P0 + 2t *  * P1 + t^2 * P2, t ∈ [0,1] * * @param t 曲线长度比例 * @param p0 起始点 * @param p1 控制点 * @param p2 终止点 * @return t对应的点 */ public static PointF CalculateBezierPointForQuadratic(float t, PointF p0, PointF p1, PointF p2) { PointF point = new PointF(); float temp = 1 - t; point.x = temp * temp * p0.x + 2 * t * temp * p1.x + t * t * p2.x; point.y = temp * temp * p0.y + 2 * t * temp * p1.y + t * t * p2.y; return point; } /** * B = P0 * ^3 + 3 * P1 * t * ^2 + 3 * P2 * t^2 *  + P3 * t^3, t ∈ [0,1] * * @param t 曲线长度比例 * @param p0 起始点 * @param p1 控制点1 * @param p2 控制点2 * @param p3 终止点 * @return t对应的点 */ public static PointF CalculateBezierPointForCubic(float t, PointF p0, PointF p1, PointF p2, PointF p3) { PointF point = new PointF(); float temp = 1 - t; point.x = p0.x * temp * temp * temp + 3 * p1.x * t * temp * temp + 3 * p2.x * t * t * temp + p3.x * t * t * t; point.y = p0.y * temp * temp * temp + 3 * p1.y * t * temp * temp + 3 * p2.y * t * t * temp + p3.y * t * t * t; return point; }}

咱俩来将路径绘制到View中,看是否精确:

package com.xys.animationart.views;import android.animation.AnimatorSet;import android.animation.ValueAnimator;import android.content.Context;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.PointF;import android.util.AttributeSet;import android.view.View;import com.xys.animationart.util.BezierUtil;/** * 通过计算模拟二阶、三阶贝塞尔曲线 * <p/> * Created by xuyisheng on 16/7/13. */public class CalculateBezierPointView extends View implements View.OnClickListener { private Paint mPaint; private ValueAnimator mAnimatorQuadratic; private ValueAnimator mAnimatorCubic; private PointF mPointQuadratic; private PointF mPointCubic; public CalculateBezierPointView(Context context) { super; } public CalculateBezierPointView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public CalculateBezierPointView(Context context, AttributeSet attrs) { super(context, attrs); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mAnimatorQuadratic = ValueAnimator.ofFloat; mAnimatorQuadratic.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { PointF point = BezierUtil.CalculateBezierPointForQuadratic(valueAnimator.getAnimatedFraction(), new PointF, new PointF, new PointF); mPointQuadratic.x = point.x; mPointQuadratic.y = point.y; invalidate; mAnimatorCubic = ValueAnimator.ofFloat; mAnimatorCubic.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { PointF point = BezierUtil.CalculateBezierPointForCubic(valueAnimator.getAnimatedFraction(), new PointF, new PointF(100, 1100), new PointF(500, 1000), new PointF); mPointCubic.x = point.x; mPointCubic.y = point.y; invalidate; mPointQuadratic = new PointF(); mPointQuadratic.x = 100; mPointQuadratic.y = 100; mPointCubic = new PointF(); mPointCubic.x = 100; mPointCubic.y = 600; setOnClickListener; } @Override protected void onDraw(final Canvas canvas) { super.onDraw; canvas.drawCircle(mPointQuadratic.x, mPointQuadratic.y, 10, mPaint); canvas.drawCircle(mPointCubic.x, mPointCubic.y, 10, mPaint); } @Override public void onClick(View view) { AnimatorSet set = new AnimatorSet(); set.playTogether(mAnimatorQuadratic, mAnimatorCubic); set.setDuration; set.start(); }}

本次我们并不曾经过API提供的贝塞尔曲线绘制方法来绘制二阶、3阶贝塞尔曲线,而是经过时间t和起首点来总结一条贝塞尔曲线上的全数一点点,能够发掘,通过算法计算出来的点,与经过API所绘制出来的点,是截然合乎的。

贝塞尔曲线有多个百般常用的卡通片效果——MetaBall算法。相信广大开垦者都见过类似的动画片,举个例子QQ的小红点消除,UC浏览器的下拉刷新loading等等。要做好这么些动画,实际上最要害的就是由此贝塞尔曲线来拟合三个图形。

作用如图所示:

图片 248.png

矩形拟合

我们来看一下拟合的原理,实际上正是通过贝塞尔曲线来连接七个圆上的四个点,当大家调治下画笔的填充方式,并绘制一些帮助线,大家来看现实是如何进行拟合的,如图所示:

图片 259.png

能够发掘,调整点为两圆圆心连线的中部,连接线为图中的这样八个矩形,当圆比相当的小时,这种通过矩形来拟合的办法大致是平素不难点的,但大家把圆放大,再来看下这种拟合,如图所示:

图片 2610.png

当圆的半径扩展之后,就可以丰裕理解的意识拟合的连接点与圆有早晚相交的区域,那样的拟合效果就不佳了,大家将画笔方式调治回来,如图所示:

图片 2711.png

故而,简单的矩形拟合,在圆半径小的时候,是能够的,但当圆半径变大之后,就须要更加的严峻的拟合了。

此处我们先来说课下,怎么着计算矩形拟合的多少个关键点。

在此之前方那张线图能够看到,标红的三个角是突出的,而以此角可以由此三个圆心的坐标来算出,有了那样二个角度,通过CR-Vx cos和 君越 x
sin来计量矩形的一个终端的坐标,类似的,其余坐标可求,关键代码如下所示:

private void metaBallVersion1(Canvas canvas) { float x = mCircleTwoX; float y = mCircleTwoY; float startX = mCircleOneX; float startY = mCircleOneY; float dx = x - startX; float dy = y - startY; double a = Math.atan; float offsetX =  (mCircleOneRadius * Math.cos; float offsetY =  (mCircleOneRadius * Math.sin; float x1 = startX + offsetX; float y1 = startY - offsetY; float x2 = x + offsetX; float y2 = y - offsetY; float x3 = x - offsetX; float y3 = y + offsetY; float x4 = startX - offsetX; float y4 = startY + offsetY; float controlX = (startX + x) / 2; float controlY = (startY + y) / 2; mPath.reset(); mPath.moveTo; mPath.quadTo(controlX, controlY, x2, y2); mPath.lineTo; mPath.quadTo(controlX, controlY, x4, y4); mPath.lineTo; // 辅助线 canvas.drawLine(mCircleOneX, mCircleOneY, mCircleTwoX, mCircleTwoY, mPaint); canvas.drawLine(0, mCircleOneY, mCircleOneX + mRadiusNormal + 400, mCircleOneY, mPaint); canvas.drawLine(mCircleOneX, 0, mCircleOneX, mCircleOneY + mRadiusNormal + 50, mPaint); canvas.drawLine(x1, y1, x2, y2, mPaint); canvas.drawLine(x3, y3, x4, y4, mPaint); canvas.drawCircle(controlX, controlY, 5, mPaint); canvas.drawLine(mCircleTwoX, mCircleTwoY, mCircleTwoX, 0, mPaint); canvas.drawLine(x1, y1, x1, mCircleOneY, mPaint); canvas.drawPath(mPath, mPaint); }

切线拟合

如前方所说,矩形拟合在半径非常的小的景况下,是足以兑现完美拟合的,而当半径变大后,就能够现出贝塞尔曲线与圆相交的地方,导致拟合退步。

那就是说怎么样来落到实处完美的拟合呢?实际上,也便是说贝塞尔曲线与圆的连接点到贝塞尔曲线的调节点的连线,一定是圆的切线,那样的话,无论圆的半径怎样变迁,贝塞尔曲线一定是与圆拟合的,具体效果如图所示:

图片 2812.png

那儿大家把画笔格局调度回来看下填充效果,如图所示:

图片 2913.png

如此那般拟合是特别周全的。那么要哪些来测算这么些拟合的关键点呢?在前边的线图中,作者标识出了七个角,那八个角分别能够求出,相减,就足以拿走切点与圆心的夹角了,那样,通过猎豹CS6x cos和陆风X捌 x sin就足以求出切点的坐标了。

当中,小的角能够透过四个圆心的坐标来求出,而大的角,能够由此直角三角形(圆心、切点、调节点)来求出,即决定点到圆心的相距/半径。

重中之重代码如下所示:

private void metaBallVersion2(Canvas canvas) { float x = mCircleTwoX; float y = mCircleTwoY; float startX = mCircleOneX; float startY = mCircleOneY; float controlX = (startX + x) / 2; float controlY = (startY + y) / 2; float distance =  Math.sqrt((controlX - startX) * (controlX - startX) + (controlY - startY) * (controlY - startY)); double a = Math.acos(mRadiusNormal / distance); double b = Math.acos((controlX - startX) / distance); float offsetX1 =  (mRadiusNormal * Math.cos; float offsetY1 =  (mRadiusNormal * Math.sin; float tanX1 = startX + offsetX1; float tanY1 = startY - offsetY1; double c = Math.acos((controlY - startY) / distance); float offsetX2 =  (mRadiusNormal * Math.sin; float offsetY2 =  (mRadiusNormal * Math.cos; float tanX2 = startX - offsetX2; float tanY2 = startY + offsetY2; double d = Math.acos((y - controlY) / distance); float offsetX3 =  (mRadiusNormal * Math.sin; float offsetY3 =  (mRadiusNormal * Math.cos; float tanX3 = x + offsetX3; float tanY3 = y - offsetY3; double e = Math.acos((x - controlX) / distance); float offsetX4 =  (mRadiusNormal * Math.cos; float offsetY4 =  (mRadiusNormal * Math.sin; float tanX4 = x - offsetX4; float tanY4 = y + offsetY4; mPath.reset(); mPath.moveTo(tanX1, tanY1); mPath.quadTo(controlX, controlY, tanX3, tanY3); mPath.lineTo(tanX4, tanY4); mPath.quadTo(controlX, controlY, tanX2, tanY2); canvas.drawPath(mPath, mPaint); // 辅助线 canvas.drawCircle(tanX1, tanY1, 5, mPaint); canvas.drawCircle(tanX2, tanY2, 5, mPaint); canvas.drawCircle(tanX3, tanY3, 5, mPaint); canvas.drawCircle(tanX4, tanY4, 5, mPaint); canvas.drawLine(mCircleOneX, mCircleOneY, mCircleTwoX, mCircleTwoY, mPaint); canvas.drawLine(0, mCircleOneY, mCircleOneX + mRadiusNormal + 400, mCircleOneY, mPaint); canvas.drawLine(mCircleOneX, 0, mCircleOneX, mCircleOneY + mRadiusNormal + 50, mPaint); canvas.drawLine(mCircleTwoX, mCircleTwoY, mCircleTwoX, 0, mPaint); canvas.drawCircle(controlX, controlY, 5, mPaint); canvas.drawLine(startX, startY, tanX1, tanY1, mPaint); canvas.drawLine(tanX1, tanY1, controlX, controlY, mPaint); }

圆的拟合

贝塞尔曲线做动画,许多时候都需求利用到圆的特效,而经过二阶、三阶贝塞尔曲线来拟合圆,也不是两个非常简单的政工,所以,我一贯把结论拿出去了,具体的算法地址如下所示:

http://spencermortensen.com/articles/bezier-circle/

http://stackoverflow.com/questions/1734745/how-to-create-circle-with-b%C3%A9zier-curves

图片 3014.png图片 3115.png

有了贝塞尔曲线的调控点,再对其促成动画,就特别轻松了,与在此以前的卡通未有太大的不一致。

本次的疏解代码已经全体上传到Github :

https://github.com/xuyisheng/BezierArt

接待大家提issue。

相关文章