Animate the text of a textView so that it is displayed progressively?

I would like the textView text present in My activity not to be displayed all at once, but gradually, something like a Power Point transition.

An example of what I want to do exactly would be GBA pokémon game dialogue texts see from 1:33 not necessarily one character at a time as displayed in the video, but one word at a time until the end of the text.

I would like to know if you can limit the total time of writing the text, so that if the imposed limit is exceeded, the rest of the text is all written instantly ( for very long texts ).

Activity:

package genesysgeneration.font;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(android.R.layout.activity_list_item);

        tv=(TextView)findViewById(R.id.tv);
        tv.setText("Lorem Ipsum é simplesmente uma simulação de texto da indústria tipográfica e de impressos, e vem sendo utilizado desde o século XVI, quando um impressor desconhecido pegou uma bandeja de tipos e os embaralhou para fazer um livro de modelos de tipos. Lorem Ipsum sobreviveu não só a cinco séculos, como também ao salto para a editoração eletrônica, permanecendo essencialmente inalterado. Se popularizou na década de 60, quando a Letraset lançou decalques contendo passagens de Lorem Ipsum, e mais recentemente quando passou a ser integrado a softwares de editoração eletrônica como Aldus PageMaker.");

    }
}

Xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>

Edit:

Tried the one suggested by @mr_anderson, but was unsuccessful.

Several rows presented errors:

insert the description of the image here

MainActivity:

package genesysgeneration.pokemaos;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TypeWriter t = (TypeWriter)findViewById(R.id.meuTxt);
        t.setCharacterDelay(100);
        t.animateText("Olha só");

    }
}

Class:

package genesysgeneration.pokemaos;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.TextView;
import java.util.logging.Handler;

public class TypeWriter extends TextView {

    private CharSequence mText;
    private int mIndex;
    private long mDelay = 500;

    public TypeWriter(Context context){

        super(context);

    }

    public TypeWriter(Context context, AttributeSet attrs){

        super(context, attrs);

    }

    private Handler mHandler = new Handler();
    private Runnable characterAdder = new Runnable() {
        @Override
        public void run() {
            setText(mText.subSequence(0, mIndex++));
            if (mIndex<=mText.length()){

                mHandler.postDelayed(characterAdder, mDelay);

            }
        }
    };

    public void animateText(CharSequence text){

        mText=text;
        mIndex=0;

        setText("");
        mHandler.removeCallbacks(characterAdder);
        mHandler.postDelayed(characterAdder, mDelay);

    }

    public void setCharacterDelay(long millis){

        mDelay=millis;

    }

}

Xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="genesysgeneration.pokemaos.MainActivity">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.Pokemaos.view.custom.TypeWriter

        android:id="@+id/meuTxt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</RelativeLayout>
Author: ramaral, 2017-03-20

3 answers

You will create a class that is a custom view

public class Typewriter extends TextView {

    private CharSequence mText;
    private int mIndex;
    private long mDelay = 500; //Default 500ms delay


    public Typewriter(Context context) {
        super(context);
    }

    public Typewriter(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    private Handler mHandler = new Handler();
    private Runnable characterAdder = new Runnable() {
        @Override
        public void run() {
            setText(mText.subSequence(0, mIndex++));
            if(mIndex <= mText.length()) {
                mHandler.postDelayed(characterAdder, mDelay);
            }
        }
    };

    public void animateText(CharSequence text) {
        mText = text;
        mIndex = 0;

        setText("");
        mHandler.removeCallbacks(characterAdder);
        mHandler.postDelayed(characterAdder, mDelay);
    }

    public void setCharacterDelay(long millis) {
        mDelay = millis;
    }
}

Then uses it in xml activity_main (edited)

<genesysgeneration.pokemaos.Typewriter
        android:id="@+id/meuTxt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

Then just instantiate in MainActivity

Typewriter t = (Typewriter) findViewById(R.id.meuTxt);
t.setCharacterDelay(100);
t.animateText("Olha só que legal");
 2
Author: Mr_Anderson, 2017-03-22 13:23:31

Using the property animation system SDK you can achieve the effect you want.

This approach allows you to have" free " all the features of the animation system like pause / resume, reverse, repeat, animator listener and update listener .

Write a wrapper over a ValueAnimator in conjunction with a TimeInterpolator and two TypeEvaluator.

The TimeInterpolator is used to calculate the number of letters or number of words that the text should have at a given moment in the animation.

Valueanimators use the value calculated by the TimeInterpolator to determine the part of the text that should be displayed at that point in the animation.

TextViewAnimator.java

public class TextViewAnimator {

