Android 如何在 textView 中链接 "exploding" 文本动画?

Android how to chain "exploding" text animations in textView?

我试图在 textView 中将 3 个 "exploding" 文本动画链接在一起,以按顺序显示 3 个单词:"Ready"、"Set" 和 "Go!"。 "exploding",我的意思是文本大小从默认的 0.25f 变为默认的 1.00f,而 alpha=0 到 alpha=1。

问题:我能够按预期获得第一个词 "Ready" 到 "explode" 但下一个词 "Set" 没有"explode" 即根本不会改变文本大小(只有动画的 alpha 部分有效)。

我的MainActivity.java如下。我没有放入第三个 "explosion" 因为如果我可以让第二个工作,那就是复制和粘贴的问题。

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    public void actionExplode(View view) {

        // .setTextSize() defaults to sp but .getTextSize() defaults to px 

        String textReady = "Ready";
        final String textSet = "Set";
        final String textGo =  "Go!";

        final TextView questionDisplay = (TextView) findViewById(R.id.textView);
        final float textSizePx = questionDisplay.getTextSize();
        Log.i("actionExplode", "textSizePx=" + textSizePx);

        final float scaleSmall = 0.25f;
        float scaleFull = 1.0f;
        final float fadeOut = 0f;
        float fadeIn = 1f;

        questionDisplay.setAlpha(fadeOut);
        questionDisplay.setText(textReady);
        questionDisplay.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePx * scaleSmall);
        Log.i("actionExplode", ".getTextSize()=" + questionDisplay.getTextSize());

        int animateDurationReadySetGo = 1000;
        int animateDurationFudge = 100;

        ObjectAnimator animateReadyFadeIn = ObjectAnimator.ofFloat(questionDisplay, "alpha", fadeOut, fadeIn);
        animateReadyFadeIn.setDuration(animateDurationReadySetGo);
        ObjectAnimator animateReadyX = ObjectAnimator.ofFloat(questionDisplay, "scaleX", scaleFull/scaleSmall);
        animateReadyX.setDuration(animateDurationReadySetGo);
        ObjectAnimator animateReadyY = ObjectAnimator.ofFloat(questionDisplay, "scaleY", scaleFull/scaleSmall);
        animateReadyY.setDuration(animateDurationReadySetGo /* + animateDurationFudge */ );
        animateReadyY.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                Log.i("onAnimationEnd", "before .getTextSize()=" + questionDisplay.getTextSize());
                //questionDisplay.setAlpha(fadeOut);
                questionDisplay.setText(textSet);
                questionDisplay.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePx * scaleSmall);
                Log.i("onAnimationEnd", "after .getTextSize()=" + questionDisplay.getTextSize());
            }
        });

        ObjectAnimator animateSetFadeIn = ObjectAnimator.ofFloat(questionDisplay, "alpha", fadeOut, fadeIn);
        animateSetFadeIn.setDuration(animateDurationReadySetGo);
        ObjectAnimator animateSetX = ObjectAnimator.ofFloat(questionDisplay, "scaleX", scaleFull/scaleSmall);
        animateSetX.setDuration(animateDurationReadySetGo);
        ObjectAnimator animateSetY = ObjectAnimator.ofFloat(questionDisplay, "scaleY", scaleFull/scaleSmall);
        animateSetY.setDuration(animateDurationReadySetGo /* + animateDurationFudge */ );
        animateSetY.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                Log.i("onAnimationEnd", "before .getTextSize()=" + questionDisplay.getTextSize());
                //questionDisplay.setAlpha(fadeOut);
                questionDisplay.setText(textGo);
                questionDisplay.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePx * scaleSmall);
                Log.i("onAnimationEnd", "after .getTextSize()=" + questionDisplay.getTextSize());
            }
        });

        AnimatorSet animateReadySetGo = new AnimatorSet();
        animateReadySetGo.playTogether(animateReadyX, animateReadyY, animateReadyFadeIn);
        animateReadySetGo.playTogether(animateSetX, animateSetY, animateSetFadeIn);
        animateReadySetGo.playSequentially(animateReadyY, animateSetY);
        animateReadySetGo.start();
    }

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

