项目地址:https://github.com/JeasonWong/SlackLoadingView
老规矩,先上效果。
图好大。。
说下第一眼看到这个动画后的思路:
+两根平行线,要用到直线方程 y=kx+b
+另外两根平行线,与之前两根平行线的斜率相乘为-1,即k1*k2=-1
+线条做圆周运动就是k值的不断变化
+然后就是简单的线条长度变化
我相信很多人第一眼会和我有类似的思路,但是当我上了个厕所后意识到我想复杂了~
说下上完厕所后的思路:
不要想着线条是斜的,就是一个普通的线段,一个LineTo搞定(startX和stopX一样,仅Y不同)
线条的垂直更容易,直接Canvas翻转(转过后再转回)
整个动画的圆周运动也是Canvas翻转(转过后不转回)
线条的单度变化依然用属性动画(这是必须的。。)
动画开始前就让整个Canvas旋转
这样一来就太容易了。
我把动画分成了四步:
画布旋转及线条变化动画(Canvas Rotate Line Change)
画布旋转动画(Canvas Rotate)
画布旋转圆圈变化动画(Canvas Rotate Circle Change)
线条变化动画(Line Change)
详细说明前先介绍下成员变量和一些初始化
成员变量
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
|
//静止状态
private final int STATUS_STILL = 0 ;
//加载状态
private final int STATUS_LOADING = 1 ;
//线条最大长度
private final int MAX_LINE_LENGTH = dp2px(getContext(), 120 );
//线条最短长度
private final int MIN_LINE_LENGTH = dp2px(getContext(), 40 );
//最大间隔时长
private final int MAX_DURATION = 3000 ;
//最小间隔时长
private final int MIN_DURATION = 500 ;
private Paint mPaint;
private int [] mColors = new int []{ 0xB07ECBDA , 0xB0E6A92C , 0xB0D6014D , 0xB05ABA94 };
private int mWidth, mHeight;
//动画间隔时长
private int mDuration = MIN_DURATION;
//线条总长度
private int mEntireLineLength = MIN_LINE_LENGTH;
//圆半径
private int mCircleRadius;
//所有动画
private List<Animator> mAnimList = new ArrayList<>();
//Canvas起始旋转角度
private final int CANVAS_ROTATE_ANGLE = 60 ;
//动画当前状态
private int mStatus = STATUS_STILL;
//Canvas旋转角度
private int mCanvasAngle;
//线条长度
private float mLineLength;
//半圆Y轴位置
private float mCircleY;
//第几部动画
private int mStep;
|
初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
|
private void initView() {
mPaint = new Paint();
mPaint.setAntiAlias( true );
mPaint.setColor(mColors[ 0 ]);
}
private void initData() {
mCanvasAngle = CANVAS_ROTATE_ANGLE;
mLineLength = mEntireLineLength;
mCircleRadius = mEntireLineLength / 5 ;
mPaint.setStrokeWidth(mCircleRadius * 2 );
mStep = 0 ;
}
|
一、画布旋转及线条变化动画(Canvas Rotate Line Change)
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
|
/**
* Animation1
* 动画1
* Canvas Rotate Line Change
* 画布旋转及线条变化动画
*/
private void startCRLCAnim() {
Collection<Animator> animList = new ArrayList<>();
ValueAnimator canvasRotateAnim = ValueAnimator.ofInt(CANVAS_ROTATE_ANGLE + 0 , CANVAS_ROTATE_ANGLE + 360 );
canvasRotateAnim.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCanvasAngle = ( int ) animation.getAnimatedValue();
}
});
animList.add(canvasRotateAnim);
ValueAnimator lineWidthAnim = ValueAnimator.ofFloat(mEntireLineLength, -mEntireLineLength);
lineWidthAnim.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mLineLength = ( float ) animation.getAnimatedValue();
invalidate();
}
});
animList.add(lineWidthAnim);
AnimatorSet animationSet = new AnimatorSet();
animationSet.setDuration(mDuration);
animationSet.playTogether(animList);
animationSet.setInterpolator( new LinearInterpolator());
animationSet.addListener( new AnimatorListener() {
@Override
public void onAnimationEnd(Animator animation) {
Log.d( "@=>" , "动画1结束" );
if (mStatus == STATUS_LOADING) {
mStep++;
startCRAnim();
}
}
});
animationSet.start();
mAnimList.add(animationSet);
}
|
第一步动画涉及到两个动画同时进行,所以使用了AnimatorSet,这个类很强大,可以让N个动画同时进行(playTogether),也可以让N个动画顺序执行(playSequentially)。
说到这里,其实我的四个动画就是顺序进行的,但是每个动画里又有同时进行的动画,为了讲解方便,我是监听了onAnimationEnd来控制动画执行顺序,其实可以直接使用playSequentially。
上方动画就干了两件事:
1、旋转画布,从CANVAS_ROTATE_ANGLE + 0转到CANVAS_ROTATE_ANGLE + 360,CANVAS_ROTATE_ANGLE是画布初始倾斜角度
2、线条长度变化,从mEntireLineLength到-mEntireLineLength。
对应的onDraw方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
@Override
protected void onDraw(Canvas canvas) {
super .onDraw(canvas);
switch (mStep % 4 ) {
case 0 :
for ( int i = 0 ; i < mColors.length; i++) {
mPaint.setColor(mColors[i]);
drawCRLC(canvas, mWidth / 2 - mEntireLineLength / 2 .2f, mHeight / 2 - mLineLength, mWidth / 2 - mEntireLineLength / 2 .2f, mHeight / 2 + mEntireLineLength, mPaint, mCanvasAngle + i * 90 );
}
break ;
...
}
}
...
private void drawCRLC(Canvas canvas, float startX, float startY, float stopX, float stopY, @NonNull Paint paint, int rotate) {
canvas.rotate(rotate, mWidth / 2 , mHeight / 2 );
canvas.drawArc( new RectF(startX - mCircleRadius, startY - mCircleRadius, startX + mCircleRadius, startY + mCircleRadius), 180 , 180 , true , mPaint);
canvas.drawLine(startX, startY, stopX, stopY, paint);
canvas.drawArc( new RectF(stopX - mCircleRadius, stopY - mCircleRadius, stopX + mCircleRadius, stopY + mCircleRadius), 0 , 180 , true , mPaint);
canvas.rotate(-rotate, mWidth / 2 , mHeight / 2 );
}
|
是不是很机智,drawCRLC做了三件事:
1、画布旋转后又旋转回来
2、画半圆(为什么要画半圆?不画整个圆?这里留个思考题。)
3、画线条
这样动画1就完成了。
二、画布旋转动画(Canvas Rotate)
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
|
/**
* Animation2
* 动画2
* Canvas Rotate
* 画布旋转动画
*/
private void startCRAnim() {
ValueAnimator canvasRotateAnim = ValueAnimator.ofInt(mCanvasAngle, mCanvasAngle + 180 );
canvasRotateAnim.setDuration(mDuration / 2 );
canvasRotateAnim.setInterpolator( new LinearInterpolator());
canvasRotateAnim.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCanvasAngle = ( int ) animation.getAnimatedValue();
invalidate();
}
});
canvasRotateAnim.addListener( new AnimatorListener() {
@Override
public void onAnimationEnd(Animator animation) {
Log.d( "@=>" , "动画2结束" );
if (mStatus == STATUS_LOADING) {
mStep++;
startCRCCAnim();
}
}
});
canvasRotateAnim.start();
mAnimList.add(canvasRotateAnim);
}
...
@Override
protected void onDraw(Canvas canvas) {
super .onDraw(canvas);
switch (mStep % 4 ) {
...
case 1 :
for ( int i = 0 ; i < mColors.length; i++) {
mPaint.setColor(mColors[i]);
drawCR(canvas, mWidth / 2 - mEntireLineLength / 2 .2f, mHeight / 2 + mEntireLineLength, mPaint, mCanvasAngle + i * 90 );
}
break ;
...
}
}
...
private void drawCR(Canvas canvas, float x, float y, @NonNull Paint paint, int rotate) {
canvas.rotate(rotate, mWidth / 2 , mHeight / 2 );
canvas.drawCircle(x, y, mCircleRadius, paint);
canvas.rotate(-rotate, mWidth / 2 , mHeight / 2 );
}
|
有了动画1的底子,那这个就太容易了,只是简单的旋转Canvas。
三、画布旋转圆圈变化动画(Canvas Rotate Circle Change)
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
|
/**
* Animation3
* 动画3
* Canvas Rotate Circle Change
* 画布旋转圆圈变化动画
*/
private void startCRCCAnim() {
Collection<Animator> animList = new ArrayList<>();
ValueAnimator canvasRotateAnim = ValueAnimator.ofInt(mCanvasAngle, mCanvasAngle + 90 , mCanvasAngle + 180 );
canvasRotateAnim.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCanvasAngle = ( int ) animation.getAnimatedValue();
}
});
animList.add(canvasRotateAnim);
ValueAnimator circleYAnim = ValueAnimator.ofFloat(mEntireLineLength, mEntireLineLength / 4 , mEntireLineLength);
circleYAnim.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCircleY = ( float ) animation.getAnimatedValue();
invalidate();
}
});
animList.add(circleYAnim);
AnimatorSet animationSet = new AnimatorSet();
animationSet.setDuration(mDuration);
animationSet.playTogether(animList);
animationSet.setInterpolator( new LinearInterpolator());
animationSet.addListener( new AnimatorListener() {
@Override
public void onAnimationEnd(Animator animation) {
Log.d( "@=>" , "动画3结束" );
if (mStatus == STATUS_LOADING) {
mStep++;
startLCAnim();
}
}
});
animationSet.start();
mAnimList.add(animationSet);
}
...
@Override
protected void onDraw(Canvas canvas) {
super .onDraw(canvas);
switch (mStep % 4 ) {
...
case 2 :
for ( int i = 0 ; i < mColors.length; i++) {
mPaint.setColor(mColors[i]);
drawCRCC(canvas, mWidth / 2 - mEntireLineLength / 2 .2f, mHeight / 2 + mCircleY, mPaint, mCanvasAngle + i * 90 );
}
break ;
...
}
}
...
private void drawCRCC(Canvas canvas, float x, float y, @NonNull Paint paint, int rotate) {
canvas.rotate(rotate, mWidth / 2 , mHeight / 2 );
canvas.drawCircle(x, y, mCircleRadius, paint);
canvas.rotate(-rotate, mWidth / 2 , mHeight / 2 );
}
|
动画3做了两件事:
1、旋转Canvas
2、变化Circle的Y坐标,达到往里缩的效果
四、线条变化动画(Line Change)
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
|
/**
* Animation4
* 动画4
* Line Change
* 线条变化动画
*/
private void startLCAnim() {
ValueAnimator lineWidthAnim = ValueAnimator.ofFloat(mEntireLineLength, -mEntireLineLength);
lineWidthAnim.setDuration(mDuration);
lineWidthAnim.setInterpolator( new LinearInterpolator());
lineWidthAnim.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mLineLength = ( float ) animation.getAnimatedValue();
invalidate();
}
});
lineWidthAnim.addListener( new AnimatorListener() {
@Override
public void onAnimationEnd(Animator animation) {
Log.d( "@=>" , "动画4结束" );
if (mStatus == STATUS_LOADING) {
mStep++;
startCRLCAnim();
}
}
});
lineWidthAnim.start();
mAnimList.add(lineWidthAnim);
}
...
@Override
protected void onDraw(Canvas canvas) {
super .onDraw(canvas);
switch (mStep % 4 ) {
...
case 3 :
for ( int i = 0 ; i < mColors.length; i++) {
mPaint.setColor(mColors[i]);
drawLC(canvas, mWidth / 2 - mEntireLineLength / 2 .2f, mHeight / 2 + mEntireLineLength, mWidth / 2 - mEntireLineLength / 2 .2f, mHeight / 2 + mLineLength, mPaint, mCanvasAngle + i * 90 );
}
break ;
}
}
...
private void drawLC(Canvas canvas, float startX, float startY, float stopX, float stopY, @NonNull Paint paint, int rotate) {
canvas.rotate(rotate, mWidth / 2 , mHeight / 2 );
canvas.drawArc( new RectF(startX - mCircleRadius, startY - mCircleRadius, startX + mCircleRadius, startY + mCircleRadius), 0 , 180 , true , mPaint);
canvas.drawLine(startX, startY, stopX, stopY, paint);
canvas.drawArc( new RectF(stopX - mCircleRadius, stopY - mCircleRadius, stopX + mCircleRadius, stopY + mCircleRadius), 180 , 180 , true , mPaint);
canvas.rotate(-rotate, mWidth / 2 , mHeight / 2 );
}
|
动画4只做了线条的变化。
这样整个Slack的Loading动画就完成了,是不是很简单。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。