在重新启动其动画之前暂停 ObjectAnimator

huangapple go评论71阅读模式
英文:

Pausing an ObjectAnimator before it restarts its animation

问题

我正在使用ObjectAnimator来使TextView上的长文本在HorizontalScrollView内滚动。滚动动画效果很好,文本被正确滚动,但我在使动画在重新开始之前暂停几秒钟方面遇到了问题。

我正在使用以下类来创建这样的动画:

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);
    }
}

这些类在RecyclerView.Adapter中使用(因为这些滚动文本位于RecyclerView的项目中)并如下使用:

@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);
}

在使用onAnimationRepeat(Animator animation)时,我面临的问题是,动画重新启动,然后才暂停了一段时间以重新开始滚动。在重新开始滚动之前暂停动画的这种延迟是我设想中的一部分,但不是我想要实现的全部,因为我想要的是在动画完成后但在重新开始之前也暂停动画。

更明确地说,我的期望结果如下:

延迟 -> 开始动画 -> 完成滚动动画 -> 在结束时暂停 -> 带有延迟重新开始动画 -> ...

然而,到目前为止,我得到的是这样的:

延迟 -> 开始动画 -> 完成滚动动画 -> **没有暂停** -> 带有延迟重新开始动画 -> ...

是否有可能使用ObjectAnimator实现这样的效果?我之所以从Animation更改为ObjectAnimator,正是因为我想在重新开始之前暂停动画。

此外,我希望使用setRepeatCount(ValueAnimator.INFINITE)选项来解决问题,因为我希望确保即使用户忘记解锁手机,滚动也不会停止。

提前感谢您的所有帮助!

英文:

I am using an ObjectAnimator to make a long text on a TextView to be scrolled inside a HorizontalScrollView. The scroll animation is working nicely, the text is being scrolled correctly, but I am having trouble to make the animation stop for a few moments before it restarts.

I am using the following classes to make such animations:

 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);
}
}

Those classes are utilized in a RecyclerView.Adapter (cause those scrolling texts are located inside the items of a RecyclerView) and are utilized as follows:

@Override
public void onBindViewHolder(@NonNull ViewHolderInterface.MainListViewHolder holder, int position) {
\\------------
\\omitting code that does not relate to animations
\\------------
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);
}

where beforeDraw is just a method created to allow me to find the views' proper size on the screen to decide if the text should be scrolled or not.

Anyway, the problem I am facing is the fact that, when utilizing the onAnimationRepeat(Animator animation), the animation restarts, and just then it pauses for the time determined as delay to restart scrolling. This delay before resuming the animation is part of what I envisioned, but it isn't everything that I want to achieve, since what I wanted is that the animation also paused after its completion but before its restart.

To be more clear, my desired outcome would be as follows:

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

However, what I am getting so far would be like this:

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

Is it possible to achieve such thing with ObjectAnimator? I changed from Animation to ObjectAnimator precisely because I wanted to pause the animation before restarting it.

Also, I would prefer to have a solution that uses the setRepeatCount(ValueAnimator.INFINITE) option since I want to make sure it will never stop scrolling even if the user does forget his phone unlocked.

Thanks in advance for all the help!

答案1

得分: 1

你可以使用 AnimationUpdateListener,并在重复之前控制动画在结束时何时暂停,使用以下代码:

animator.addUpdateListener(animation -> {
    // 目标值可以是结束值,即 viewWidth - (itemWidth + 50),或者在您希望暂停时的其他值
    if (animation.getAnimatedValue() == targetValue) {
        animation.pause();
        new Handler().postDelayed(animation::resume, delayMillis);
    }
});
英文:

You could use AnimationUpdateListener and control when to pause the animation at the end before repeating using

animator.addUpdateListener(animation -> {
//target value could the end value which is either viewWidth-(itemWidth + 50) or when you want to pause
(animation.getAnimatedValue() == targetValue) {
animation.pause();
new Handler().postDelayed(animation::resume, delayMillis);
}
});

答案2

得分: 0

我必须感谢Rajan Kali和5f3bde39-70a2-4df1-afa2-47f61b的回答,因为他们帮助我找到了解决方案。这可能不是最优雅的解决方案,我愿意寻找更好的方法,但这个方法对我有效:

class ObjectScroller {
ObjectAnimator animator;
TextView scrollText;
int viewWidth;
int itemWidth;
long duration = 7500;
long delay = 2500;
public ObjectScroller(TextView scroll, int itemWidth, int viewWidth) {
this.scrollText = scroll;
this.itemWidth = itemWidth;
this.viewWidth = viewWidth;
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));
//这允许在重新启动之前暂停动画。
// 由于我的TextView被平移到viewWidth-(itemWidth + 50)个单位,
// 我选择在其平移小于viewWidth-(itemWidth + 49)时暂停
// (因为我的文本是从右向左平移的,这意味着
// 运动将创建一个负值)
// 因为正如指出的那样,它永远不会达到viewWidth-(itemWidth + 50),
// 而且LinearInterpolator不一定以整数速率增加。
animator.addUpdateListener(animation -> {
float value = (float) animation.getAnimatedValue();
if (value < viewWidth-(itemWidth + 49)) {
animation.pause();
new Handler().postDelayed(animation::resume, 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);
}
}
英文:

I must thank both Rajan Kali and 5f3bde39-70a2-4df1-afa2-47f61b answers to being able to find a solution. It might not be the most elegant one and I am willing to look for even better ones, but this one worked for me:

class ObjectScroller {
ObjectAnimator animator;
TextView scrollText;
int viewWidth;
int itemWidth;
long duration = 7500;
long delay = 2500;
public ObjectScroller(TextView scroll, int itemWidth, int viewWidth) {
this.scrollText = scroll;
this.itemWidth = itemWidth;
this.viewWidth = viewWidth;
animator = ObjectAnimator.ofFloat(scrollText, 
&quot;translationX&quot;, 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));
//This allows the animation to be paused BEFORE it restarts.
// Since my textview is translated viewWidth-(itemWidth + 50) units, 
// I choose to pause when its translation is lower than viewWidth-(itemWidth + 49)
// (since my text is being translated from right to left, which means
// the movement will create a negative value)
// because, as pointed out, it will NEVER reach viewWidth-(itemWidth + 50) AND
// the LinearInterpolator does not necessarily increments in integer rates.
animator.addUpdateListener(animation -&gt; {
float value = (float) animation.getAnimatedValue();
if (value &lt; viewWidth-(itemWidth + 49)) {
animation.pause();
new Handler().postDelayed(animation::resume, 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) {
}
// This code makes the animation to be paused after it has restarted,
// allowing the user to read the beginning of the text more easily
@Override
public void onAnimationRepeat(Animator animation) {
animation.pause();
new Handler().postDelayed(animation::resume, delayMillis);
}
}

huangapple
  • 本文由 发表于 2023年6月16日 02:41:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/76484622.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定