Android 实现微信满屏表情下落动画

效果:

这里写图片描述

看完上面的效果图,大家一定都迫不及待地想要试一试了,那就让我们来动手吧。

首先我们定义一个实体类DropLook:

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
/**
* 下落的表情
*/
public class DropLook {

// x轴坐标
private float x;
// y轴坐标
private float y;
// 初始旋转角度
private float rotation;
// 下落速度
private float speed;
// 旋转速度
private float rotationSpeed;
// 宽度
private int width;
// 高度
private int height;
// 图片
private Bitmap bitmap;

public float getX() {
return x;
}

public void setX(float x) {
this.x = x;
}

public float getY() {
return y;
}

public void setY(float y) {
this.y = y;
}

public float getRotationSpeed() {
return rotationSpeed;
}

public void setRotationSpeed(float rotationSpeed) {
this.rotationSpeed = rotationSpeed;
}

public float getRotation() {
return rotation;
}

public void setRotation(float rotation) {
this.rotation = rotation;
}

public float getSpeed() {
return speed;
}

public void setSpeed(float speed) {
this.speed = speed;
}

public int getWidth() {
return width;
}

public void setWidth(int width) {
this.width = width;
}

public Bitmap getBitmap() {
return bitmap;
}

public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
}

public int getHeight() {
return height;
}

public void setHeight(int height) {
this.height = height;
}

}

我们定义的实体类很简单,只是设置了如宽高、x,y坐标、下落速度等。接下来我们再创建一个DropLookFactory类,用来创建DropLook对象。

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
public class DropLookFactory {

private DropLookFactory() {

}

public static DropLook createDropLook(int width, int height,Bitmap originalBitmap) {
DropLook look = new DropLook();
if (originalBitmap == null) {
throw new NullPointerException("originalBitmap cannot be null");
}
// 设置与图片等宽
look.setWidth(originalBitmap.getWidth());
// 设置与图片等高
look.setHeight(originalBitmap.getHeight());
// 设置起始位置的X坐标
look.setX((float) Math.random() * (width - look.getWidth()));
// 设置起始位置的Y坐标
look.setY((float) Math.random() * (height - look.getHeight()));
// 设置速度
look.setSpeed(20 + (float) Math.random() * 40);
// 设置初始旋转角度
look.setRotation((float) Math.random() * 180 - 90);
// 设置旋转速度
look.setRotationSpeed((float) Math.random() * 90 - 60);
// 设置图片
look.setBitmap(originalBitmap);
return look;
}

}

其中createDropLook(Context context, float xRange, Bitmap originalBitmap)的第一个参数代表着下落表情在x轴上的范围,第二个参数代表在y轴上的范围,第三个参数是表情的图片。在createDropLook方法中相信大家都看得懂,主要就是用随机数初始化DropLook的坐标及下落速度等。

好了,下面就是今天的重头戏DropLookView,先来看看onMeasure():

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
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
mWidth = widthSize;
} else {
mWidth = Tools.dip2px(getContext(),200);
}
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode == MeasureSpec.EXACTLY) {
mHeight = heightSize;
} else {
mHeight = Tools.dip2px(getContext(),200);
}
setMeasuredDimension(mWidth, mHeight);
if (looks.size() == 0) {
for (int i = 0; i < DEFAULT_DROP_LOOK_NUMS; ++i) {
looks.add(DropLookFactory.createDropLook(mWidth, mHeight, mBitmap));
}
Log.i(TAG, "num = " + looks.size());
}
}

onMeasure里主要是对View的测量,如果是wrap_content的话设置一个默认的宽高度200dp。然后就是初始化DropLook,looks是DropLook类的集合,用于管理DropLook。而DEFAULT_LOOK_NUMS是默认的looks集合的数量。

接下来就是最关键的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
25
26
27
28
29
30
31
32
33
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
long nowTime = System.currentTimeMillis();
if (nowTime - startTime < 100) {
try {
Thread.sleep(100 + startTime - nowTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

for (int i = 0; i < DEFAULT_DROP_LOOK_NUMS; i++) {

DropLook look = looks.get(i);
mMatrix.setTranslate(-look.getWidth() / 2, -look.getHeight() / 2);
mMatrix.postRotate(look.getRotation());
mMatrix.postTranslate(look.getWidth() / 2 + look.getX(), look.getHeight() / 2 + look.getY());
canvas.drawBitmap(look.getBitmap(), mMatrix, mPaint);

look.setY(look.getY() + look.getSpeed());
if (look.getY() > getHeight()) {
look.setY((float) (0 - Math.random() * look.getHeight()));
}

look.setRotation(look.getRotation() + look.getRotationSpeed());
}

canvas.restore();
startTime = System.currentTimeMillis();
invalidate();
}

一开始判断时间间隔如果没有超过100ms,就让线程睡眠一会。然后就是用drawBitmap的方法把looks里面逐个绘制出来。并且再把look的y轴坐标加上下落速度等,旋转的角度也是如此。最后就是调用invalidate()不断地重绘。总体上并没有什么难点。

以下是DropLookView的完整代码:

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
/**
* 表情下落view
*/
public class DropLookView extends View {

// 表情
private Bitmap mBitmap;
// 所有表情集合
List<DropLook> looks = new ArrayList();
// view开始时间
private long startTime;
// view宽度
private int mWidth;
// view高度
private int mHeight;
// 画笔
private Paint mPaint;
// 默认表情下落数
private static final int DEFAULT_DROP_LOOK_NUMS = 35;

private static final String TAG = "DropLookView";

private Matrix mMatrix = new Matrix();

public DropLookView(Context context) {
this(context, null);
}

public DropLookView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public DropLookView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 图片
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.d_5_xiaoku);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
mWidth = widthSize;
} else {
mWidth = Tools.dip2px(getContext(),200);
}
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode == MeasureSpec.EXACTLY) {
mHeight = heightSize;
} else {
mHeight = Tools.dip2px(getContext(),200);
}
setMeasuredDimension(mWidth, mHeight);
if (looks.size() == 0) {
for (int i = 0; i < DEFAULT_DROP_LOOK_NUMS; ++i) {
looks.add(DropLookFactory.createDropLook(mWidth, mHeight, mBitmap));
}
Log.i(TAG, "num = " + looks.size());
}
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
long nowTime = System.currentTimeMillis();
if (nowTime - startTime < 100) {
try {
Thread.sleep(100 + startTime - nowTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

for (int i = 0; i < DEFAULT_DROP_LOOK_NUMS; i++) {

DropLook look = looks.get(i);
mMatrix.setTranslate(-look.getWidth() / 2, -look.getHeight() / 2);
mMatrix.postRotate(look.getRotation());
mMatrix.postTranslate(look.getWidth() / 2 + look.getX(), look.getHeight() / 2 + look.getY());
canvas.drawBitmap(look.getBitmap(), mMatrix, mPaint);

look.setY(look.getY() + look.getSpeed());
if (look.getY() > getHeight()) {
look.setY((float) (0 - Math.random() * look.getHeight()));
}

look.setRotation(look.getRotation() + look.getRotationSpeed());
}

canvas.restore();
startTime = System.currentTimeMillis();
invalidate();
}

}