    private TextValueAnimator textValueAnimator;

    public static TextViewAnimator perLetter(TextView textView){

        int steps = textView.getText().length();
        TextViewAnimator textViewAnimator =
                new TextViewAnimator(textView,
                                     new TextEvaluatorPerLetter(),
                                     new TextInterpolator(steps));
        return textViewAnimator;
    }

    public static TextViewAnimator perWord(TextView textView){

        int steps = textView.getText().toString().split(" ").length;

        TextViewAnimator textViewAnimator =
                new TextViewAnimator(textView,
                                     new TextEvaluatorPerWord(),
                                     new TextInterpolator(steps));
        return textViewAnimator;
    }

    public TextViewAnimator(TextView textView,
                            TypeEvaluator typeEvaluator,
                            TextInterpolator textInterpolator){

        this.textValueAnimator = new TextValueAnimator(textView, textView.getText().toString());
        textValueAnimator.setEvaluator(typeEvaluator);
        textValueAnimator.setInterpolator(textInterpolator);
    }

    private static class TextValueAnimator extends ValueAnimator implements ValueAnimator.AnimatorUpdateListener {

        private WeakReference<TextView> weakTextView;

        public TextValueAnimator(TextView textView, String text) {

            weakTextView = new WeakReference<>(textView);
            setObjectValues(text);
            addUpdateListener(this);
        }

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            String text = (String) animation.getAnimatedValue();
            TextView textView = weakTextView.get();
            if(textView != null) {
                textView.setText(text);
            }
        }
    }

    private static class TextEvaluatorPerLetter implements TypeEvaluator {

        @Override
        public Object evaluate(float fraction, Object startValue, Object endValue) {
            int step = (int) fraction;
            return ((String) endValue).substring(0, step);
        }
    }

    private static class TextEvaluatorPerWord implements TypeEvaluator {

        private String[] words;
        @Override
        public Object evaluate(float fraction, Object startValue, Object endValue) {

            int step = (int) fraction;
            if(words == null){
                words = ((String) endValue).split(" ");
            }
            String textAtStep = "";
            for (int i = 1; i <= step; i++) {
                textAtStep += words[i-1] + " ";
            }

            return textAtStep;
        }
    }

    private static class TextInterpolator implements TimeInterpolator {

        private int steps;
        public TextInterpolator(int steps) {

            this.steps = steps;
        }
        @Override
        public float getInterpolation(float input) {
            return input * steps;
        }
    }

    public void start(){
        textValueAnimator.start();
    }
    public void cancel(){
        textValueAnimator.cancel();
    }
    public void end(){
        textValueAnimator.end();
    }

    @RequiresApi(19)  
    public void pause(){
        textValueAnimator.pause();
    }
    @RequiresApi(19)
    public void resume(){
        textValueAnimator.resume();
    }
    @RequiresApi(19)
    public boolean isStarted(){
        return textValueAnimator.isStarted();
    }
    @RequiresApi(19)
    public float getAnimatedFraction(){
        return textValueAnimator.getAnimatedFraction();
    }
    public void setRepeatCount(int value){
        textValueAnimator.setRepeatCount(value);
    }
    public void setRepeatMode(int repeatMode){
        textValueAnimator.setRepeatMode(repeatMode);
    }
    public void setDuration(long duration){
        textValueAnimator.setDuration(duration);
    }
    public void setStartDelay(long startDelay){
        textValueAnimator.setStartDelay(startDelay);
    }
    public void addUpdateListener(ValueAnimator.AnimatorUpdateListener listener){
        textValueAnimator.addUpdateListener(listener);
    }
    public void removeUpdateListener(ValueAnimator.AnimatorUpdateListener listener){
        textValueAnimator.removeUpdateListener(listener);
    }
    public boolean isRunning(){
        return textValueAnimator.isRunning();
    }
    public void addListener(Animator.AnimatorListener listener){
        textValueAnimator.addListener(listener);
    }
    public void removeListener(Animator.AnimatorListener listener){
        textValueAnimator.removeListener(listener);
    }
}

