相信很多同学都知道“贝塞尔曲线”这个词,我们在很多地方都能经常看到。利用“贝塞尔曲线”可以做出很多好看的UI效果,本篇博客就让我们一起学习“贝塞尔曲线”。

贝塞尔曲线的原理

贝塞尔曲线是用一系列点来控制曲线状态的,这些点简单分为两类:

类型 作用
数据点 确定曲线的起始和结束位置
控制点 确定曲线的弯曲程度

一阶贝塞尔曲线 
一阶曲线是没有控制点的,仅有两个数据点(A 和 B),最终效果一个线段。 一阶曲线其实就是lineTo方法

二阶贝塞尔曲线 
在平面内任选 3 个不共线的点,依次用线段连接。 

在第一条线段上任选一个点 D。计算该点到线段起点的距离 AD,与该线段总长 AB 的比例。 

连接这两点DE。 E点满足AD:AB = BE:BC。

从新的线段 DE 上再次找出相同比例的点 F,使得 DF:DE = AD:AB = BE:BC。

到这里,我们就确定了贝塞尔曲线上的一个点 F。接下来,让选取的点D从起点 A 移动到终点 B,F的轨迹就是二阶贝塞尔曲线。

动态过程如下,二阶曲线其实就是quadTo方法

三阶贝塞尔曲线 
控制点个数为4 时,就是三阶的曲线。

同理满足AE:AB = BF:BC = CG:CD = EH:EF = FI:FG = HJ:HI,其中点J轨迹即为三阶贝塞尔曲线。 

这样我们得到的是一条三次贝塞尔曲线。 

动态图如下,三阶曲线对应的方法是cubicTo

学习贝塞尔曲线函数

一阶曲线是一条线段,非常简单,不再进行介绍,都是path的基本用法。

二阶曲线: 
首先,两个数据点是控制贝塞尔曲线开始和结束的位置,而控制点则是控制贝塞尔的弯曲状态。

从上面的动态图可以看出,贝塞尔曲线在动态变化过程中有类似于橡皮筋一样的弹性效果,因此在制作一些弹性效果的时候很常用。

代码如下:

public class Bezier extends View {private Paint mPaint;private int centerX, centerY;private PointF start, end, control;public Bessel1(Context context) {super(context);mPaint = new Paint();mPaint.setColor(Color.BLACK);mPaint.setStrokeWidth(8);mPaint.setStyle(Paint.Style.STROKE);mPaint.setTextSize(60);start = new PointF(0,0);end = new PointF(0,0);control = new PointF(0,0);}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);centerX = w/2;centerY = h/2;// 初始化数据点和控制点的位置start.x = centerX-200;start.y = centerY;end.x = centerX+200;end.y = centerY;control.x = centerX;control.y = centerY-100;}@Overridepublic boolean onTouchEvent(MotionEvent event) {// 根据触摸位置更新控制点,并提示重绘control.x = event.getX();control.y = event.getY();invalidate();return true;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 绘制数据点和控制点mPaint.setColor(Color.GRAY);mPaint.setStrokeWidth(20);canvas.drawPoint(start.x,start.y,mPaint);canvas.drawPoint(end.x,end.y,mPaint);canvas.drawPoint(control.x,control.y,mPaint);// 绘制辅助线mPaint.setStrokeWidth(4);canvas.drawLine(start.x,start.y,control.x,control.y,mPaint);canvas.drawLine(end.x,end.y,control.x,control.y,mPaint);// 绘制贝塞尔曲线mPaint.setColor(Color.RED);mPaint.setStrokeWidth(8);Path path = new Path();path.moveTo(start.x,start.y);path.quadTo(control.x,control.y,end.x,end.y);canvas.drawPath(path, mPaint);}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72

三阶曲线: 
三阶曲线由两个数据点和两个控制点来控制曲线状态。 

public class Bezier2 extends View {private Paint mPaint;private int centerX, centerY;private PointF start, end, control1, control2;private boolean mode = true;public Bezier2(Context context) {this(context, null);}public Bezier2(Context context, AttributeSet attrs) {super(context, attrs);mPaint = new Paint();mPaint.setColor(Color.BLACK);mPaint.setStrokeWidth(8);mPaint.setStyle(Paint.Style.STROKE);mPaint.setTextSize(60);start = new PointF(0, 0);end = new PointF(0, 0);control1 = new PointF(0, 0);control2 = new PointF(0, 0);}public void setMode(boolean mode) {this.mode = mode;}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);centerX = w / 2;centerY = h / 2;// 初始化数据点和控制点的位置start.x = centerX - 200;start.y = centerY;end.x = centerX + 200;end.y = centerY;control1.x = centerX;control1.y = centerY - 100;control2.x = centerX;control2.y = centerY - 100;}@Overridepublic boolean onTouchEvent(MotionEvent event) {// 根据触摸位置更新控制点,并提示重绘if (mode) {control1.x = event.getX();control1.y = event.getY();} else {control2.x = event.getX();control2.y = event.getY();}invalidate();return true;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//drawCoordinateSystem(canvas);// 绘制数据点和控制点mPaint.setColor(Color.GRAY);mPaint.setStrokeWidth(20);canvas.drawPoint(start.x, start.y, mPaint);canvas.drawPoint(end.x, end.y, mPaint);canvas.drawPoint(control1.x, control1.y, mPaint);canvas.drawPoint(control2.x, control2.y, mPaint);// 绘制辅助线mPaint.setStrokeWidth(4);canvas.drawLine(start.x, start.y, control1.x, control1.y, mPaint);canvas.drawLine(control1.x, control1.y,control2.x, control2.y, mPaint);canvas.drawLine(control2.x, control2.y,end.x, end.y, mPaint);// 绘制贝塞尔曲线mPaint.setColor(Color.RED);mPaint.setStrokeWidth(8);Path path = new Path();path.moveTo(start.x, start.y);path.cubicTo(control1.x, control1.y, control2.x,control2.y, end.x, end.y);canvas.drawPath(path, mPaint);}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95