这是日志显示的内容。没有意义的是:1) 为什么 "before" 行显示 43.75 而它们应该显示 175.0?和 2) 为什么 "after" 行显示 43.75 但文本大小没有缩小?

I/actionExplode: textSizePx=175.0
I/actionExplode: .getTextSize()=43.75
I/onAnimationEnd: before .getTextSize()=43.75
I/onAnimationEnd: after .getTextSize()=43.75
I/onAnimationEnd: before .getTextSize()=43.75
I/onAnimationEnd: after .getTextSize()=43.75

我的activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    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"
    tools:context="com.plaudev.explodingtext.MainActivity">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:text="@string/textView"
        android:id="@+id/textView"
        android:fontFamily="casual"
        android:textSize="50sp"
        android:textStyle="normal|bold"
        android:textAlignment="center"
        android:gravity="center" />

    <Button
        android:text="@string/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textView"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="50dp"
        android:id="@+id/button"
        android:background="@color/colourTransparent"
        android:textAllCaps="false"
        android:textSize="25sp"
        android:textStyle="normal|bold"
        android:fontFamily="casual"
        android:onClick="actionExplode"
        android:textColor="@android:color/holo_green_dark" />
</RelativeLayout>

更新 1:仍未找到解决方案,但想记录此行为。如果我递归地将 actionExplode() 重写为 运行 并使用其他东西作为 onClick 来开始,那么我的 MainActivity class 如下所示,那么我可以获得 3 [=59 的链=] 但每个都是从一个连续较小的文本大小开始的,如下面的附加日志所示。似乎不知何故通过使插座 questionDisplay final 以便听众或计时器可以使用它来链接动画导致插座保留文本大小,即使我仍然可以更改其文本(即仅选择性地final)。因此,我也尝试了一些变体(如代码注释中所述),但与变体 A 相比,none 它们让我更接近预期的行为。

public class MainActivity extends AppCompatActivity {

    String[] explosionChain = {"Ready", "Set", "Go!"};
    int explosionIndex = 0;
    int animateDurationExplosion = 1000;
    int animateDurationFudge = 100;

    public void actionExplode(final float textSizeFullPx, final int explosionIndex) {

        final TextView questionDisplay = (TextView) findViewById(R.id.textView);
        float textSizePx = questionDisplay.getTextSize();
        Log.i("actionExplode", "textSizeFullPx=" + textSizeFullPx + ", explosionIndex=" + explosionIndex + ", textSizePx=" + textSizePx);

        float scaleSmall = 0.25f;
        float scaleFull = 1.0f;
        float fadeOut = 0f;
        float fadeIn = 1f;

        questionDisplay.setAlpha(fadeOut);
        questionDisplay.setText(explosionChain[explosionIndex]);
        // variation A
        //questionDisplay.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizeFullPx * scaleSmall);
        // variation B
        questionDisplay.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePx * scaleSmall);
        Log.i("actionExplode", ".getTextSize()=" + questionDisplay.getTextSize());

        ObjectAnimator animateFadeIn = ObjectAnimator.ofFloat(questionDisplay, "alpha", fadeOut, fadeIn);
        animateFadeIn.setDuration(animateDurationExplosion);
        ObjectAnimator animateScaleX = ObjectAnimator.ofFloat(questionDisplay, "scaleX", scaleFull/scaleSmall);
        animateScaleX.setDuration(animateDurationExplosion);
        ObjectAnimator animateScaleY = ObjectAnimator.ofFloat(questionDisplay, "scaleY", scaleFull/scaleSmall);
        animateScaleY.setDuration(animateDurationExplosion /* + animateDurationFudge */ );

        AnimatorSet animateExplosion = new AnimatorSet();
        animateExplosion.playTogether(animateScaleX, animateScaleY, animateFadeIn);
        animateExplosion.start();

