逐帧动画 Android

Frame by frame animation Android

我想创建一个会说话的头像,使用 tts Android 和 Android 中的可绘制帧动画。口型同步图像存储在 drawable 文件夹中。这是按下说话按钮时执行的功能。

该功能的要点是,根据出现的字母,将每个对应的口型同步动作添加到动画中。其余的动作都是根据选择说话的语言、音调和语速来决定的。 Thread是用来运行动画和语音并行的。

     b1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //thread to add the animation
            Thread avatarSp = new Thread(new Runnable() {
                @Override
                public void run() {
                    String toSpeak = ed1.getText().toString();
                    String[] words = toSpeak.split(" ");
                    for (String word : words) {
                        word = word.toLowerCase();
                        char[] letters = word.toCharArray();
                        for (int i = 0; i < letters.length; i++) {
                            if (letters[i] == 'a') {
                                if (sr1 == 1)
                                    avatarSpeak.addFrame(getResources().getDrawable(R.drawable.a_i), 500);
                                else if (sr1 == 2)
                                    avatarSpeak.addFrame(getResources().getDrawable(R.drawable.a_i), 750);
                            } else if (letters[i] == 'e') {
                                if (letters[i + 1] == 'i' || letters[i + 1] == 'a' || letters[i + 1] == 'e') {
                                    i++;
                                    if (sr1 == 1)
                                        avatarSpeak.addFrame(getResources().getDrawable(R.drawable.e), 500);
                                    else if (sr1 == 2)
                                        avatarSpeak.addFrame(getResources().getDrawable(R.drawable.e), 750);
                                } else {
                                    if (sr1 == 1)
                                        avatarSpeak.addFrame(getResources().getDrawable(R.drawable.l), 500);
                                    else if (sr1 == 2)
                                        avatarSpeak.addFrame(getResources().getDrawable(R.drawable.l), 750);
                                }

                            } else if (letters[i] == 'i') {
                                if (sr1 == 1)
                                    avatarSpeak.addFrame(getResources().getDrawable(R.drawable.c_d_g_k_n_r_s_th_y_z), 500);
                                else if (sr1 == 2)
                                    avatarSpeak.addFrame(getResources().getDrawable(R.drawable.c_d_g_k_n_r_s_th_y_z), 750);

                            } else if (letters[i] == 'o') {
                                if (sr1 == 1)
                                    avatarSpeak.addFrame(getResources().getDrawable(R.drawable.o), 500);
                                else if (sr1 == 2)
                                    avatarSpeak.addFrame(getResources().getDrawable(R.drawable.o), 750);
                            } else if (letters[i] == 'u') {
                                if (sr1 == 1)
                                    avatarSpeak.addFrame(getResources().getDrawable(R.drawable.u), 500);
                                else if (sr1 == 2)
                                    avatarSpeak.addFrame(getResources().getDrawable(R.drawable.u), 750);
                            } else if (letters[i] == 'w' || letters[i] == 'q') {
                                if (sr1 == 1)
                                    avatarSpeak.addFrame(getResources().getDrawable(R.drawable.w_q), 500);
                                else if (sr1 == 2)
                                    avatarSpeak.addFrame(getResources().getDrawable(R.drawable.w_q), 750);
                            } else if (letters[i] == 'f' || letters[i] == 'v') {
                                if (sr1 == 1)
                                    avatarSpeak.addFrame(getResources().getDrawable(R.drawable.f_v), 500);
                                else if (sr1 == 2)
                                    avatarSpeak.addFrame(getResources().getDrawable(R.drawable.f_v), 750);
                            } else if (letters[i] == 'l') {
                                if (sr1 == 1)
                                    avatarSpeak.addFrame(getResources().getDrawable(R.drawable.l), 500);
                                else if (sr1 == 2)
                                    avatarSpeak.addFrame(getResources().getDrawable(R.drawable.l), 750);
                            } else if (letters[i] == 'm' || letters[i] == 'b' || letters[i] == 'p') {
                                if (sr1 == 1)
                                    avatarSpeak.addFrame(getResources().getDrawable(R.drawable.m_b_p), 500);
                                else if (sr1 == 2)
                                    avatarSpeak.addFrame(getResources().getDrawable(R.drawable.m_b_p), 750);
                            } else {
                                if (sr1 == 1)
                                    avatarSpeak.addFrame(getResources().getDrawable(R.drawable.c_d_g_k_n_r_s_th_y_z), 500);
                                else if (sr1 == 2)
                                    avatarSpeak.addFrame(getResources().getDrawable(R.drawable.c_d_g_k_n_r_s_th_y_z), 750);
                            }
                        }
                        if (sr1 == 1)
                            avatarSpeak.addFrame(getResources().getDrawable(R.drawable.rest), 500);
                        else if (sr1 == 2)
                            avatarSpeak.addFrame(getResources().getDrawable(R.drawable.rest), 750);
                    }
                    avatar.post(new Starter());
                }

            });
            //start the thread
            avatarSp.start();
            tts1.setPitch(p1);
            tts1.setSpeechRate(sr1);
            String toSpeak = ed1.getText().toString();
            lang = sp1.getSelectedItem().toString();
            if (lang.equals("US")) {
                System.out.print("Condition satisfied");
                tts1.setLanguage(Locale.US);
            } else if (lang.equals("UK"))
                tts1.setLanguage(Locale.UK);
            else if (lang.equals("Germany"))
                tts1.setLanguage(Locale.GERMANY);
            else if (lang.equals("Italy"))
                tts1.setLanguage(Locale.ITALY);
            else if (lang.equals("Japan"))
                tts1.setLanguage(Locale.JAPAN);
            else
                tts1.setLanguage(Locale.CHINA);

            Toast.makeText(getApplicationContext(), toSpeak, Toast.LENGTH_SHORT).show();
            //speak
            tts1.speak(toSpeak, TextToSpeech.QUEUE_FLUSH, null);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                tts1.speak(toSpeak, TextToSpeech.QUEUE_FLUSH, null, null);
            } else {
                tts1.speak(toSpeak, TextToSpeech.QUEUE_FLUSH, null);
            }
        }
    });
}

