英文:
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,
"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));
//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 -> {
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) {
}
// 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);
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论