        CountDownTimer explodeNext = new CountDownTimer(animateDurationExplosion, animateDurationExplosion) {
            @Override
            public void onTick(long millisUntilFinished) {
            }
            @Override
            public void onFinish() {
                // variation C
                Log.i("onFinish", ".getTextSize()=" + questionDisplay.getTextSize());
                // variation D
                //Log.i("onFinish", "before .getTextSize()=" + questionDisplay.getTextSize());
                //questionDisplay.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizeFullPx);
                //Log.i("onFinish", "after .getTextSize()=" + questionDisplay.getTextSize());
                if ((explosionIndex + 1) < explosionChain.length) {
                    actionExplode(textSizeFullPx, explosionIndex + 1);
                }
            }
        };
        explodeNext.start();
    }

    public void startChainExplosion(View view) {

        final TextView questionDisplay = (TextView) findViewById(R.id.textView);
        final float textSizeFullPx = questionDisplay.getTextSize();
        Log.i("startChainExplosion", "explosionIndex=" + explosionIndex + ", textSizeFullPx=" + textSizeFullPx);

        actionExplode(textSizeFullPx, explosionIndex);

    }

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

上面更新的代码产生了这个日志:

I/startChainExplosion: explosionIndex=0, textSizeFullPx=175.0
I/actionExplode: textSizeFullPx=175.0, explosionIndex=0, textSizePx=175.0
I/actionExplode: .getTextSize()=43.75
I/onFinish: .getTextSize()=43.75
I/actionExplode: textSizeFullPx=175.0, explosionIndex=1, textSizePx=43.75
I/actionExplode: .getTextSize()=10.9375
I/onFinish: .getTextSize()=10.9375
I/actionExplode: textSizeFullPx=175.0, explosionIndex=2, textSizePx=10.9375
I/actionExplode: .getTextSize()=2.734375
I/onFinish: .getTextSize()=2.734375

更新 2:按照@Xaver 的建议,尝试使用它作为 onClick。但结果与我最初的尝试相同,即 "Ready" 爆炸但 "Set" 和 "Go!" 没有。此外,在所有动画完成后,文本大小变得非常大(我猜是 175px * 4)。更新代码和日志如下。我有一种感觉,我需要每个单词都有自己的 textView 以避免此文本大小保留问题。