整个编码测试的API水平是Android5.0(21)和Android6.0(AVD 23)。但是当我按下发言按钮时,AVD 和 Phone 上的 API 崩溃了。你能告诉我吗:

    1. Where the error could possibly be?
    2. Is there a better way to do this and how?

如果是新手,请多多包涵,但我真的很想知道用我自己的代码实现功能的更好方法。

更新 1

添加日志猫信息。

    10-29 09:02:26.245    1931-1931/com.bluesbegone.avatarspeak I/art﹕ Not late-enabling -Xcheck:jni (already on)
    10-29 09:02:26.246    1931-1931/com.bluesbegone.avatarspeak I/art﹕ Late-enabling JIT
    10-29 09:02:26.411    1931-1931/com.bluesbegone.avatarspeak I/art﹕ JIT created with code_cache_capacity=2MB compile_threshold=1000
    10-29 09:02:27.282    1931-1931/com.bluesbegone.avatarspeak W/System﹕ ClassLoader referenced unknown path: /data/app/com.bluesbegone.avatarspeak-2/lib/x86
    10-29 09:02:28.992    1931-1931/com.bluesbegone.avatarspeak I/TextToSpeech﹕ Sucessfully bound to com.svox.pico
    10-29 09:02:29.026    1931-1956/com.bluesbegone.avatarspeak D/OpenGLRenderer﹕ Use EGL_SWAP_BEHAVIOR_PRESERVED: true
    10-29 09:02:29.056    1931-1931/com.bluesbegone.avatarspeak D/﹕ HostConnection::get() New Host Connection established 0xa3fff460, tid 1931
    10-29 09:02:29.300    1931-1938/com.bluesbegone.avatarspeak W/art﹕ Suspending all threads took: 37.049ms
    10-29 09:02:29.459    1931-1956/com.bluesbegone.avatarspeak D/﹕ HostConnection::get() New Host Connection established 0xa3fff830, tid 1956
    10-29 09:02:29.509    1931-1956/com.bluesbegone.avatarspeak I/OpenGLRenderer﹕ Initialized EGL, version 1.4
    10-29 09:02:29.671    1931-1956/com.bluesbegone.avatarspeak W/EGL_emulation﹕ eglSurfaceAttrib not implemented
    10-29 09:02:29.672    1931-1956/com.bluesbegone.avatarspeak W/OpenGLRenderer﹕ Failed to set EGL_SWAP_BEHAVIOR on surface 0xad77ac60, error=EGL_SUCCESS
    10-29 09:02:29.912    1931-1931/com.bluesbegone.avatarspeak I/Choreographer﹕ Skipped 41 frames!  The application may be doing too much work on its main thread.
    10-29 09:02:31.082    1931-1931/com.bluesbegone.avatarspeak I/Choreographer﹕ Skipped 69 frames!  The application may be doing too much work on its main thread.
    10-29 09:02:31.297    1931-1931/com.bluesbegone.avatarspeak I/TextToSpeech﹕ Connected to ComponentInfo{com.svox.pico/com.svox.pico.PicoService}
    10-29 09:02:31.822    1931-1968/com.bluesbegone.avatarspeak I/TextToSpeech﹕ Set up connection to ComponentInfo{com.svox.pico/com.svox.pico.PicoService}
    10-29 09:02:32.280    1931-1938/com.bluesbegone.avatarspeak W/art﹕ Suspending all threads took: 288.632ms
    10-29 09:02:35.320    1931-1938/com.bluesbegone.avatarspeak W/art﹕ Suspending all threads took: 12.137ms
    10-29 09:02:43.335    1931-2173/com.bluesbegone.avatarspeak E/AndroidRuntime﹕ FATAL EXCEPTION: Thread-83
