skip to Main Content

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!

2

Answers


  1. Chosen as BEST ANSWER

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

  2. 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);
        }
    });
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search