public void explodeSequentially(View view) {

        String textReady = "Ready";
        final String textSet = "Set";
        final String textGo =  "Go!";

        final TextView questionDisplay = (TextView) findViewById(R.id.textView);
        float textSizePx = questionDisplay.getTextSize();
        Log.i("explodeSequentially", "textSizePx=" + textSizePx);

        float scaleSmall = 0.25f;
        float scaleFull = 1.0f;
        float fadeOut = 0f;
        float fadeIn = 1f;

        questionDisplay.setAlpha(fadeOut);
        questionDisplay.setText(textReady);
        questionDisplay.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePx * scaleSmall);
        Log.i("explodeSequentially", ".getTextSize()=" + questionDisplay.getTextSize());

        ObjectAnimator animateReadyFadeIn = ObjectAnimator.ofFloat(questionDisplay, "alpha", fadeOut, fadeIn);
        animateReadyFadeIn.setDuration(animateDurationExplosion);
        ObjectAnimator animateReadyX = ObjectAnimator.ofFloat(questionDisplay, "scaleX", scaleFull/scaleSmall);
        animateReadyX.setDuration(animateDurationExplosion);
        ObjectAnimator animateReadyY = ObjectAnimator.ofFloat(questionDisplay, "scaleY", scaleFull/scaleSmall);
        animateReadyY.setDuration(animateDurationExplosion /* + animateDurationFudge */ );
        animateReadyY.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                Log.i("onAnimationEnd", "before .getTextSize()=" + questionDisplay.getTextSize());
                //questionDisplay.setAlpha(fadeOut);
                questionDisplay.setText(textSet);
                //questionDisplay.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePx * scaleSmall);
                //Log.i("onAnimationEnd", "after .getTextSize()=" + questionDisplay.getTextSize());
            }
        });
        AnimatorSet animateReady = new AnimatorSet();
        animateReady.playTogether(animateReadyX, animateReadyY, animateReadyFadeIn);

        ObjectAnimator animateSetFadeIn = ObjectAnimator.ofFloat(questionDisplay, "alpha", fadeOut, fadeIn);
        animateSetFadeIn.setDuration(animateDurationExplosion);
        ObjectAnimator animateSetX = ObjectAnimator.ofFloat(questionDisplay, "scaleX", scaleFull/scaleSmall);
        animateSetX.setDuration(animateDurationExplosion);
        ObjectAnimator animateSetY = ObjectAnimator.ofFloat(questionDisplay, "scaleY", scaleFull/scaleSmall);
        animateSetY.setDuration(animateDurationExplosion /* + animateDurationFudge */ );
        animateSetY.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                Log.i("onAnimationEnd", "before .getTextSize()=" + questionDisplay.getTextSize());
                //questionDisplay.setAlpha(fadeOut);
                questionDisplay.setText(textGo);
                //questionDisplay.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePx * scaleSmall);
                //Log.i("onAnimationEnd", "after .getTextSize()=" + questionDisplay.getTextSize());
            }
        });
        AnimatorSet animateSet = new AnimatorSet();
        animateSet.playTogether(animateSetX, animateSetY, animateSetFadeIn);

        ObjectAnimator animateGoFadeIn = ObjectAnimator.ofFloat(questionDisplay, "alpha", fadeOut, fadeIn);
        animateGoFadeIn.setDuration(animateDurationExplosion);
        ObjectAnimator animateGoX = ObjectAnimator.ofFloat(questionDisplay, "scaleX", scaleFull/scaleSmall);
        animateGoX.setDuration(animateDurationExplosion);
        ObjectAnimator animateGoY = ObjectAnimator.ofFloat(questionDisplay, "scaleY", scaleFull/scaleSmall);
        animateGoY.setDuration(animateDurationExplosion /* + animateDurationFudge */ );
        animateGoY.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                Log.i("onAnimationEnd", "before .getTextSize()=" + questionDisplay.getTextSize());
                //questionDisplay.setAlpha(fadeOut);
                questionDisplay.setText("Here is the question!");
                //questionDisplay.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePx * scaleSmall);
                //Log.i("onAnimationEnd", "after .getTextSize()=" + questionDisplay.getTextSize());
            }
        });
        AnimatorSet animateGo = new AnimatorSet();
        animateGo.playTogether(animateGoX, animateGoY, animateGoFadeIn);

        AnimatorSet animateReadySetGo = new AnimatorSet();
        animateReadySetGo.playSequentially(animateReady, animateSet, animateGo);
        animateReadySetGo.start();
    }

和日志

I/explodeSequentially: textSizePx=175.0
I/explodeSequentially: .getTextSize()=43.75
I/onAnimationEnd: before .getTextSize()=43.75
I/onAnimationEnd: before .getTextSize()=43.75
I/onAnimationEnd: before .getTextSize()=43.75

我的猜测是正确的。每当在插座上使用 final 时,textViews 上的文本大小都会徘徊,如果我想链接动画,这种使用似乎是不可避免的。然而,通过将我想要 "explode" 的每个单词拆分成它自己的 textView,至少我可以让 "explosion" 效果按预期顺序工作。

以下代码只会实现预期的动画序列一次,即下次单击按钮时,动画序列将从 43.75px 开始,而不是最初的 175.0px,并以每次 0.25f 因子继续减少进一步点击。我将修改代码以动态创建和销毁 textViews 以便稍后解决(现在在下面更新)。

新MainActivity.java:

public class MainActivity extends AppCompatActivity {

