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

如何实现ObjectAnimator完成滚动动画后暂停再重启?

解决ObjectAnimator实现文本滚动后暂停再循环的问题

使用ObjectAnimator实现HorizontalScrollView内TextView长文本滚动,动画运行正常,但无法实现动画完成后暂停片刻再重启的需求。当前代码设置了setRepeatCount(ValueAnimator.INFINITE),但实际流程是动画结束后直接重启,没有预期的暂停环节。

预期流程:

Delay -> Start Animation -> Complete Scroll Animation -> Pause at the End -> Restart Animation with Delay -> ...

实际流程:

Delay -> Start Animation -> Complete Scroll Animation -> No Pause -> Restart Animation with Delay -> ...


原实现代码

class ObjectScroller {
    ObjectAnimator animator;
    TextView scrollText;
    long duration = 7500;
    long delay = 2500;

    public ObjectScroller(TextView scroll, int itemWidth, int viewWidth) {
        this.scrollText = scroll;
        animator = ObjectAnimator.ofFloat(scrollText, "translationX", viewWidth-(itemWidth + 50));
        animator.setStartDelay(delay);
        animator.setInterpolator(new LinearInterpolator());
        animator.setDuration(duration);
        animator.setRepeatMode(ValueAnimator.RESTART);
        animator.setRepeatCount(ValueAnimator.INFINITE);
    }

    public void startObjectScroll() {
        animator.addListener(new DelayAnimation(delay));
        animator.start();
    }
}

class DelayAnimation implements Animator.AnimatorListener {
    private long delayMillis;

    public DelayAnimation(long delayMillis) {
        this.delayMillis = delayMillis;
    }

    @Override
    public void onAnimationStart(Animator animation) {}

    @Override
    public void onAnimationEnd(Animator animation) {}

    @Override
    public void onAnimationCancel(Animator animation) {}

    @Override
    public void onAnimationRepeat(Animator animation) {
        animation.pause();
        new Handler().postDelayed(animation::resume, delayMillis);
    }
}

Adapter中的使用代码

@Override
public void onBindViewHolder(@NonNull ViewHolderInterface.MainListViewHolder holder, int position) {
    // 省略无关代码
    this.beforeDraw(holder.textInfoOne, () -> {
        Paint textPaint = holder.textInfoOne.getPaint();
        String text = holder.textInfoOne.getText().toString();
        int itemWidth = Math.round(textPaint.measureText(text));
        int viewWidth = holder.scrollText.getWidth();

        if (itemWidth > viewWidth) {
            holder.obScroller = new ObjectScroller(holder.textInfoOne, itemWidth,viewWidth);
            holder.obScroller.startObjectScroll();
        }
    });
}

public void beforeDraw(View view, Runnable runnable) {
    ViewTreeObserver.OnPreDrawListener preDraw = new ViewTreeObserver.OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            view.getViewTreeObserver().removeOnPreDrawListener(this);
            runnable.run();
            return true;
        }
    };
    view.getViewTreeObserver().addOnPreDrawListener(preDraw);
}

问题原因

原方案依赖系统的setRepeatCount(INFINITE)和onAnimationRepeat回调,但onAnimationRepeat的触发时机是动画即将开始重复的时候,此时系统已经完成了动画的重置准备,调用pause()和resume()无法实现“动画结束后暂停”的效果,只会让重复后的动画延迟启动,而非结束后暂停。


解决方案

放弃系统的自动重复机制,改为在动画结束后手动延迟重启,同时处理RecyclerView复用场景的动画清理:

  1. 修改ObjectScroller类,移除系统重复配置,在onAnimationEnd中实现延迟重启
  2. 在RecyclerView的onViewRecycled中停止动画,避免复用ViewHolder时出现动画混乱

调整后的ObjectScroller代码

import android.os.Handler;
import android.os.Looper;
import android.view.animation.LinearInterpolator;
import android.widget.TextView;
import androidx.animation.ObjectAnimator;
import androidx.animation.Animator;

class ObjectScroller {
    private TextView scrollText;
    private final long duration = 7500;
    private final long delay = 2500;
    private final float targetTranslationX;
    private Handler handler;

    public ObjectScroller(TextView scroll, int itemWidth, int viewWidth) {
        this.scrollText = scroll;
        this.targetTranslationX = viewWidth - (itemWidth + 50);
        this.handler = new Handler(Looper.getMainLooper());
    }

    private ObjectAnimator createAnimator() {
        ObjectAnimator anim = ObjectAnimator.ofFloat(scrollText, "translationX", 0f, targetTranslationX);
        anim.setInterpolator(new LinearInterpolator());
        anim.setDuration(duration);
        anim.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {}

            @Override
            public void onAnimationEnd(Animator animation) {
                // 动画结束后,延迟指定时间再重启
                handler.postDelayed(() -> {
                    // 先重置translationX到初始位置
                    scrollText.setTranslationX(0f);
                    createAnimator().start();
                }, delay);
            }

            @Override
            public void onAnimationCancel(Animator animation) {}

            @Override
            public void onAnimationRepeat(Animator animation) {}
        });
        return anim;
    }

    public void startObjectScroll() {
        // 第一次启动添加初始延迟
        handler.postDelayed(() -> createAnimator().start(), delay);
    }

    public void stopScroll() {
        if (handler != null) {
            handler.removeCallbacksAndMessages(null);
        }
        // 重置位置
        scrollText.setTranslationX(0f);
    }
}

修改后的Adapter代码

@Override
public void onBindViewHolder(@NonNull ViewHolderInterface.MainListViewHolder holder, int position) {
    // 先停止之前的动画(如果有)
    if (holder.obScroller != null) {
        holder.obScroller.stopScroll();
        holder.obScroller = null;
    }

    // 省略无关代码
    this.beforeDraw(holder.textInfoOne, () -> {
        Paint textPaint = holder.textInfoOne.getPaint();
        String text = holder.textInfoOne.getText().toString();
        int itemWidth = Math.round(textPaint.measureText(text));
        int viewWidth = holder.scrollText.getWidth();

        if (itemWidth > viewWidth) {
            holder.obScroller = new ObjectScroller(holder.textInfoOne, itemWidth, viewWidth);
            holder.obScroller.startObjectScroll();
        }
    });
}

// 添加ViewHolder回收时的动画清理
@Override
public void onViewRecycled(@NonNull ViewHolderInterface.MainListViewHolder holder) {
    super.onViewRecycled(holder);
    if (holder.obScroller != null) {
        holder.obScroller.stopScroll();
        holder.obScroller = null;
    }
}

方案说明

这个方案完全通过手动控制动画的结束与重启,实现了“动画完成后暂停再循环”的需求,同时保留了无限滚动的效果。另外添加了stopScroll方法和RecyclerView的回收处理,避免了ViewHolder复用导致的动画冲突和内存泄漏问题。

内容的提问来源于stack exchange,提问作者CorMatt

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 03:10:35