15

We want a Meter animation in TextView

enter image description here

To make it a little more interesting, I want each digit come from top to bottom or bottom to top ?

Right now I using listview for achieving this, I have also tried with TextSwitcher but its have a limitation of two child only.

I'm using getListView().smoothScrollToPosition(0...3...6...6...n);

Is there a simple way of doing this? because right now , we need to maintain 3 ListView and Adapter as well for maintaining this.

Please refer link to more understand this question

Display StopWatch Timer animated like the petrol pump meter using NSTimer

Lavekush Agrawal
  • 6,040
  • 7
  • 52
  • 85
  • 1
    Using 3 `ListView`s is not a good idea. Maybe its better to use 3 `TextView`s (one for each digit) then animate them depending on the counter value. – Rami Jan 29 '16 at 15:17
  • Yes i was also thinking the same , but I caught a various of problem during animation, – Lavekush Agrawal Jan 30 '16 at 05:05

4 Answers4

38

ListView's might be good enough solution, but I've implemented it with a custom View (FrameLayout), which contains inside 2 TextViews, which are animating based on the value changes:

enter image description here

The idea of code is very basic:

  • You pass to setValue desired value;
  • If it's bigger than current one - start animation from from bottom to top (and vice versa) to increment/decrement current value by 1. Here, we animating two TextViews to replace each other;
  • In AnimationEnd listener, check if we reached desired value - if not - do one more run (recursively);

        public class DigitTextView extends FrameLayout {
    
            private static int ANIMATION_DURATION = 250;
            TextView currentTextView, nextTextView;
    
            public DigitTextView(Context context, AttributeSet attrs) {
                super(context, attrs);
                init(context);
            }
    
            public DigitTextView(Context context) {
                super(context);
                init(context);
            }
    
            private void init(Context context) {
                LayoutInflater.from(context).inflate(R.layout.digit_text_view, this);
                currentTextView = (TextView) getRootView().findViewById(R.id.currentTextView);
                nextTextView = (TextView) getRootView().findViewById(R.id.nextTextView);
    
                nextTextView.setTranslationY(getHeight());
    
                setValue(0);
            }
    
            public void setValue(final int desiredValue) {
                if (currentTextView.getText() == null || currentTextView.getText().length() == 0) {
                    currentTextView.setText(String.format(Locale.getDefault(), "%d", desiredValue));
                }
    
                final int oldValue = Integer.parseInt(currentTextView.getText().toString());
    
                if (oldValue > desiredValue) {
                    nextTextView.setText(String.format(Locale.getDefault(), "%d", oldValue-1));
    
                    currentTextView.animate().translationY(-getHeight()).setDuration(ANIMATION_DURATION).start();
                    nextTextView.setTranslationY(nextTextView.getHeight());
                    nextTextView.animate().translationY(0).setDuration(ANIMATION_DURATION).setListener(new Animator.AnimatorListener() {
                        @Override
                        public void onAnimationStart(Animator animation) {}
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            currentTextView.setText(String.format(Locale.getDefault(), "%d", oldValue - 1));
                            currentTextView.setTranslationY(0);
                            if (oldValue - 1 != desiredValue) {
                                setValue(desiredValue);
                            }
                        }
                        @Override
                        public void onAnimationCancel(Animator animation) {}
                        @Override
                        public void onAnimationRepeat(Animator animation) {}
                    }).start();
                } else if (oldValue < desiredValue) {
                    nextTextView.setText(String.format(Locale.getDefault(), "%d", oldValue+1));
    
                    currentTextView.animate().translationY(getHeight()).setDuration(ANIMATION_DURATION).start();
                    nextTextView.setTranslationY(-nextTextView.getHeight());
                    nextTextView.animate().translationY(0).setDuration(ANIMATION_DURATION).setListener(new Animator.AnimatorListener() {
                        @Override
                        public void onAnimationStart(Animator animation) {}
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            currentTextView.setText(String.format(Locale.getDefault(), "%d", oldValue + 1));
                            currentTextView.setTranslationY(0);
                            if (oldValue + 1 != desiredValue) {
                                setValue(desiredValue);
                            }
                        }
                        @Override
                        public void onAnimationCancel(Animator animation) {}
                        @Override
                        public void onAnimationRepeat(Animator animation) {}
                    }).start();
                }
            }
        }
    

And it's XML:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="48dp"
    android:layout_height="56dp"
    android:padding="8dp"
    android:background="@drawable/rounded_blue_rect">
    <TextView
        android:id="@+id/currentTextView"
        android:textColor="#FFFFFF"
        android:textSize="18sp"
        android:gravity="center"
        android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/nextTextView"
        android:textColor="#FFFFFF"
        android:textSize="18sp"
        android:layout_gravity="center"
        android:gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</FrameLayout>

And it's very easy to use:

Add to layout:

<klogi.com.myapplication.DigitTextView
    android:id="@+id/digitTextView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

And set Value in code:

DigitTextView digitTextView = (DigitTextView) findViewById(R.id.digitTextView);
digitTextView.setValue(5);

Upd:
Another option to use, from what I see, is to set up a bit customized NumberPicker

I hope, it helps!

Konstantin Loginov
  • 15,802
  • 5
  • 58
  • 95
15

Ever since Robinhood won the Material design awards they have open sourced there custom TextView just like you are describing.

Check out Robinhood's Ticker library

enter image description here

timr
  • 6,668
  • 7
  • 47
  • 79
  • 1
    Sorry but if there are too many characters, more than could be shown in the container's width, then the whole characters are not shown... It's a bug... And it makes it unusable in production. Don't recommend this plug-in anymore... – JarsOfJam-Scheduler Mar 15 '20 at 15:15
3

This code performs the same animation where number rolldown from top to bottom.

Rolling-TextView-Animation

enter image description here

Kavita Patil
  • 1,784
  • 1
  • 17
  • 30
2

You can also use a handler to get the desired effect. Using this, you won't have to make any custom views. Create a function handleTextView which takes in initialValue, finalValue and targetTextview as arguments. The method is-

private void handleTextView(int initialValue, int finalValue, final TextView targetTextview) {
    DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator(1f);
    final int newInitialValue = Math.min(initialValue, finalValue);
    final int newFinalValue = Math.max(initialValue, finalValue);
    final int difference = Math.abs(finalValue - initialValue);
    Handler handler = new Handler();
    for (int count = newInitialValue; count <= newFinalValue; count++) {
      //Time to display the current value to the user.
      int time = Math.round(decelerateInterpolator.getInterpolation((((float) count) / difference)) * 100) * count;
      final int finalCount = ((initialValue > finalValue) ? initialValue - count : count);
      handler.postDelayed(new Runnable() {
        @Override
        public void run() {
          targetTextview.setText(finalCount.toString());
        }
      }, time);
    }
  }

UPDATE: Option 2- You can use a value animator as well-

private void handleTextView(int initialValue, int finalValue, final TextView  textview) {

    ValueAnimator valueAnimator = ValueAnimator.ofInt(initialValue, finalValue);
    valueAnimator.setDuration(1500);

    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {

            textview.setText(valueAnimator.getAnimatedValue().toString());

        }
    });
    valueAnimator.start();

}

By using this method we do not need to do any math.

Vaibhav Sharma
  • 2,293
  • 1
  • 17
  • 24