You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在后台线程运行平移动画等动画,避免UI卡顿?

解决动画启动延迟及视图更新卡顿问题

问题根源

  1. 旧补间动画的局限性:你使用的TranslateAnimation是补间动画,仅修改视图的视觉呈现,不会改变视图的真实布局位置,后续视图操作需要重新计算布局,额外增加主线程负担。
  2. 主线程阻塞:动画结束后在onAnimationEnd中直接执行stacksView.addViews()这类视图创建/删除操作,这类耗时操作会阻塞主线程,引发UI卡顿。
  3. 动画前计算阻塞: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 03:17:45