三阶曲线相比于二阶曲线可以制作更加复杂的形状,但是对于高阶的曲线,用低阶的曲线组合也可达到相同的效果,就是传说中的降阶。因此我们对贝塞尔曲线的封装方法一般最高只到三阶曲线。

降阶与升阶

类型 释义 变化
降阶 在保持曲线形状与方向不变的情况下,减少控制点数量,即降低曲线阶数 方法变得简单,数据点变多,控制点可能减少,灵活性变弱
升阶 在保持曲线形状与方向不变的情况下,增加控制点数量,即升高曲线阶数 方法更加复杂,数据点不变,控制点增加,灵活性变强

贝塞尔曲线实例

一般使用贝塞尔曲线的情况如下:

序号 内容 用例
1 事先不知道曲线状态,需要实时计算时 方天气预报气温变化的平滑折线图
2 显示状态会根据用户操作改变时 QQ小红点,仿真翻书效果
3 一些比较复杂的运动状态(配合PathMeasure使用) 复杂运动状态的动画效果

至于只需要一个静态的曲线图形的情况,用图片岂不是更好,大量的计算会很不划算。

如果是显示SVG矢量图的话,已经有相关的解析工具了(内部依旧运用的有贝塞尔曲线),不需要手动计算。

贝塞尔曲线的主要优点是可以实时控制曲线状态,并可以通过改变控制点的状态实时让曲线进行平滑的状态变化。

QQ红点的实现效果

qq的红点去除效果,其实就是用了两条贝塞尔曲线。 

基本理论:只要在拖动的时候 去改变辅助点的Y,和固定圆的半径, 就可以出来效果。

创建画笔

mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);mPaint.setColor(Color.RED);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

绘制动圆和固定圆

 /*** 固定圆  并且初始化*/private PointF mFixedCircle = new PointF(150f, 150f);/*** 固定圆的半径*/float mFixedRadius = 14f;/*** 动圆  并且初始化*/private PointF mDragCircle = new PointF(80f, 80f);/*** 动圆半径*/float mDragRadius = 20f;/*** 动圆两个焦点的坐标*/private PointF[] mDragPoints;/*** 固定圆的两个焦点坐标*/private PointF[] mFixedPoints;/*** 控制焦点*/private PointF mControlPoint;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

获取两个圆之间的距离:

/*** 获取临时的固定圆的半径** @return*/private float getTempFiexdCircle() {//获取到两个圆心之间的距离float instance = GeometryUtil.getDistanceBetween2Points(mDragCircle, mFixedCircle);//这个是在连个圆之间的实际距离和我们定义的距离之间取得最小值instance = Math.min(instance, farestDistance);//0.0f--->1.0f>>>>>1.0f---》0.0ffloat percent = instance / farestDistance;return evaluate(percent, mFixedRadius, mFixedRadius * 0.2);}/*** 估值器** @param fraction* @param startValue* @param endValue* @return*/public Float evaluate(float fraction, Number startValue, Number endValue) {float startFloat = startValue.floatValue();return startFloat + fraction * (endValue.floatValue() - startFloat);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

onDraw()方法绘制圆

//根据两个圆的圆心的距离获取固定圆的半径float distance = getTempFiexdCircle();//计算连接部分//1、获取直线与圆的焦点float yOffset = mFixedCircle.y - mDragCircle.y;float xOffset = mFixedCircle.x - mDragCircle.x;/*** 获取斜率*/Double lineK = null;if (xOffset != 0) {lineK = (double) yOffset / xOffset;}//通过几何工具获取焦点坐标this.mFixedPoints = GeometryUtil.getIntersectionPoints(mFixedCircle, distance, lineK);this.mDragPoints = GeometryUtil.getIntersectionPoints(mDragCircle, mDragRadius, lineK);//2、获取控制点坐标this.mControlPoint = GeometryUtil.getMiddlePoint(mDragCircle, mFixedCircle);//绘制动圆canvas.drawCircle(mDragCircle.x, mDragCircle.y, mDragRadius, mPaint);//画一个固定圆//canvas.drawCircle(150f,150f,14f,mPaint);canvas.drawCircle(mFixedCircle.x, mFixedCircle.y, distance, mPaint);//canvas.drawCircle(150f,150f,14f,mPaint);canvas.drawCircle(mFixedCircle.x, mFixedCircle.y, distance, mPaint);//画连接部分   这个是用的那个贝塞尔曲线绘制的连接部分Path path = new Path();//跳到某个点1path.moveTo(mFixedPoints[0].x, mFixedPoints[0].y);//画曲线 1--->2path.quadTo(mControlPoint.x, mControlPoint.y, mDragPoints[0].x, mDragPoints[0].y);//画直线2---->3path.lineTo(mDragPoints[1].x, mDragPoints[1].y);//画曲线3---->4path.quadTo(mControlPoint.x, mControlPoint.y, mFixedPoints[1].x, mFixedPoints[1].y);path.close();canvas.drawPath(path, mPaint);//恢复画布canvas.restore();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

处理onTouch()方法,让红点随手势动起来

 @Overridepublic boolean onTouchEvent(MotionEvent event) {float x = 0;float y = 0;switch (event.getAction()) {case MotionEvent.ACTION_DOWN://获取到按下的时候的坐标(因为我们已经把画布往上移动了状态栏的高度了,或者是我们在这里做判断)x = event.getRawX();y = event.getRawY();//更新动圆的坐标updataDragCircle(x, y);break;case MotionEvent.ACTION_MOVE://移动的时候获取坐标x = event.getRawX();y = event.getRawY();updataDragCircle(x, y);//处理断开float distance = GeometryUtil.getDistanceBetween2Points(mDragCircle, mFixedCircle);if (distance > farestDistance) {  //如果获取到的距离大于我们定义的最大的距离isOutToRange = true;  //断开设置为trueinvalidate();  //重绘}break;case MotionEvent.ACTION_UP:if (isOutToRange) {  //如果是断开isOutToRange = false;  //设置为false//处理断开float d = GeometryUtil.getDistanceBetween2Points(mDragCircle, mFixedCircle);if (d > farestDistance) {// * a、拖拽超出范围,断开-->松手-->消失//松手还没有放回去isDisappear = true;//重绘一下invalidate();} else {//    * b、拖拽超出范围,断开---->放回去了--->恢复updataDragCircle(mFixedCircle.x, mFixedCircle.y);isDisappear = false;}} else {final PointF tempDragCircle = new PointF(mDragCircle.x, mDragCircle.y);//    * c、拖拽没有超出范围,断开--->恢复final ValueAnimator mAnim = ValueAnimator.ofFloat(1.0f);mAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {float percent = mAnim.getAnimatedFraction();PointF p = GeometryUtil.getPointByPercent(tempDragCircle, mFixedCircle, percent);updataDragCircle(p.x, p.y);}});//差之器,这个是设置弹性的mAnim.setInterpolator(new OvershootInterpolator(4));mAnim.setDuration(500);mAnim.start();}break;}return true;}/*** 更新拖拽圆的圆心坐标** @param rawX* @param rawY*/private void updataDragCircle(float rawX, float rawY) {//更新的坐标mDragCircle.set(rawX, rawY);invalidate();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85

修改onDraw()判断 
isOutToRange和isDisappear分别为true和false的情况

@Overrideprotected void onDraw(Canvas canvas) {//保持当前画布的状态canvas.save();//移动画布canvas.translate(0, -mStatusBarHeight);//根据两个圆的圆心的距离获取固定圆的半径float distance = getTempFiexdCircle();//计算连接部分//1、获取直线与圆的焦点float yOffset = mFixedCircle.y - mDragCircle.y;float xOffset = mFixedCircle.x - mDragCircle.x;/*** 获取斜率*/Double lineK = null;if (xOffset != 0) {lineK = (double) yOffset / xOffset;}//通过几何工具获取焦点坐标this.mFixedPoints = GeometryUtil.getIntersectionPoints(mFixedCircle, distance, lineK);this.mDragPoints = GeometryUtil.getIntersectionPoints(mDragCircle, mDragRadius, lineK);//2、获取控制点坐标this.mControlPoint = GeometryUtil.getMiddlePoint(mDragCircle, mFixedCircle);if (!isDisappear) {//画拖拽圆//canvas.drawCircle(80f,80f,20f,mPaint);canvas.drawCircle(mDragCircle.x, mDragCircle.y, mDragRadius, mPaint);if (!isOutToRange) {//画一个固定圆//canvas.drawCircle(150f,150f,14f,mPaint);canvas.drawCircle(mFixedCircle.x, mFixedCircle.y, distance, mPaint);//画连接部分   这个是用的那个贝塞尔曲线绘制的连接部分Path path = new Path();//跳到某个点1path.moveTo(mFixedPoints[0].x, mFixedPoints[0].y);//画曲线 1--->2path.quadTo(mControlPoint.x, mControlPoint.y, mDragPoints[0].x, mDragPoints[0].y);//画直线2---->3path.lineTo(mDragPoints[1].x, mDragPoints[1].y);//画曲线3---->4path.quadTo(mControlPoint.x, mControlPoint.y, mFixedPoints[1].x, mFixedPoints[1].y);path.close();canvas.drawPath(path, mPaint);}}//恢复canvas.restore();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

漂浮的心

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

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

public class BezierEvaluator implements TypeEvaluator<PointF> {private PointF mControlP1;private PointF mControlP2;public BezierEvaluator(PointF controlP1, PointF controlP2) {this.mControlP1 = controlP1;this.mControlP2 = controlP2;}@Overridepublic 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;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

之后自定义一个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 要注意 不是truelp.addRule(ALIGN_PARENT_BOTTOM, TRUE);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

入场动画

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;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

贝塞尔曲线动画

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() {@Overridepublic 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;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

结合动画添加爱心

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() {@Overridepublic void onAnimationEnd(Animator animation) {super.onAnimationEnd(animation);removeView((imageView));//删除爱心}});finalSet.start();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

弹性的圆

还有一个实例,就是特别出名的弹性的圆

将这个圆的动画效果拆解开看的画,可以分为5个状态。 

这个动画效果的实现就是不同状态之间的转化加上水平位移的实现。

我们需要先了解一下如何用贝塞尔曲线画一个圆,因为我的做法是通过贝塞尔曲线来实现的。

就是所需要的数值c约等于0.551915024494f,具体可以参考这篇文章,http://spencermortensen.com/articles/bezier-circle/,那么这个c的值的作用,就是把图中的1理解为圆的半径,那么对应的另外个值就应该是半径乘以0.551915024494f。

坐标轴也就是Android中的坐标轴了,如果我们打算用贝塞尔曲线来画这么一个圆的话,我们需要知道这个圆的半径,以及图中的M的值,知道这两个值的话就能够知道图中12个点的坐标,知道坐标就能够用Path的cubicTo方法来使用贝塞尔曲线画出圆了。

public class BezierDemo3 extends View {private static final float C = 0.551915024494f;     // 一个常量,用来计算绘制圆形贝塞尔曲线控制点的位置private Paint mPaint;private int mCenterX, mCenterY;private PointF mCenter = new PointF(0,0);private float mCircleRadius = 200;                  // 圆的半径private float mDifference = mCircleRadius*C;        // 圆形的控制点与数据点的差值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;            // 每一份的时长public Bezier3(Context context) {this(context, null);}public Bezier3(Context context, AttributeSet attrs) {super(context, attrs);mPaint = new Paint();mPaint.setColor(Color.BLACK);mPaint.setStrokeWidth(8);mPaint.setStyle(Paint.Style.STROKE);mPaint.setTextSize(60);// 初始化数据点mData[0] = 0;mData[1] = mCircleRadius;mData[2] = mCircleRadius;mData[3] = 0;mData[4] = 0;mData[5] = -mCircleRadius;mData[6] = -mCircleRadius;mData[7] = 0;// 初始化控制点mCtrl[0]  = mData[0]+mDifference;mCtrl[1]  = mData[1];mCtrl[2]  = mData[2];mCtrl[3]  = mData[3]+mDifference;mCtrl[4]  = mData[2];mCtrl[5]  = mData[3]-mDifference;mCtrl[6]  = mData[4]+mDifference;mCtrl[7]  = mData[5];mCtrl[8]  = mData[4]-mDifference;mCtrl[9]  = mData[5];mCtrl[10] = mData[6];mCtrl[11] = mData[7]-mDifference;mCtrl[12] = mData[6];mCtrl[13] = mData[7]+mDifference;mCtrl[14] = mData[0]-mDifference;mCtrl[15] = mData[1];}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);mCenterX = w / 2;mCenterY = h / 2;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);drawCoordinateSystem(canvas);       // 绘制坐标系canvas.translate(mCenterX, mCenterY); // 将坐标系移动到画布中央canvas.scale(1,-1);                 // 翻转Y轴drawAuxiliaryLine(canvas);// 绘制贝塞尔曲线mPaint.setColor(Color.RED);mPaint.setStrokeWidth(8);Path path = new Path();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);}}// 绘制辅助线private void drawAuxiliaryLine(Canvas canvas) {// 绘制数据点和控制点mPaint.setColor(Color.GRAY);mPaint.setStrokeWidth(20);for (int i=0; i<8; i+=2){canvas.drawPoint(mData[i],mData[i+1], mPaint);}for (int i=0; i<16; i+=2){canvas.drawPoint(mCtrl[i], mCtrl[i+1], mPaint);}// 绘制辅助线mPaint.setStrokeWidth(4);for (int i=2, j=2; i<8; i+=2, j+=4){canvas.drawLine(mData[i],mData[i+1],mCtrl[j],mCtrl[j+1],mPaint);canvas.drawLine(mData[i],mData[i+1],mCtrl[j+2],mCtrl[j+3],mPaint);}canvas.drawLine(mData[0],mData[1],mCtrl[0],mCtrl[1],mPaint);canvas.drawLine(mData[0],mData[1],mCtrl[14],mCtrl[15],mPaint);}// 绘制坐标系private void drawCoordinateSystem(Canvas canvas) {canvas.save();                      // 绘制做坐标系canvas.translate(mCenterX, mCenterY); // 将坐标系移动到画布中央canvas.scale(1,-1);                 // 翻转Y轴Paint fuzhuPaint = new Paint();fuzhuPaint.setColor(Color.RED);fuzhuPaint.setStrokeWidth(5);fuzhuPaint.setStyle(Paint.Style.STROKE);canvas.drawLine(0, -2000, 0, 2000, fuzhuPaint);canvas.drawLine(-2000, 0, 2000, 0, fuzhuPaint);canvas.restore();}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167

这样我们就知道如何使用贝塞尔曲线来绘制一个圆了。也就是状态1和状态5我们都会绘制了,接下来看看状态2如何绘制。 

状态2其实就是把右边的点向右移动点距离 

状态3的实现就是在状态2的基础上修改了个值,一个是M的值加大,让圆看起来跟肥一点,还有就是圈住的那些点向右移动,做到居中。

实现如下:

public class Ball {/*** 圆心横坐标*/public float x;/*** 圆心纵坐标*/public float y;/*** 半径*/public float radius;/*** 构造方法* @param x* @param y* @param radius*/public Ball(float x, float y, float radius) {this.x = x;this.y = y;this.radius = radius;this.topX = x;this.topY = y - radius;this.bottomX = x;this.bottomY = y + radius;this.leftX = x - radius;this.leftY = y;this.rightX = x + radius;this.rightY = y;}public void refresh(float x, float y, float topX, float topY, float bottomX, float bottomY,float leftX, float leftY, float rightX, float rightY){this.x = x;this.y = y;this.topX = topX;this.topY = topY;this.bottomX = bottomX;this.bottomY = bottomY;this.leftX = leftX;this.leftY = leftY;this.rightX = rightX;this.rightY = rightY;}/*** 球左边点的坐标*/public float leftX;public float leftY;/*** 球右边点的坐标*/public float rightX;public float rightY;/*** 球顶点的坐标*/public float topX;public float topY;/*** 球底部点的坐标*/public float bottomX;public float bottomY;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
public class MagicBall extends Ball {/*** 向上运动*/private static final int DIRECTION_UP = 1;/*** 向下运动*/private static final int DIRECTION_DOWN = 2;/*** 向左运动*/private static final int DIRECTION_LEFT = 3;/*** 向右运动*/private static final int DIRECTION_RIGHT = 4;/*** 动画消费时间*/private long mDuration = 1200;/*** 偏移值*/private float offsetTop, offsetBottom, offsetLeft, offsetRight;/*** 运动方向*/private int mDirection;/*** 动画完成百分比(0~1)*/private float mAnimPercent;/*** 弹性距离*/private float mElasticDistance;/*** 弹性比例*/private float mElasticPercent = 0.8f;/*** 位移距离*/private float mMoveDistance;/*** 圆形偏移比例*/private float c = 0.551915024494f;private float c2 = 0.65f;/*** 动画开始点*/private Ball mStartPoint;/*** 动画结束点*/private Ball mEndPoint;/*** 构造方法** @param x 圆心横坐标* @param y 圆心纵坐标* @param radius 圆半径*/public MagicBall(float x, float y, float radius) {super(x, y, radius);init();}private void init() {mElasticDistance = mElasticPercent * radius;offsetTop = c * radius;offsetBottom = c * radius;offsetLeft = c * radius;offsetRight = c * radius;}public interface ElasticBallInterface{void onChange(Path path);void onFinish();}private ElasticBallInterface mElasticBallInterface;/*** 对外公布方法,设置弹性比例 (0~1)* @param elasticPercent*/public void setElasticPercent(float elasticPercent) {}/*** 对外公布方法,设置动画时间* @param duration*/public void setDuration(long duration) {this.mDuration = duration;}/*** 对外公布方法, 开启动画* @param endPoint*/public void startElasticAnim(PointF endPoint, ElasticBallInterface elasticBallInterface) {this.mEndPoint = new MagicBall(endPoint.x, endPoint.y, radius);this.mStartPoint = new MagicBall(x, y, radius);this.mStatusPoint1 = new MagicBall(x, y, radius);this.mStatusPoint2 = new MagicBall(x, y, radius);this.mStatusPoint3 = new MagicBall(x, y, radius);this.mStatusPoint4 = new MagicBall(x, y, radius);this.mStatusPoint5 = new MagicBall(x, y, radius);this.mElasticBallInterface = elasticBallInterface;calculateDirection();mMoveDistance = getDistance(mStartPoint.x, mStatusPoint1.y, endPoint.x, endPoint.y);animStatus0();ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);valueAnimator.setDuration(mDuration);valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());valueAnimator.start();valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mAnimPercent = (float) animation.getAnimatedValue();if(mAnimPercent>=0 && mAnimPercent <= 0.2){animStatus1();}else if(mAnimPercent > 0.2 && mAnimPercent <= 0.5){animStatus2();}else if(mAnimPercent > 0.5 && mAnimPercent <= 0.8){animStatus3();}else if(mAnimPercent > 0.8 && mAnimPercent <= 0.9){animStatus4();}else if(mAnimPercent > 0.9&&mAnimPercent <= 1){animStatus5();}if (mElasticBallInterface != null) {mElasticBallInterface.onChange(drawMagicCircle(topX, topY, offsetTop, offsetTop,bottomX, bottomY, offsetBottom, offsetBottom,leftX, leftY, offsetLeft, offsetLeft,rightX, rightY, offsetRight, offsetRight));}}});valueAnimator.addListener(new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animation) {}@Overridepublic void onAnimationEnd(Animator animation) {if (mElasticBallInterface != null) {mElasticBallInterface.onFinish();}}@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic void onAnimationRepeat(Animator animation) {}});}private void calculateDirection() {if (mEndPoint.x - mStartPoint.x > 0) {mDirection = DIRECTION_RIGHT;}else if (mEndPoint.x - mStartPoint.x < 0) {mDirection = DIRECTION_LEFT;}else if (mEndPoint.y - mStartPoint.x > 0) {mDirection = DIRECTION_DOWN;}else if (mEndPoint.y - mStartPoint.y < 0){mDirection = DIRECTION_UP;}}/*** 动画状态0 (初始状态:圆形)*/private void animStatus0() {offsetTop = c * radius;offsetBottom = c * radius;offsetLeft = c * radius;offsetRight = c * radius;}private Ball mStatusPoint1;/*** 动画状态1 (0~0.2)*/private void animStatus1() {float percent = mAnimPercent * 5f;if (mDirection == DIRECTION_LEFT) {leftX = mStartPoint.leftX - percent * mElasticDistance;} else if (mDirection == DIRECTION_RIGHT) {rightX = mStartPoint.rightX + percent * mElasticDistance;} else if (mDirection == DIRECTION_UP) {topY = mStartPoint.topY - percent * mElasticDistance;} else if (mDirection == DIRECTION_DOWN) {bottomY = mStartPoint.bottomY + percent * mElasticDistance;}mStatusPoint1.refresh(x, y, topX, topY, bottomX, bottomY,leftX, leftY, rightX, rightY);}private Ball mStatusPoint2;/*** 动画状态2 (0.2~0.5)*/private void animStatus2() {float percent = (float) ((mAnimPercent - 0.2) * (10f / 3));if (mDirection == DIRECTION_LEFT) {leftX = mStatusPoint1.leftX - percent * (mMoveDistance / 2 - mElasticDistance / 2 );x = mStatusPoint1.x - percent * (mMoveDistance / 2);rightX = mStatusPoint1.rightX - percent * (mMoveDistance / 2 - mElasticDistance / 2 );topX = x;bottomX = x;//偏移值稍作变化offsetTop = radius * c + radius * ( c2 - c ) * percent;offsetBottom = radius * c + radius * ( c2 - c ) * percent;} else if (mDirection == DIRECTION_RIGHT) {rightX = mStatusPoint1.rightX + percent * (mMoveDistance / 2 - mElasticDistance / 2 );x = mStatusPoint1.x + percent * (mMoveDistance / 2);leftX = mStatusPoint1.leftX + percent * (mMoveDistance / 2 - mElasticDistance / 2 );topX = x;bottomX = x;//偏移值稍作变化offsetTop = radius * c + radius * ( c2 - c ) * percent;offsetBottom = radius * c + radius * ( c2 - c ) * percent;} else if (mDirection == DIRECTION_UP) {topY = mStatusPoint1.topY - percent * (mMoveDistance / 2 - mElasticDistance / 2 );y = mStatusPoint1.y - percent * (mMoveDistance / 2);bottomY = mStatusPoint1.bottomY - percent * (mMoveDistance / 2 - mElasticDistance / 2 );leftY = y;rightY = y;//偏移值稍作变化offsetLeft = radius * c + radius * ( c2 - c ) * percent;offsetRight = radius * c + radius * ( c2 - c ) * percent;} else if (mDirection == DIRECTION_DOWN) {bottomY = mStatusPoint1.bottomY + percent * (mMoveDistance / 2 - mElasticDistance / 2 );y = mStatusPoint1.y + percent * (mMoveDistance / 2);topY = mStatusPoint1.topY + percent * (mMoveDistance / 2 - mElasticDistance / 2 );leftY = y;rightY = y;//偏移值稍作变化offsetLeft = radius * c + radius * ( c2 - c ) * percent;offsetRight = radius * c + radius * ( c2 - c ) * percent;}mStatusPoint2.refresh(x, y, topX, topY, bottomX, bottomY,leftX, leftY, rightX, rightY);}private Ball mStatusPoint3;/*** 动画状态3 (0.5~0.8)*/private void animStatus3() {float percent = (mAnimPercent - 0.5f) * (10f / 3f);if (mDirection == DIRECTION_LEFT) {leftX = mStatusPoint2.leftX - Math.abs(percent * (mEndPoint.rightX - mStatusPoint2.rightX));x = mStatusPoint2.x - Math.abs(percent * (mEndPoint.x - mStatusPoint2.x));rightX = mStatusPoint2.rightX - Math.abs(percent * (mEndPoint.x - mStatusPoint2.x));topX = x;bottomX = x;//偏移值稍作变化offsetTop = radius * c2 - radius * ( c2 - c ) * percent;offsetBottom = radius * c2 - radius * ( c2 - c ) * percent;} else if (mDirection == DIRECTION_RIGHT) {rightX = mStatusPoint2.rightX + percent * (mEndPoint.rightX - mStatusPoint2.rightX);x = mStatusPoint2.x + percent * (mEndPoint.x - mStatusPoint2.x);leftX = mStatusPoint2.leftX + percent * (mEndPoint.x - mStatusPoint2.x);topX = x;bottomX = x;//偏移值稍作变化offsetTop = radius * c2 - radius * ( c2 - c ) * percent;offsetBottom = radius * c2 - radius * ( c2 - c ) * percent;} else if (mDirection == DIRECTION_UP) {topY = mStatusPoint2.topY - Math.abs(percent * (mEndPoint.topY - mStatusPoint2.topY));y = mStatusPoint2.y - Math.abs(percent * (mEndPoint.y - mStatusPoint2.y));bottomY = mStatusPoint2.bottomY - Math.abs(percent * (mEndPoint.y - mStatusPoint2.y));leftY = y;rightY = y;//偏移值稍作变化offsetLeft = radius * c2 - radius * ( c2 - c ) * percent;offsetRight = radius * c2 - radius * ( c2 - c ) * percent;} else if (mDirection == DIRECTION_DOWN) {bottomY = mStatusPoint2.bottomY + percent * (mEndPoint.bottomY - mStatusPoint2.bottomY);y = mStatusPoint2.y + percent * (mEndPoint.y - mStatusPoint2.y);topY = mStatusPoint2.topY + percent * (mEndPoint.y - mStatusPoint2.y);leftY = y;rightY = y;//偏移值稍作变化offsetLeft = radius * c2 - radius * ( c2 - c ) * percent;offsetRight = radius * c2 - radius * ( c2 - c ) * percent;}mStatusPoint3.refresh(x, y, topX, topY, bottomX, bottomY,leftX, leftY, rightX, rightY);}private Ball mStatusPoint4;/*** 动画状态4 (0.8~0.9)*/private void animStatus4() {float percent = (float) (mAnimPercent - 0.8) * 10;if (mDirection == DIRECTION_LEFT) {rightX = mStatusPoint3.rightX - percent * (Math.abs(mEndPoint.rightX - mStatusPoint3.rightX) + mElasticDistance/2);//再做一次赋值,防止和终点不重合leftX = mEndPoint.leftX;x = mEndPoint.x;bottomX = mEndPoint.bottomX;topX = mEndPoint.topX;} else if (mDirection == DIRECTION_RIGHT) {leftX = mStatusPoint3.leftX + percent * (mEndPoint.leftX - mStatusPoint3.leftX +mElasticDistance/2);//再做一次赋值,防止和终点不重合rightX = mEndPoint.rightX;x = mEndPoint.x;bottomX = mEndPoint.bottomX;topX = mEndPoint.topX;} else if (mDirection == DIRECTION_UP) {bottomY = mStatusPoint3.bottomY - percent * (Math.abs(mEndPoint.bottomY - mStatusPoint3.bottomY) + mElasticDistance/2);//再做一次赋值,防止和终点不重合topY = mEndPoint.topY;y = mEndPoint.y;leftY = mEndPoint.leftY;rightY = mEndPoint.rightY;} else if (mDirection == DIRECTION_DOWN) {topY = mStatusPoint3.topY + percent * (mEndPoint.topY - mStatusPoint3.topY + mElasticDistance/2);//再做一次赋值,防止和终点不重合bottomY = mEndPoint.bottomY;y = mEndPoint.y;leftY = mEndPoint.leftY;rightY = mEndPoint.rightY;}mStatusPoint4.refresh(x, y, topX, topY, bottomX, bottomY,leftX, leftY, rightX, rightY);}private Ball mStatusPoint5;/*** 动画状态5 (0.9~1)回弹*/private void animStatus5() {float percent = (float) (mAnimPercent - 0.9) * 10;if (mDirection == DIRECTION_LEFT) {rightX = mStatusPoint4.rightX + percent * (mEndPoint.rightX - mStatusPoint4.rightX);} else if (mDirection == DIRECTION_RIGHT) {leftX = mStatusPoint4.leftX + percent * (mEndPoint.leftX - mStatusPoint4.leftX);} else if (mDirection == DIRECTION_UP) {bottomY = mStatusPoint4.bottomY + percent * (mEndPoint.bottomY - mStatusPoint4.bottomY);} else if (mDirection == DIRECTION_DOWN) {topY = mStatusPoint4.topY + percent * (mEndPoint.topY - mStatusPoint4.topY);}mStatusPoint5.refresh(x, y, topX, topY, bottomX, bottomY,leftX, leftY, rightX, rightY);}/*** 绘制弹性圆* 通过绘制四段三阶贝塞尔曲线,来实现有弹性变化的圆* @param topX* @param topY* @param offsetTop1* @param offsetTop2* @param bottomX* @param bottomY* @param offsetBottom1* @param offsetBottom2* @param leftX* @param leftY* @param offsetLeft1* @param offsetLeft2* @param rightX* @param rightY* @param offsetRight1* @param offsetRight2* @return*/private Path drawMagicCircle(float topX, float topY, float offsetTop1, float offsetTop2,float bottomX, float bottomY, float offsetBottom1, float offsetBottom2,float leftX, float leftY, float offsetLeft1, float offsetLeft2,float rightX, float rightY, float offsetRight1, float offsetRight2) {/*** 绘制每一段三阶贝塞尔曲线需要两个控制点*/PointF controlTop1, controlTop2, controlBottom1, controlBottom2,controlLeft1, controlLeft2, controlRight1, controlRight2;controlTop1 = new PointF();controlTop1.x = topX - offsetTop1;controlTop1.y = topY;controlTop2 = new PointF();controlTop2.x = topX + offsetTop2;controlTop2.y = topY;controlBottom1 = new PointF();controlBottom1.x = bottomX - offsetBottom1;controlBottom1.y = bottomY;controlBottom2 = new PointF();controlBottom2.x = bottomX + offsetBottom2;controlBottom2.y = bottomY;controlLeft1 = new PointF();controlLeft1.x = leftX;controlLeft1.y = leftY - offsetLeft1;controlLeft2 = new PointF();controlLeft2.x = leftX;controlLeft2.y = leftY + offsetLeft2;controlRight1 = new PointF();controlRight1.x = rightX;controlRight1.y = rightY - offsetRight1;controlRight2 = new PointF();controlRight2.x = rightX;controlRight2.y = rightY + offsetRight2;Path path = new Path();/*** 绘制top到left的圆弧*/path.moveTo(topX, topY);path.cubicTo(controlTop1.x, controlTop1.y, controlLeft1.x, controlLeft1.y, leftX, leftY);/*** 绘制left到bottom的圆弧*/path.cubicTo(controlLeft2.x ,controlLeft2.y, controlBottom1.x, controlBottom1.y, bottomX,bottomY);/*** 绘制bottom到right的圆弧*/path.cubicTo(controlBottom2.x, controlBottom2.y, controlRight2.x, controlRight2.y,rightX, rightY);/*** 绘制right到top的圆弧*/path.cubicTo(controlRight1.x, controlRight1.y, controlTop2.x, controlTop2.y, topX, topY);return path;}/*** 求两点之间的距离* @param x1 第一个点的横坐标* @param y1 第一个点的纵坐标* @param x2 第二个点的横坐标* @param y2 第二个点的纵坐标* @return 两点距离*/private float getDistance(float x1, float y1, float x2, float y2) {return (float) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451
  • 452
  • 453
  • 454
  • 455
  • 456
  • 457
  • 458
  • 459
  • 460
  • 461
  • 462
  • 463
  • 464
  • 465
  • 466
  • 467
  • 468
  • 469
  • 470
  • 471
  • 472
  • 473
  • 474
  • 475
  • 476
  • 477
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451
  • 452
  • 453
  • 454
  • 455
  • 456
  • 457
  • 458
  • 459
  • 460
  • 461
  • 462
  • 463
  • 464
  • 465
  • 466
  • 467
  • 468
  • 469
  • 470
  • 471
  • 472
  • 473
  • 474
  • 475
  • 476
  • 477

Android开发——贝塞尔曲线解析相关推荐

  1. Android利用贝塞尔曲线实现翻书效果(适配AndroidX)

    实现背景 不知道你有没有遇到同样的问题,要实现翻书效果,如果你是使用github上的demo或者好多博客上写的方式,你会发现,当api从28开始,会抛出Invalid Region.Op.REPLAC ...

  2. Android 贝塞尔曲线解析

    相信很多同学都知道"贝塞尔曲线"这个词,我们在很多地方都能经常看到.利用"贝塞尔曲线"可以做出很多好看的UI效果,本篇博客就让我们一起学习"贝塞尔曲线 ...

  3. Android中贝塞尔曲线的绘制方法

    贝塞尔曲线,很多人可能不太了解,什么叫做贝塞尔曲线呢?这里先做一下简单介绍:贝塞尔曲线也可以叫做贝济埃曲线或者贝兹曲线,它由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋.一般的矢量图形软件常 ...

  4. android贝塞尔曲线实例,android中贝塞尔曲线的应用示例

    前言: 贝塞尔曲线又称贝兹曲线,它的主要意义在于无论是直线或曲线都能在数学上予以描述.最初由保罗·德卡斯特里奥(Paul de Casteljau)于1959年运用德卡斯特里奥演算法开发(de Cas ...

  5. android 自定义心形,android使用贝塞尔曲线自定义心形View

    贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线. 绘制心形需要Path类中的两个方法分别是: moveTo(float x,float y) 贝塞 ...

  6. android离散点贝塞尔曲线,离散点拟合曲线贝塞尔曲线B样条.ppt

    离散点拟合曲线贝塞尔曲线B样条 第三章 离散点绘制平面曲线;;不规则曲线(拟合曲线):指已知平面一些离散点的坐标,但曲线方程未知,需要人为设计曲线方程对这些点进行拟合形成的曲线.; 在用拟合方法建立曲 ...

  7. iOS开发 贝塞尔曲线UIBezierPath

    2019独角兽企业重金招聘Python工程师标准>>> UIBezierPath基础 UIBezierPath对象是CGPathRef数据类型的封装.每一个直线段或者曲线段的结束的地 ...

  8. iOS开发 贝塞尔曲线UIBezierPath(后记)

    使用CAShapeLayer与UIBezierPath可以实现不在view的drawRect方法中就画出一些想要的图形 . 1:UIBezierPath: UIBezierPath是在 UIKit 中 ...

  9. Android高级-贝塞尔曲线与计算规则

    我们先看Path详解: 我们还是从demo看吧 看demo 运行结果: 去掉闭合: 其中 mPath.rLineTo和mPaht.lineTo效果是一样的,我们看一下区别: 运行效果: 我们可以看到, ...

最新文章

  1. vs2012中 build、compile,debug区别
  2. 开源云计算平台 abiCloud
  3. Java黑皮书课后题第8章:*8.30(代数:解答线性方程)编写一个方法,解答下面的2*2线性方程组系统
  4. java改变变量编码方式_Java 10将如何改变您的编码方式
  5. 单例设计模式 (2)
  6. 【口语语言理解】新分类!全总结!最新Awesome-SLU-Survey资源库开源!
  7. 团队作业个人博客07
  8. 高效管理 Android 前台服务
  9. mysql8.0认证方式修改
  10. SQL SERVER 2000 自动下载木马病毒 cmd.exe和ftp.exe解决办法
  11. C语言之-1与0xffffffff
  12. 旋转(Rotation)矩阵转欧拉角(euler)
  13. linux下 oracle怎么导入dmp文件
  14. macsv服务器状态,macsv操作员站下装过程及服务器下装过程备课讲稿.pdf
  15. 马铃薯淀粉生产线的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  16. oracle reco进程停止,oracle的后台进程能否杀掉
  17. 不允许sam账户和共享的匿名枚举_不允许SAM 帐户匿名枚举是什么意思?
  18. 论文笔记 Semantics-Guided Neural Networks for Efficient Skeleton-Based Human Action Recognition - CVPR
  19. 星座 member.php,计算 星座 PHP
  20. Latex---IEEE论文写作

热门文章

  1. 王思聪吃翔项目 - 共享充电宝 - 经营、销售分析系统DB设计实践
  2. 11个相似图片搜索网站(以图找图)[转]
  3. 基于STM32F103c8t6的智能垃圾桶项目
  4. 学习 lt MATLAB gt 心得,lt;lt;MATLAB可视化大学物理学gt;gt;使大学物理更具体,更有趣。 - 物理 - 小木虫 - 学术 科研 互动社区...
  5. Linux的编程模型ILP32和LP64
  6. 从小市值因子策略入手,带你入门量化投资 (附年化收益率77.83%策略)
  7. Java一对多、多对多关系示例
  8. (一)理解word2vec:原理篇
  9. java while求百钱买百鸡问题_java - 百钱百鸡小算法
  10. JAVA 环境安装包 JDK 64位 jdk-8u221-windows-x64.exe