    public void explodeThreeTextViews (View view) {

        int animateDurationExplosion = 1000;
        float scaleSmall = 0.25f;
        float scaleFull = 1.0f;
        float fadeOut = 0f;
        final float fadeIn = 1f;

        final TextView tvQuestion = (TextView) findViewById(R.id.question);
        final TextView tvReady = (TextView) findViewById(R.id.ready);
        final TextView tvSet = (TextView) findViewById(R.id.set);
        final TextView tvGo = (TextView) findViewById(R.id.go);

        float tvReadySizePx = tvReady.getTextSize();
        float tvSetSizePx = tvSet.getTextSize();
        float tvGoSizePx = tvGo.getTextSize();
        Log.i("explodeThreeTextViews", "tvReadySizePx=" + tvReadySizePx + ", tvSetSizePx=" + tvSetSizePx + ", tvGoSizePx=" + tvGoSizePx);

        tvQuestion.setAlpha(fadeOut);
        tvReady.setAlpha(fadeOut);
        tvSet.setAlpha(fadeOut);
        tvGo.setAlpha(fadeOut);

        tvQuestion.setText("The question!");
        tvReady.setText("Ready");
        tvSet.setText("Set");
        tvGo.setText("Go!");

        tvQuestion.setVisibility(View.GONE);
        tvReady.setVisibility(View.VISIBLE);

        tvReady.setTextSize(TypedValue.COMPLEX_UNIT_PX, tvReadySizePx * scaleSmall);
        tvSet.setTextSize(TypedValue.COMPLEX_UNIT_PX, tvSetSizePx * scaleSmall);
        tvGo.setTextSize(TypedValue.COMPLEX_UNIT_PX, tvGoSizePx * scaleSmall);
        Log.i("explodeThreeTextViews", "tvReady.getTextSize=" + tvReady.getTextSize() + ", tvSet.getTextSize=" + tvSet.getTextSize() + ", tvGo.getTextSize=" + tvGo.getTextSize());

        ObjectAnimator animateReadyFadeIn = ObjectAnimator.ofFloat(tvReady, "alpha", fadeOut, fadeIn);
        animateReadyFadeIn.setDuration(animateDurationExplosion);
        ObjectAnimator animateReadyX = ObjectAnimator.ofFloat(tvReady, "scaleX", scaleFull/scaleSmall);
        animateReadyX.setDuration(animateDurationExplosion);
        ObjectAnimator animateReadyY = ObjectAnimator.ofFloat(tvReady, "scaleY", scaleFull/scaleSmall);
        animateReadyY.setDuration(animateDurationExplosion /* + animateDurationFudge */ );
        animateReadyY.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                Log.i("onAnimationEnd", "tvReady.getTextSize()=" + tvReady.getTextSize());
                //questionDisplay.setAlpha(fadeOut);
                tvReady.setVisibility(View.GONE);
                tvSet.setVisibility(View.VISIBLE);
                //questionDisplay.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePx * scaleSmall);
                //Log.i("onAnimationEnd", "after .getTextSize()=" + questionDisplay.getTextSize());
            }
        });
        AnimatorSet animateReady = new AnimatorSet();
        animateReady.playTogether(animateReadyX, animateReadyY, animateReadyFadeIn);

        ObjectAnimator animateSetFadeIn = ObjectAnimator.ofFloat(tvSet, "alpha", fadeOut, fadeIn);
        animateSetFadeIn.setDuration(animateDurationExplosion);
        ObjectAnimator animateSetX = ObjectAnimator.ofFloat(tvSet, "scaleX", scaleFull/scaleSmall);
        animateSetX.setDuration(animateDurationExplosion);
        ObjectAnimator animateSetY = ObjectAnimator.ofFloat(tvSet, "scaleY", scaleFull/scaleSmall);
        animateSetY.setDuration(animateDurationExplosion /* + animateDurationFudge */ );
        animateSetY.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                Log.i("onAnimationEnd", "tvSet.getTextSize()=" + tvSet.getTextSize());
                //questionDisplay.setAlpha(fadeOut);
                tvSet.setVisibility(View.GONE);
                tvGo.setVisibility(View.VISIBLE);
                //questionDisplay.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePx * scaleSmall);
                //Log.i("onAnimationEnd", "after .getTextSize()=" + questionDisplay.getTextSize());
            }
        });
        AnimatorSet animateSet = new AnimatorSet();
        animateSet.playTogether(animateSetX, animateSetY, animateSetFadeIn);

        ObjectAnimator animateGoFadeIn = ObjectAnimator.ofFloat(tvGo, "alpha", fadeOut, fadeIn);
        animateGoFadeIn.setDuration(animateDurationExplosion);
        ObjectAnimator animateGoX = ObjectAnimator.ofFloat(tvGo, "scaleX", scaleFull/scaleSmall);
        animateGoX.setDuration(animateDurationExplosion);
        ObjectAnimator animateGoY = ObjectAnimator.ofFloat(tvGo, "scaleY", scaleFull/scaleSmall);
        animateGoY.setDuration(animateDurationExplosion /* + animateDurationFudge */ );
        animateGoY.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                Log.i("onAnimationEnd", "tvGo.getTextSize()=" + tvGo.getTextSize());
                //questionDisplay.setAlpha(fadeOut);
                tvGo.setVisibility(View.GONE);
                tvQuestion.setVisibility(View.VISIBLE);
                tvQuestion.setAlpha(fadeIn);
                //questionDisplay.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePx * scaleSmall);
                //Log.i("onAnimationEnd", "after .getTextSize()=" + questionDisplay.getTextSize());
            }
        });
        AnimatorSet animateGo = new AnimatorSet();
        animateGo.playTogether(animateGoX, animateGoY, animateGoFadeIn);

        AnimatorSet animateReadySetGo = new AnimatorSet();
        animateReadySetGo.playSequentially(animateReady, animateSet, animateGo);
        animateReadySetGo.start();
    }

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

新activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    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"
    tools:context="com.plaudev.explodingtext.MainActivity">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:id="@+id/questionDisplay">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:text="@string/textView"
            android:id="@+id/question"
            android:fontFamily="casual"
            android:textSize="50sp"
            android:textStyle="normal|bold"
            android:textAlignment="center"
            android:gravity="center"
            android:layout_weight="1"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:text="@string/textView"
            android:id="@+id/ready"
            android:fontFamily="casual"
            android:textSize="50sp"
            android:textStyle="normal|bold"
            android:textAlignment="center"
            android:gravity="center"
            android:layout_weight="1"
            android:visibility="gone"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:text="@string/textView"
            android:id="@+id/set"
            android:fontFamily="casual"
            android:textSize="50sp"
            android:textStyle="normal|bold"
            android:textAlignment="center"
            android:gravity="center"
            android:layout_weight="1"
            android:visibility="gone"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:text="@string/textView"
            android:id="@+id/go"
            android:fontFamily="casual"
            android:textSize="50sp"
            android:textStyle="normal|bold"
            android:textAlignment="center"
            android:gravity="center"
            android:layout_weight="1"
            android:visibility="gone"/>

        <Button
            android:text="@string/button"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:id="@+id/button"
            android:background="@color/colourTransparent"
            android:textAllCaps="false"
            android:textSize="25sp"
            android:textStyle="normal|bold"
            android:fontFamily="casual"
            android:onClick="explodeThreeTextViews"
            android:textColor="@android:color/holo_green_dark"
            android:layout_weight="1" />

    </LinearLayout>

</RelativeLayout>

你运行这个时候的日志:

I/explodeThreeTextViews: tvReadySizePx=175.0, tvSetSizePx=175.0, tvGoSizePx=175.0
I/explodeThreeTextViews: tvReady.getTextSize=43.75, tvSet.getTextSize=43.75, tvGo.getTextSize=43.75
I/onAnimationEnd: tvReady.getTextSize()=43.75
I/onAnimationEnd: tvSet.getTextSize()=43.75
I/onAnimationEnd: tvGo.getTextSize()=43.75

