如何在后台线程运行平移动画等动画,避免UI卡顿?
解决动画启动延迟及视图更新卡顿问题
问题根源
- 旧补间动画的局限性:你使用的
TranslateAnimation是补间动画,仅修改视图的视觉呈现,不会改变视图的真实布局位置,后续视图操作需要重新计算布局,额外增加主线程负担。 - 主线程阻塞:动画结束后在
onAnimationEnd中直接执行stacksView.addViews()这类视图创建/删除操作,这类耗时操作会阻塞主线程,引发UI卡顿。 - 动画前计算阻塞:
getYPosition、indexOf这类计算逻辑如果数据量大,会占用主线程时间,导致动画启动出现延迟。
优化方案
- 改用属性动画(ObjectAnimator):直接修改视图的
x、y属性,动画结束后视图位置真实更新,避免后续布局调整的额外开销。 - 非UI计算逻辑移至后台线程:提前计算动画所需的目标位置,主线程仅负责启动动画,确保动画立即执行无延迟。
- 延迟视图操作至动画完全结束后,且将视图操作的准备工作移到后台,主线程仅做最终的UI更新。
修改后的代码
public void stackToStack(ArrayList<Card> subString, int targetX, boolean fromUndo) { System.out.println("Size ==> " + subString.size()); final Stacks targetStack = mainStacks.get(targetX); // 后台线程计算动画所需位置数据,避免阻塞主线程 new Thread(() -> { ArrayList<Float> getYPositions = getYPosition(targetStack, false); final float targetBaseX = targetX * cardWidth; // 切换到主线程启动动画 runOnUiThread(() -> { if (!fromUndo) { Music.move(); } AnimatorSet animatorSet = new AnimatorSet(); ArrayList<Animator> animators = new ArrayList<>(); for (Card card : subString) { CardView cardView = mainRelative.findViewWithTag(card.getCard()); if (cardView == null) continue; cardView.setZ(1); int index = targetStack.indexOf(card, -1, 1); float targetY = getYPositions.get(index); // 使用属性动画直接修改视图真实位置 ObjectAnimator animX = ObjectAnimator.ofFloat(cardView, "x", cardView.getX(), targetBaseX); ObjectAnimator animY = ObjectAnimator.ofFloat(cardView, "y", cardView.getY(), targetY); animX.setDuration(animationSpeed); animY.setDuration(animationSpeed); animators.add(animX); animators.add(animY); } // 监听所有动画结束,统一处理后续视图操作 animatorSet.playTogether(animators); animatorSet.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) {} @Override public void onAnimationEnd(Animator animation) { // 后台做视图操作的准备工作,主线程仅执行UI更新 new Thread(() -> { StacksView stacksView = new StacksView(); // 若addViews包含大量计算逻辑,移至此处执行 runOnUiThread(() -> { stacksView.addViews(); isNewClick = true; }); }).start(); } @Override public void onAnimationCancel(Animator animation) {} @Override public void onAnimationRepeat(Animator animation) {} }); animatorSet.start(); }); }).start(); }
额外优化建议
- 如果
getYPosition方法涉及大量循环或数据处理,建议缓存计算结果,避免重复计算消耗资源。 - 若
StacksView创建操作频繁,可考虑使用对象池复用实例,减少内存开销和初始化时间。 - 可用
ViewPropertyAnimator简化属性动画代码,写法更简洁:cardView.animate() .x(targetBaseX) .y(targetY) .setDuration(animationSpeed) .setListener(...);
内容的提问来源于stack exchange,提问作者Human
相关产品推荐
相关产品推荐

