42

Anyone knows any efficient method of perform an animation that what is has to do is to display a text, character by character? Like:

T
Th
Thi
This
This i
This is
...

And so on.

Thanks!

Alexis Philip
  • 517
  • 2
  • 5
  • 23
zegnus
  • 1,437
  • 3
  • 17
  • 23

9 Answers9

115

This may not be the most elegant solution, but the simplest is probably a quick subclass of TextView with a Handler that updates the text every so often until the complete sequence is displayed:

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

You can then use this in an Activity like so:

public class MyActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Typewriter writer = new Typewriter(this);
        setContentView(writer);

        //Add a character every 150ms
        writer.setCharacterDelay(150);
        writer.animateText("Sample String");
    }
}

If you want some animation effects with each letter added, perhaps look at subclassing TextSwitcher instead.

starball
  • 20,030
  • 7
  • 43
  • 238
devunwired
  • 62,780
  • 12
  • 127
  • 139
  • 1
    Super greattttttt :-) how can I do if i want to to use layout a xml layout as a background and benefit from that animation? or how can i do add a special background to the writter?? – youssoua May 24 '12 at 13:21
  • youssoua, depending on which package your Typewriter class is in, you would use something like – ThePerson Mar 28 '14 at 10:31
  • Do i have to use writer in my setContentFunction() ?? – Meghal Agrawal Jul 06 '15 at 19:41
  • writer.setCharacterDelay(150); writer.animateText("Sample String"); // After using these two lines how i will print all this in my textView ? Please tell me – Meghal Agrawal Jul 06 '15 at 19:43
  • I extended @Devunwired's code to include animating typing, deleting, pausing and executing arbitrary runnables. See https://gist.github.com/claudijo/0cf9f43705efadfeb852 – Claudijo Nov 19 '15 at 09:38
  • How to add a onAnimationEndListener to it please? – Beeing Jk Jan 20 '16 at 11:27
  • 3
    when the word does not fit in the current line, it deletes it at rewrites it on the next line. Is a very annoying behaviour. Do you have a solution for this? – NullPointerException Feb 13 '17 at 21:56
  • 2
    android studio is asking me to extend `android.support.v7.widget.AppCompatTextView` instead. why is that ? – mrid Sep 23 '17 at 17:02
14

No need to set an extra class Use this, here tv is a textview in your layout just call

setCharacterDelay(150);
animateText("Sample String");

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

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

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

}

public void setCharacterDelay(long millis) {
    mDelay = millis;
}
Community
  • 1
  • 1
Viswanath Lekshmanan
  • 9,945
  • 1
  • 40
  • 64
4

this new copy for Devunwired with xml layout

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

code use

        textView = (Typewriter)findViewById(R.id.textView1);
    //Add a character every 150ms
    textView.setCharacterDelay(150);
    textView.animateText("Sample String");

then define textView in classStart

Bahaa Odeh
  • 583
  • 1
  • 7
  • 16
  • This worked for me, with help from K-RAD's answer. I was not clear on the `textView` variable in the **TyperWriter** class. In there, I replaced `textView` with `Typewriter.this` – Gene Bo Sep 06 '16 at 01:36
4

I used a recursive method, also added a bit delay in between words to have more human feel. Send the textView as view along with the text and send '1' as the length to type from start

  private fun typingAnimation(view: TextView, text: String, length: Int) {
    var delay = 100L
    if(Character.isWhitespace(text.elementAt(length-1))){
        delay = 600L
    }
    view.text = text.substring(0,length)
    when (length) {
        text.length -> return
        else -> Handler().postDelayed({
            typingAnimation(view, text, length+1 )
        }, delay)
    }
}
2

I know its too late now but someone still may arrive here from Google. Actually, I too needed something like this for my app, so made one myself. Try out Fade-In TextView, it makes every character appear with a smooth alpha animation. Usage is also quite simple.

In the XML layout

    <believe.cht.fadeintextview.TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="30sp"
        android:textColor="@android:color/black"

        app:letterDuration="250"/>

In the Activity/Fragment

believe.cht.fadeintextview.TextView textView = (believe.cht.fadeintextview.TextView) findViewById(R.id.textView);

textView.setLetterDuration(250); // sets letter duration programmatically
textView.isAnimating(); // returns current animation state (boolean)
textView.setText(); // sets the text with animation

Some more information

The Fade-In TextView library inherits its properties directly from the native TextView class, which means that all the native TextView methods are supported. There are practically no limitations including multiline support. The library also has some of its own methods and attributes which offer full control over the View.

cht
  • 319
  • 3
  • 13
0