更新:使用动态创建和销毁的视图以根据先前尝试的递归方法启用重复使用。

已更新 MainActivity.java - 您可以选择使用动画侦听器或 CountDownTimer 来链接 "explosions":

public class MainActivity extends AppCompatActivity {

    // constants
    String[] explosionChain = {"Ready", "Set", "Go!"};
    float scaleSmall = 0.25f;
    float scaleFull = 1.0f;
    float fadeOut = 0f;
    float fadeIn = 1f;
    int animateDurationExplosion = 1000;
    int animateDurationFudge = 100;

    // variables
    float textSizeFullPx;
    TextView[] explosionViews;
    int explosionIndex;

    public void prepareChainExplosions(View view) {

        //String typeface = "casual";

        LinearLayout questionDisplay = (LinearLayout) findViewById(R.id.questionDisplay);
        TextView questionView = (TextView) findViewById(R.id.questionView);
        textSizeFullPx = questionView.getTextSize();
        Log.i("prepareChainExplosions", "textSizeFullPx=" + textSizeFullPx);

        explosionViews = new TextView[explosionChain.length];
        for (int i = 0; i < explosionViews.length; i++) {
            explosionViews[i] = new TextView(questionDisplay.getContext());
            explosionViews[i].setVisibility(View.GONE);
            explosionViews[i].setAlpha(fadeOut);
            //explosionViews[i].setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT, 1f));
            explosionViews[i].setLayoutParams(questionView.getLayoutParams());
            //explosionViews[i].setGravity(Gravity.CENTER);
            explosionViews[i].setGravity(questionView.getGravity());
            //explosionViews[i].setTypeface(Typeface.create(typeface, Typeface.BOLD));
            explosionViews[i].setTypeface(questionView.getTypeface());
            explosionViews[i].setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizeFullPx);
            explosionViews[i].setText(explosionChain[i]);
            questionDisplay.addView(explosionViews[i]);
        }
        Log.i("prepareChainExplosions", "questionDisplay.getChildCount()=" + questionDisplay.getChildCount());
    }

public void actionExplode(final int explosionIndex) {

        final TextView questionView = (TextView) findViewById(R.id.questionView);
        final TextView explodingView = explosionViews[explosionIndex];
        float textSizePx = explodingView.getTextSize();
        Log.i("actionExplode", "explosionIndex=" + explosionIndex + ", textSizePx=" + textSizePx);

        explodingView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePx * scaleSmall);
        Log.i("actionExplode", ".getTextSize()=" + explodingView.getTextSize());

        if (explosionIndex == 0) questionView.setVisibility(View.GONE);
        explodingView.setVisibility(View.VISIBLE);

        ObjectAnimator animateFadeIn = ObjectAnimator.ofFloat(explodingView, "alpha", fadeOut, fadeIn);
        animateFadeIn.setDuration(animateDurationExplosion);
        ObjectAnimator animateScaleX = ObjectAnimator.ofFloat(explodingView, "scaleX", scaleFull/scaleSmall);
        animateScaleX.setDuration(animateDurationExplosion);
        ObjectAnimator animateScaleY = ObjectAnimator.ofFloat(explodingView, "scaleY", scaleFull/scaleSmall);
        animateScaleY.setDuration(animateDurationExplosion /* + animateDurationFudge */ );
        animateScaleY.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                Log.i("onAnimationEnd", ".getTextSize()=" + explodingView.getTextSize());
                explodingView.setVisibility(View.GONE);
                if ((explosionIndex + 1) < explosionChain.length) {
                    actionExplode(explosionIndex + 1);
                } else {
                    questionView.setVisibility(View.VISIBLE);
                }
            }
        });

        AnimatorSet animateExplosion = new AnimatorSet();
        animateExplosion.playTogether(animateScaleX, animateScaleY, animateFadeIn);
        animateExplosion.start();

        /*
        CountDownTimer explodeNext = new CountDownTimer(animateDurationExplosion, animateDurationExplosion) {
            @Override
            public void onTick(long millisUntilFinished) {
            }
            @Override
            public void onFinish() {
                Log.i("onFinish", ".getTextSize()=" + explodingView.getTextSize());
                explodingView.setVisibility(View.GONE);
                if ((explosionIndex + 1) < explosionChain.length) {
                    actionExplode(explosionIndex + 1);
                } else {
                    questionView.setVisibility(View.VISIBLE);
                }
            }
        };
        explodeNext.start();
        */
    }

    public void startChainExplosions(View view) {

        prepareChainExplosions(view);
        explosionIndex = 0;
        Log.i("startChainExplosions", "explosionIndex=" + explosionIndex);

        actionExplode(explosionIndex);

        CountDownTimer cleanChainExplosions = new CountDownTimer(animateDurationExplosion * explosionChain.length, animateDurationExplosion) {
            @Override
            public void onTick(long millisUntilFinished) {
            }
            @Override
            public void onFinish() {
                for (int i = 0; i < explosionViews.length; i++) {
                    ((ViewManager) explosionViews[i].getParent()).removeView(explosionViews[i]);
                }
                LinearLayout questionDisplay = (LinearLayout) findViewById(R.id.questionDisplay);
                Log.i("startChainExplosions", "questionDisplay.getChildCount()=" + questionDisplay.getChildCount());
            }
        };
        cleanChainExplosions.start();
    }

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