The class provides two factory methods :

  • TextViewAnimator.perLetter().
    Returns a TextViewAnimator that animates the text, previously assigned to the TextView, letter by letter.
  • TextViewAnimator.perWord().
    Returns a TextViewAnimator that animates the text, previously assigned to the TextView, word by word.

The choice of a" wrapper "for the implementation, rather than inheritance, is due to the need to" hide " some of the public methods of the ValueAnimator class.

Example of using animation letter by letter:

public class MainActivity extends AppCompatActivity {

    TextView textView;
    Button button;
    TextViewAnimator textViewAnimator;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.text1);

        textViewAnimator = TextViewAnimator.perLetter(textView);
        textViewAnimator.setDuration(5000);
        textViewAnimator.setRepeatCount(2);
        textViewAnimator.setRepeatMode(ValueAnimator.REVERSE);

        button = (Button)findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                textViewAnimator.start();
            }
        });    
    }
}

The layout of Activity (activity_main.xml ) should have a button and a TextView with ID's "@+id/button" and "@+id/text1" respectively.

Notes:
- Requires minSdkVersion 11.
- Some methods require minSdkVersion 19.

 3
Author: ramaral, 2019-10-22 22:21:32

Although Ramaral gave a valid answer, I thought of another way in which it works for any version of Android.

Basically I use the Runnanble with a delay concatenating letter by letter or word by word.

  • animPerLetter(): letter by letter
  • animPerWord(): word for word

MainActivity:

TextAnimatedView textAnimatedView = (TextAnimatedView) findViewById(R.id.tv);
textAnimatedView.setCharacterDelay(150);
textAnimatedView.animPerWord("Desta forma vai funcionar como esperado");

In XML you use this way below:

<seu.pacote.TextAnimatedView
    android:id="@+id/tv"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

See below how the Class TextAnimatedView Looked:

public class TextAnimatedView extends TextView {

    private Handler handler = new Handler();
    private StringBuilder stringBuilder = new StringBuilder();
    private CharSequence text;
    private String[] arr;
    private int i;
    private long delay = 450;

    public TextAnimatedView(Context context) {
        super(context);
    }

    public TextAnimatedView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            setText(text.subSequence(0, i++));
            if (i <= text.length()) {
                handler.postDelayed(runnable, delay);
            }
        }
    };

    public void animPerLetter(CharSequence text) {
        this.text = text;
        i = 0;

        setText("");
        handler.removeCallbacks(runnable);
        handler.postDelayed(runnable, delay);
    }

    private Runnable runnablePerWord = new Runnable() {
        @Override
        public void run() {

            stringBuilder.append(arr[i++]).append(" ");
            setText(stringBuilder.toString());
            if (i < arr.length) {
                handler.postDelayed(runnablePerWord, delay);
            }
        }
    };

    public void animPerWord(CharSequence text) {
        this.text = text;
        i = 0;

        setText("");
        arr = text.toString().split(" ");
        handler.removeCallbacks(runnablePerWord);
        handler.postDelayed(runnablePerWord, delay);
    }

    public void setCharacterDelay(long delay) {
        this.delay = delay;
    }
}

Um GIF is worth a thousand images.

insert the description of the image here insert the description of the image here

 2
Author: viana, 2020-06-11 14:45:34