将Build SDK从23升级到27后onAnimationEnd触发两次问题求助
我之前在SDK版本升级时也碰到过类似的动画回调异常问题,结合你的代码和场景,咱们来分析下根本原因和更彻底的解决方案:
可能的根本原因
1. Android 7.0+ 动画系统的行为变更
从API 24开始,Android对动画的替换逻辑做了优化。你代码中先调用fragmentToPopView.clearAnimation();再立刻启动新动画,在旧版本(SDK23)中这会直接终止旧动画并启动新的,但在SDK27里,clearAnimation()会触发动画的取消回调,而新动画结束后又会触发一次正常的onAnimationEnd,导致两次回调。
2. setFillAfter(true)的状态处理差异
当设置setFillAfter(true)时,动画结束后视图会保持最终状态。SDK23之后,系统对视图填充状态的检测逻辑有调整,可能在动画结束后,视图状态的变更被系统额外捕获,从而再次触发onAnimationEnd。
3. FragmentManager与动画的时序冲突
SDK27中popBackStackImmediate()的内部实现有更新,在动画执行过程中执行Fragment弹出操作,可能触发视图的重新布局,间接导致动画回调被重复调用。
彻底解决方案(替代临时的isAnimating标志)
方案1:优化动画替换逻辑
传统的startAnimation()本身会自动替换当前视图正在运行的动画,不需要手动调用clearAnimation()。你可以移除fragmentToPopView.clearAnimation();这行代码,直接启动新动画:
@Override public void onBackPressed() { Animation slideOutRightAnimation = AnimationUtils.loadAnimation(this, R.anim.slide_out_right); slideOutRightAnimation.setFillAfter(true); slideOutRightAnimation.setAnimationListener(new AnimationListener() { @Override public void onAnimationStart(Animation animation) { // 不需要isAnimating标志 } @Override public void onAnimationRepeat(Animation animation) {} @Override public void onAnimationEnd(Animation animation) { getSupportFragmentManager().popBackStackImmediate(); } }); // 移除clearAnimation调用 fragmentToPopView.startAnimation(slideOutRightAnimation); }
如果担心视图有未结束的动画,也可以加个判断再清理:
if (fragmentToPopView.getAnimation() != null && !fragmentToPopView.getAnimation().hasEnded()) { fragmentToPopView.clearAnimation(); }
方案2:改用ViewPropertyAnimator(推荐)
传统Animation类在API12后就逐渐被ViewPropertyAnimator取代,它的回调更稳定,适配新版本系统的动画逻辑:
@Override public void onBackPressed() { int viewWidth = fragmentToPopView.getWidth(); fragmentToPopView.animate() .translationX(viewWidth) // 对应slide_out_right的效果 .setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime)) .setListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) {} @Override public void onAnimationEnd(Animator animation) { getSupportFragmentManager().popBackStackImmediate(); } @Override public void onAnimationCancel(Animator animation) {} @Override public void onAnimationRepeat(Animator animation) {} }); }
这种方式能从根本上避免传统Animation的回调异常,而且代码更简洁,还支持硬件加速。
方案3:调整Fragment弹出的时序
如果暂时不想替换动画方式,可以把弹出操作延迟到动画完全结束后执行,避免时序冲突:
@Override public void onAnimationEnd(Animation animation) { fragmentToPopView.postDelayed(() -> { getSupportFragmentManager().popBackStackImmediate(); }, 50); // 短延迟确保动画状态稳定 }
总结
临时的isAnimating标志能解决当前页面的问题,但如果其他模块也有类似的动画+Fragment操作逻辑,很可能会出现同样的问题。推荐统一改用ViewPropertyAnimator,或者优化动画替换逻辑,这样能彻底适配SDK27的动画系统变化,避免后续出现更多隐藏问题。
内容的提问来源于stack exchange,提问作者Chris Gunawardena

