一、背景介绍
项目背景是在界面中弹出一个浮层动画,同时播放一个音效。
二、当前实现
实现思路比较简单:继承一个DialogFragment,在相关的生命周期方法onViewCreated中调用startLottieAnim进行动画播放,同时监听lottie动画播放的回调事件,在动画开始播放时播放音效文件;动画播放结束时关闭DialogFragment。
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
29private void startLottieAnim(String assetFolder, final Uri voiceUri){
lottieAnimationView.setImageAssetsFolder(assetFolder + "/images");
lottieAnimationView.setAnimation(assetFolder + "/anim.json");
lottieAnimationView.setRepeatCount(0);
lottieAnimationView.addAnimatorListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation){
playAnimVoice(voiceUri);
}
@Override
public void onAnimationEnd(Animator animation){
stopAnimVoice();
dismiss();
}
@Override
public void onAnimationCancel(Animator animation){
stopAnimVoice();
dismiss();
}
@Override
public void onAnimationRepeat(Animator animation){
playAnimVoice(voiceUri);
}
});
lottieAnimationView.playAnimation();
}
三、发现问题
对于以上的代码,实际运行起来会发现动画播放的同时并不能播放音频,而且播放结束也不会自动消失。也就是说onAnimationStart和onAnimationEnd方法并没有被回调。这是为什么呢?看lottie实现源码 (BaseLottieAnimator),会发现
1
2
3
4
5
6
7
8
9void notifyStart(boolean isReverse){
for (Animator.AnimatorListener listener : listeners) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
listener.onAnimationStart(this, isReverse);
} else {
listener.onAnimationStart(this);
}
}
}
1
2
3
4
5
6
7
8void notifyEnd(boolean isReverse){
for (Animator.AnimatorListener listener : listeners) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
listener.onAnimationEnd(this, isReverse);
} else {
listener.onAnimationEnd(this);
}
}
在通知动画开始和结束的时候,会根据系统版本决定走哪个方法,这里需要注意方法签名。由于之前代码只写了一个参数的方法回调,因此在Android O及以上版本中会走另一个方法回调。因此,首先想到需要在注册的AnimatorListener中添加之前遗漏的两个方法回调。
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{
lottieAnimationView.setImageAssetsFolder(assetFolder + "/images");
lottieAnimationView.setAnimation(assetFolder + "/anim.json");
lottieAnimationView.setRepeatCount(0);
lottieAnimationView.addAnimatorListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation){
playAnimVoice(voiceUri);
}
@Override
public void onAnimationStart(Animator animation, boolean isReverse){
playAnimVoice(voiceUri);
}
@Override
public void onAnimationEnd(Animator animation){
stopAnimVoice();
dismiss();
}
@Override
public void onAnimationEnd(Animator animation, boolean isReverse){
stopAnimVoice();
dismiss();
}
@Override
public void onAnimationCancel(Animator animation){
stopAnimVoice();
dismiss();
}
@Override
public void onAnimationRepeat(Animator animation){
playAnimVoice(voiceUri);
}
});
lottieAnimationView.playAnimation();
}
修改之后运行会发现,动画播放结束的回调事件成功接收了,但是音效依然无法播放。也就是onAnimationStart方法依然未被回调。继续看下源码:在LottieAnimationView.java中
1
2
3
4
5
6
7
8public void playAnimation(){
if (isShown()) {
lottieDrawable.playAnimation();
enableOrDisableHardwareLayer();
} else {
wasAnimatingWhenNotShown = true;
}
}
如果isShown判断成功,会继续走LottieDrawable.java中的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public void playAnimation(){
if (compositionLayer == null) {
lazyCompositionTasks.add(new LazyCompositionTask() {
@Override
public void run(LottieComposition composition){
playAnimation();
}
});
return;
}
if (systemAnimationsEnabled || getRepeatCount() == 0) {
animator.playAnimation();
}
if (!systemAnimationsEnabled) {
setFrame((int) (getSpeed() < 0 ? getMinFrame() : getMaxFrame()));
}
}
进而走到LottieValueAnimator.java中的playAnimation
1
2
3
4
5
6
7
8
9@MainThread
public void playAnimation(){
running = true;
notifyStart(isReversed());
setFrame((int) (isReversed() ? getMaxFrame() : getMinFrame()));
lastFrameTimeNs = 0;
repeatCount = 0;
postFrameCallback();
}
在这里看到了熟悉的notifyStart方法。可是我们并没有收到对应的回调,可以想到LottieAnimationView.java中并没有走isShown判断分支,也就是说走了
1
2
3
4
5
6
7
8public void playAnimation(){
if (isShown()) {
lottieDrawable.playAnimation();
enableOrDisableHardwareLayer();
} else {
wasAnimatingWhenNotShown = true;
}
}
只是标记了一个状态,标明lottie动画还未显示出来。那它为什么后来又能播放动画呢?在LottieAnimationView.java中有监听可见性回调的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19protected void onVisibilityChanged(@NonNull View changedView, int visibility){
// This can happen on older versions of Android because onVisibilityChanged gets called from the
// constructor of View so this will get called before lottieDrawable gets initialized.
// https://github.com/airbnb/lottie-android/issues/1143
if (lottieDrawable == null) {
return;
}
if (isShown()) {
if (wasAnimatingWhenNotShown) {
resumeAnimation();
wasAnimatingWhenNotShown = false;
}
} else {
if (isAnimating()) {
pauseAnimation();
wasAnimatingWhenNotShown = true;
}
}
}
当lottieview从不可见到可见时,会根据wasAnimatingWhenNotShown之前记录的这个状态去resumeAnimation。在resumeAnimation方法一路跟下去最后会走到LottieValueAnimator.java的
1
2
3
4
5
6
7
8
9
10public void resumeAnimation(){
running = true;
postFrameCallback();
lastFrameTimeNs = 0;
if (isReversed() && getFrame() == getMinFrame()) {
frame = getMaxFrame();
} else if (!isReversed() && getFrame() == getMaxFrame()) {
frame = getMinFrame();
}
}
这里可以发现并没有像startAnimation那样的notifyXXX的回调方法了。因此我们收不到onAnimationStart的方法回调了。
至此弄清楚了异常的原因,如何修改就很简单了。现在已经能猜到是布局加载之后,lottieview还没有渲染出来我们就去startAnimation导致回调无法走到,因此我们可以通过对lottieView进行post或者通过监听viewTreeObserver的事件再进行startPlayAnimation操作就行了。
四、总结
1、回调方法要写完整;
2、如果弹框一开始就要显示lottie动画,需要等ui控件可见之后再播放动画