相信很多同学都知道“贝塞尔曲线”这个词,我们在很多地方都能经常看到。利用“贝塞尔曲线”可以做出很多好看的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);
}
@Override
protected 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;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
control.x = event.getX();
control.y = event.getY();
invalidate();
return true;
}
@Override
protected 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;
}
@Override
protected 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;
}
@Override
public 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;
}
@Override
protected 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(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)
绘制动圆和固定圆
/**
* 固定圆 并且初始化
*/
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);
float 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()
//跳到某个点1
path.moveTo(mFixedPoints[0].x, mFixedPoints[0].y)
//画曲线 1--->2
path.quadTo(mControlPoint.x, mControlPoint.y, mDragPoints[0].x, mDragPoints[0].y)
//画直线2---->3
path.lineTo(mDragPoints[1].x, mDragPoints[1].y)
//画曲线3---->4
path.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()方法,让红点随手势动起来
@Override
public 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;
invalidate();
}
break;
case MotionEvent.ACTION_UP:
if (isOutToRange) {
isOutToRange = false;
float d = GeometryUtil.getDistanceBetween2Points(mDragCircle, mFixedCircle);
if (d > farestDistance) {
isDisappear = true;
invalidate();
} else {
updataDragCircle(mFixedCircle.x, mFixedCircle.y);
isDisappear = false;
}
} else {
final PointF tempDragCircle = new PointF(mDragCircle.x, mDragCircle.y);
final ValueAnimator mAnim = ValueAnimator.ofFloat(1.0f);
mAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public 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的情况
@Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.translate(0, -mStatusBarHeight);
float distance = getTempFiexdCircle();
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);
this.mControlPoint = GeometryUtil.getMiddlePoint(mDragCircle, mFixedCircle);
if (!isDisappear) {
canvas.drawCircle(mDragCircle.x, mDragCircle.y, mDragRadius, mPaint);
if (!isOutToRange) {
canvas.drawCircle(mFixedCircle.x, mFixedCircle.y, distance, mPaint);
Path path = new Path();
path.moveTo(mFixedPoints[0].x, mFixedPoints[0].y);
path.quadTo(mControlPoint.x, mControlPoint.y, mDragPoints[0].x, mDragPoints[0].y);
path.lineTo(mDragPoints[1].x, mDragPoints[1].y);
path.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 {
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;
}
}
- 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);
lp.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() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
PointF pointF = (PointF) valueAnimator.getAnimatedValue();
target.setX(pointF.x);
target.setY(pointF.y);
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() {
@Override
public 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];
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mCenterX = w / 2;
mCenterY = h / 2;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawCoordinateSystem(canvas);
canvas.translate(mCenterX, mCenterY);
canvas.scale(1,-1);
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);
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() {
@Override
public 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() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
if (mElasticBallInterface != null) {
mElasticBallInterface.onFinish();
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public 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