Most of the solutions provided above throw various errors. I guess the solutions are old. I stumbled on this android studio plugin and it works like charm.

1.Installation of AutoTypeTextView is preety simple. Just add in build.gradle

compile 'com.krsticdragan:autotypetextview:1.1'

2.Add a new namespace which you will use for adding AutoTypeTextView and using its tags.

xmlns:attv="http://schemas.android.com/apk/res-auto"

Hence your root layout should look like this

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:attv="http://schemas.android.com/apk/res-auto"

  1. Add this to your xml file.

    <com.dragankrstic.autotypetextview.AutoTypeTextView android:id="@+id/lblTextWithoutMistakes" android:layout_width="wrap_content" android:layout_height="wrap_content" attv:animateTextTypeWithoutMistakes="Hello World!" />

With just these three steps you are good to go. You can check out the documentation here for more details

Seunope
  • 4,784
  • 2
  • 24
  • 30
0

Just to add to @Devunwired's answer when working with Kotlin code,
I changed (in animateText function):
mHandler.postDelayed(mRunnable,mDelay) to mRunnable.run()

so my final Kotlin class looks like this:

class TextViewAnimationComponent(context: Context,attributeSet: AttributeSet?) : TextView(context,attributeSet) {
    private var mHandler = Handler()
    private var mText:CharSequence = ""
    private var mIndex:Int = 0
    private var mDelay:Long = 500

    private val mRunnable = object: Runnable{
        override fun run() {
            text = mText.subSequence(0,mIndex++)
            if(mIndex <= mText.length){
                mHandler.postDelayed(this,mDelay)
            }
        }
    }

    fun animateText(mText:CharSequence){
        this.mText = mText
        mIndex = 0

        text = ""
        mHandler.removeCallbacks(mRunnable)
        mRunnable.run()
//        mHandler.postDelayed(mRunnable,mDelay)
    }

    fun setCharacterDelay(millis:Long){
        mDelay = millis
    }
}



Also, a quick and dirty code (still in Kotlin) without subclassing.
Inside Activity:

    private fun animateText(mText: CharSequence, delayMillis: Long, postFunction:() -> Unit){
        var mIndex = 0

        val runnable = object : Runnable {
            override fun run() {

                // RunRunnable is a boolean flag; used in case you want to interrupt the execution
                if(runRunnable) {
                    if (mIndex <= mText.length) {

                        // change textViewSwitchStateValue with your own TextView id
                        textViewSwitchStateValue.text = mText.subSequence(0, mIndex++)
                        Handler().postDelayed(this, delayMillis)
                    } else {

                        // After all the characters finished animating; Clear the TextView's text and then run the postFunction
                        textViewSwitchStateValue.text = ""
                        postFunction()
                    }
                }
            }
        }
        runOnUiThread(runnable)

A simple example for animating a loading dots:
animateText(". . .", 400){switchStateON()}

dongkichan
  • 1,071
  • 6
  • 4
0

Yep,I know it's been a while but I hope to help others with a different approach using ValueAnimator

val text = "This is your sentence"
val textLength = text.length-1
val textView = findViewById<TextView>(R.id.sampleText)

ValueAnimator.ofInt(0, textLength).apply {
     var _Index = -1
     interpolator = LinearInterpolator()
     duration = 2000
     addUpdateListener { valueAnimator ->
         val currentCharIndex = valueAnimator.animatedValue as Int
         if (_Index != currentCharIndex) {
             val currentChar = text[currentCharIndex]
             textView.text = textView.text.toString().plus(currentChar.toString())
         }
        _Index = currentCharIndex
     }
}.start()

Update

I think is more appropriate, rather solution above, of course if you are using RxJava

 Observable.range(0, textLength)
        .concatMap { Observable.just(it).delay(75, TimeUnit.MILLISECONDS) }
        .map { text[it].toString() }
        .subscribeOn(Schedulers.computation())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe { char ->
            println("Item: $char")
            textView.text = textView.text.toString().plus(char)
        }
Lucas Orso
  • 106
  • 5
  • It will not work in Recycler view. When you scroll your items and animation is not finished yet, it is broke new item without animation. – Oleg Skidan Apr 10 '20 at 10:42
0

In theory it would be


string text = "hello"
string temp = "h"

iterate: temp += if (text.length > temp.length) text[temp.length]; wait

You will of course do the iterate in your runmethod.

ngrashia
  • 9,869
  • 5
  • 43
  • 58
RobotRock
  • 4,211
  • 6
  • 46
  • 86
  • Blocking UI thread on which you update the text is not allowed in Android. You need some asynchronous solution for that. – Ilya Gazman Jun 04 '15 at 19:51