已更新 activity_main.xml - 保留了一些冗余 textViews 以实现与先前代码的向后兼容性。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    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"
    tools:context="com.plaudev.explodingtext.MainActivity">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:id="@+id/questionDisplay">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="@string/textView"
            android:id="@+id/questionView"
            android:fontFamily="casual"
            android:textSize="50sp"
            android:textStyle="normal|bold"
            android:layout_weight="1"
            android:gravity="center" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:text="@string/textView"
            android:id="@+id/readyView"
            android:fontFamily="casual"
            android:textSize="50sp"
            android:textStyle="normal|bold"
            android:textAlignment="center"
            android:gravity="center"
            android:layout_weight="1"
            android:visibility="gone"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:text="@string/textView"
            android:id="@+id/setView"
            android:fontFamily="casual"
            android:textSize="50sp"
            android:textStyle="normal|bold"
            android:textAlignment="center"
            android:gravity="center"
            android:layout_weight="1"
            android:visibility="gone"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:text="@string/textView"
            android:id="@+id/goView"
            android:fontFamily="casual"
            android:textSize="50sp"
            android:textStyle="normal|bold"
            android:textAlignment="center"
            android:gravity="center"
            android:layout_weight="1"
            android:visibility="gone"/>

    </LinearLayout>

    <Button
        android:text="@string/button"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:id="@+id/button"
        android:background="@color/colourTransparent"
        android:textAllCaps="false"
        android:textSize="25sp"
        android:textStyle="normal|bold"
        android:fontFamily="casual"
        android:onClick="startChainExplosions"
        android:textColor="@android:color/holo_green_dark"
        android:layout_below="@+id/questionDisplay"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

</RelativeLayout>

更新日志:

I/prepareChainExplosions: textSizeFullPx=175.0
I/prepareChainExplosions: questionDisplay.getChildCount()=7
I/startChainExplosions: explosionIndex=0
I/actionExplode: explosionIndex=0, textSizePx=175.0
I/actionExplode: .getTextSize()=43.75
I/onAnimationEnd: .getTextSize()=43.75
I/actionExplode: explosionIndex=1, textSizePx=175.0
I/actionExplode: .getTextSize()=43.75
I/onAnimationEnd: .getTextSize()=43.75
I/actionExplode: explosionIndex=2, textSizePx=175.0
I/actionExplode: .getTextSize()=43.75
I/startChainExplosions: questionDisplay.getChildCount()=4
I/onAnimationEnd: .getTextSize()=43.75