Animate the text of a textView in order to be displayed progressively?

Asked

Viewed 1,946 times

4

I would like the text of the textView 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 dialogue texts from GBA Pokémon games SEE FROM 1:33 AM 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 writing time of the text, so that if the limit is exceeded, the rest of the text is 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:

I tried the one suggested by @Mr_anderson, but I was unsuccessful.

Several lines presented errors:

inserir a descrição da imagem aqui

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>

  • You can use this answer as the source: http://stackoverflow.com/questions/6700374/android-character-by-character-display-text-animation

3 answers

3

Resorting to Property Animation system(Properties animation system) of the SDK is possible to achieve the effect you want.

This approach allows having to "free" all the functionalities of the animation system as pause/resume, re-verse, repeat, Nursery and update Listener.

Write a wrapper about a Valueanimator together with a Timeinterpolator and two Typeevaluator.

Timeinterpolator is used to calculate the number of letters or number of words the text should have at a given time in the animation.

The Valueanimator uses the value calculated by the Timeinterpolator to determine the part of the text that should be displayed at this 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 offers two Factory methods:

  • TextViewAnimator.perLetter().
    Returns a Textviewanimator that animates the text, previously assigned to Textview, letter by letter.
  • TextViewAnimator.perWord().
    Returns a Textviewanimator that animates the text, previously assigned to 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 letter-by-letter animation:

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) must have a Button and a Textview with id’s "@+id/button"and "@+id/text1" respectively.

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

  • 1

    Forgot to comment on the minimum version of the SDK, in which this class will work.

  • Look what I found: https://developer.android.com/reference/android/animation/ValueAnimator.html#pause() &#Xa ps.: I am only helping in the quality of the answer.

  • 1

    @Acklay This behavior was purposely introduced in the answer example to demonstrate one of the various possibilities of animation. It is obtained by the lines textViewAnimator.setRepeatCount(2); and textViewAnimator.setRepeatMode(ValueAnimator.REVERSE);. Delete them so the text is written only once. I didn’t understand what you want with the reference to pause().

  • @Acklay I found out why you mentioned onPause(). You could have told me right away that you needed API19 :)

  • 1

    Um, I sent you the link focusing exactly on the method pause(). I thought you would look in the right corner. Sorry! xD

2

Although Ramaral has given 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 the 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 looked TextAnimatedView:

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

A GIF is worth more than a thousand images.

inserir a descrição da imagem aqui inserir a descrição da imagem aqui

  • This is identical to Mr_anderson’s answer.

  • 1

    @ramaral when you have supplementation of some other response, do you edit the user response or do you create a new one? I focused on the part that the AP asks "[...]not necessarily one character at a time as shown in the video, but one word at a time until the end of the text. [...]". In my opinion, I say in my opinion, your answer does not meet this requirement of the question.

  • Your answer is valid. I didn’t mean I shouldn’t answer, it was just an observation. The example he gives is letter by letter, just states that it doesn’t need to be so, that it can be word by word.

  • 1

    @important extension is to help, learning. =)

2


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 use 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");
  • in the xml of mainActivity?

  • In your code, you imported the wrong handler. Use the correct import: import android.os.Handler; When using the widget in xml activity_main, just type <Typewriter and give a enter that Android studio will auto-complete with the package in which you created your Typewriter class, which is genesysgeneration.pokemaos

  • I edited the response xml...

  • Whisper, now the following line continues to accuse error public class TypeWriter extends TextView { more specifically extends Textview.

  • Well, even accusing this error, runs all good, but I’m not able to make my textView display this Animated text, besides everything else that has in Activity some!!!

  • Use larger text... Maybe the animation of a text too short will end too fast and does not give the impression of being "keyboard" too slowly.

  • nn, the impression that this is being typed happens, the problem is that all the rest of Activity goes away

  • Can you post your Activity’s xml? Have you made sure to adjust the layout_width and layout_height according to your needs? Made sure they are not both match_parent?

  • All right, only problem remains being only the error that is accused in the following line: public class Typewriter extends TextView {, anyway it does not prevent the application from being executed correctly

  • Right. Here also occurs. You can give alt+enter that Android studio will ask you to extend Appcompattextview instead of a Textview. From what I read, he asks for this so that his textView, if run on low Apis, supports methods that would only work on newer Apis...

  • Hey and how to do to perform an action once it is finished?

Show 6 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.