Process: com.bluesbegone.avatarspeak, PID: 1931
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6556)
        at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:942)
        at android.view.ViewGroup.invalidateChild(ViewGroup.java:5081)
        at android.view.View.invalidateInternal(View.java:12713)
        at android.view.View.invalidate(View.java:12649)
        at android.view.View.invalidateDrawable(View.java:16788)
        at android.widget.ImageView.invalidateDrawable(ImageView.java:248)
        at android.graphics.drawable.DrawableContainer.invalidateDrawable(DrawableContainer.java:377)
        at android.graphics.drawable.Drawable.invalidateSelf(Drawable.java:385)
        at android.graphics.drawable.Drawable.setVisible(Drawable.java:764)
        at android.graphics.drawable.DrawableContainer.initializeDrawableForDisplay(DrawableContainer.java:510)
        at android.graphics.drawable.DrawableContainer.selectDrawable(DrawableContainer.java:459)
        at android.graphics.drawable.AnimationDrawable.setFrame(AnimationDrawable.java:274)
        at android.graphics.drawable.AnimationDrawable.addFrame(AnimationDrawable.java:251)
        at com.bluesbegone.avatarspeak.MainActivity.run(MainActivity.java:190)
        at java.lang.Thread.run(Thread.java:818)
    10-29 09:02:44.093    1931-1942/com.bluesbegone.avatarspeak I/art﹕ Background sticky concurrent mark sweep GC freed 10741(781KB) AllocSpace objects, 0(0B) LOS objects, 39% free, 2MB/3MB, paused 1.490ms total 313.129ms
    10-29 09:02:44.122    1931-1942/com.bluesbegone.avatarspeak W/art﹕ Suspending all threads took: 26.447ms
    10-29 09:02:44.278    1931-1938/com.bluesbegone.avatarspeak W/art﹕ Suspending all threads took: 146.147ms
    10-29 09:02:45.073    1931-1931/com.bluesbegone.avatarspeak I/Choreographer﹕ Skipped 77 frames!  The application may be doing too much work on its main thread.
    10-29 09:02:45.160    1931-1956/com.bluesbegone.avatarspeak W/EGL_emulation﹕ eglSurfaceAttrib not implemented
    10-29 09:02:45.161    1931-1956/com.bluesbegone.avatarspeak W/OpenGLRenderer﹕ Failed to set EGL_SWAP_BEHAVIOR on surface 0xa29586e0, error=EGL_SUCCESS
    10-29 09:02:45.231    1931-1956/com.bluesbegone.avatarspeak E/Surface﹕ getSlotFromBufferLocked: unknown buffer: 0xab81f1e0
    10-29 09:02:47.005    1931-1956/com.bluesbegone.avatarspeak E/Surface﹕ getSlotFromBufferLocked: unknown buffer: 0xab81f1e0
    10-29 09:02:48.334    1931-2173/com.bluesbegone.avatarspeak I/Process﹕ Sending signal. PID: 1931 SIG: 9

您正在尝试从后台线程为视图设置动画,但只有主线程可以访问视图。摆脱线程,将代码放在主要的 onClick 方法中。