【问题标题】:Most efficient way to show frame by frame animation android逐帧显示动画android的最有效方法
【发布时间】:2014-06-16 11:06:09
【问题描述】:

我试图通过更改图像视图中的图像来逐帧显示动画。我尝试在 xml 中绘制动画,并在处理程序中更改图像视图的位图。我还尝试在 arraylist 中仅存储三个位图(以避免内存不足)作为缓存机制,但改进非常低。我需要迭代 36 张图像以获得完整的动画。我面临的问题是,在我使用的所有方法中,我无法在给定的 50 毫秒时间范围内完成动画。图像范围从最小 250 kb 到最大 540 kb。动画的fps真的很低。由于应用程序的 ios 版本已准备好,我只能显示与 ios 版本一致的动画。我是renderscript和opengl的菜鸟。有什么方法可以在 50-60 毫秒内为大图像显示流畅的动画。非常感谢任何提示或建议。下面是动画截图: 这是我的图片的link,供任何感兴趣的人使用。

【问题讨论】:

  • 您是否考虑过使用带有纹理视图的画布?
  • 我从未使用过画布或纹理视图。 Canvas 在我可能的解决方案列表中。你有其他想法吗??
  • 它不起作用怎么办?不够快?画不正确?
  • 我添加了另一个示例活动,以及有关图像的一些注释。使用较小的图像似乎会使事情变得容易得多。

标签: android android-animation


【解决方案1】:

我写了一个简单的活动,做最基本的事情:

加载线程中的所有位图,然后每 40 毫秒将更改发布到 ImageView。

package mk.testanimation;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.ImageView;
import java.util.ArrayList;

public class MainActivity extends Activity {
    ImageView mImageView;

    private int mImageRes[] = new int[]{
        R.drawable.s0,
        R.drawable.s1,
        R.drawable.s2,
        R.drawable.s3,
        R.drawable.s4,
        R.drawable.s5,
        R.drawable.s6,
        R.drawable.s7,
        R.drawable.s8,
        R.drawable.s9,
        R.drawable.s10,
        R.drawable.s11,
        R.drawable.s12,
        R.drawable.s13,
        R.drawable.s14,
        R.drawable.s15,
        R.drawable.s16,
        R.drawable.s17,
        R.drawable.s18,
        R.drawable.s19,
        R.drawable.s20,
        R.drawable.s21,
        R.drawable.s22,
        R.drawable.s23,
        R.drawable.s24,
        R.drawable.s25,
        R.drawable.s26,
        R.drawable.s27,
        R.drawable.s28,
        R.drawable.s29,
        R.drawable.s30,
        R.drawable.s31,
        R.drawable.s32,
        R.drawable.s33,
        R.drawable.s34,
        R.drawable.s35,
    };

    private ArrayList<Bitmap> mBitmaps = new ArrayList<Bitmap>(mImageRes.length);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final Handler handler = new Handler();
        mImageView = new ImageView(this);
        setContentView(mImageView);
        Thread important = new Thread() {
            @Override
            public void run() {
                long timestamp = System.currentTimeMillis();
                for (int i = 0; i < mImageRes.length; i++) {
                    mBitmaps.add(BitmapFactory.decodeResource(getResources(), mImageRes[i]));
                }
                Log.d("ANIM-TAG", "Loading all bitmaps took " + (System.currentTimeMillis() - timestamp) + "ms");
                for (int i = 0; i < mBitmaps.size(); i++) {
                    final int idx = i;
                    handler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            mImageView.setImageBitmap(mBitmaps.get(idx));
                        }
                    }, i * 40);
                }
            }
        };
        important.setPriority(Thread.MAX_PRIORITY);
        important.start();
    }
}

这在我的 Nexus 7 上看起来相当不错,但加载所有位图确实需要 4 多秒。

可以提前加载位图吗?

此外,它不会节省很多,但是您的 png 在透明空间周围有一堆填充。您可以裁剪它们并稍微减少内存。否则压缩图像也会有所帮助(例如限制使用的颜色数量)。

理想情况下,在上述解决方案中,您应该在不再使用位图后立即回收它们。

另外,如果内存太重,你可以按照你提到的那样做,并有一个位图缓冲区,但我很确定它需要超过 3 个图像大。

祝你好运。

编辑:尝试 2。 首先,我将所有图像裁剪为 590x590。这将图像减少了大约 1mb。然后我创建了一个新的类,它有点“忙”,没有固定的帧速率,但是一旦图像准备好就会渲染:

package mk.testanimation;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.ImageView;

import java.util.ArrayList;

public class MainActivity extends Activity {
    ImageView mImageView;

    private int mImageRes[] = new int[]{R.drawable.s0, R.drawable.s1, R.drawable.s2, R.drawable.s3, R.drawable.s4, R.drawable.s5, R.drawable.s6, R.drawable.s7, R.drawable.s8, R.drawable.s9, R.drawable.s10, R.drawable.s11, R.drawable.s12, R.drawable.s13, R.drawable.s14, R.drawable.s15, R.drawable.s16, R.drawable.s17, R.drawable.s18, R.drawable.s19, R.drawable.s20, R.drawable.s21, R.drawable.s22, R.drawable.s23, R.drawable.s24, R.drawable.s25, R.drawable.s26, R.drawable.s27, R.drawable.s28, R.drawable.s29, R.drawable.s30, R.drawable.s31, R.drawable.s32, R.drawable.s33, R.drawable.s34, R.drawable.s35};

    private ArrayList<Bitmap> mBitmaps = new ArrayList<Bitmap>(mImageRes.length);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final long timestamp = System.currentTimeMillis();
        final Handler handler = new Handler();
        mImageView = new ImageView(this);
        setContentView(mImageView);
        Thread important = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < mImageRes.length; i++) {
                    mBitmaps.add(BitmapFactory.decodeResource(getResources(), mImageRes[i]));
                }
            }
        };
        important.setPriority(Thread.MAX_PRIORITY);
        important.start();
        Thread drawing = new Thread() {
            @Override
            public void run() {
                int i = 0;
                while (i < mImageRes.length) {
                    if (i >= mBitmaps.size()) {
                        Thread.yield();
                    } else {
                        final Bitmap bitmap = mBitmaps.get(i);
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                mImageView.setImageBitmap(bitmap);
                            }
                        });
                        i++;
                    }
                }
                Log.d("ANIM-TAG", "Time to render all frames:" + (System.currentTimeMillis() - timestamp) + "ms");
            }
        };
        drawing.setPriority(Thread.MAX_PRIORITY);
        drawing.start();
    }
}

以上内容几乎立即开始渲染,在我的 2012 Nexus 7 上用了不到 4 秒。

作为最后的努力,我将所有图像转换为 8 位 PNG 而不是 32 位。这使渲染时间不到 2 秒!

我敢打赌,您最终得到的任何解决方案都会受益于使图像尽可能小。

再次——祝你好运!

【讨论】:

  • 感谢您的回答。然而,这不是我正在寻找的解决方案。对于那个很抱歉。问题是我需要在 3 秒内消耗超过 16 mb 的数据。我之前在自定义图像视图中插入类似代码的尝试在 3 秒内得到了更好的结果。但是第一张图片显示的时间更长。在所有当前答案中,您的答案提供了一些试验,我会记住这一点。 TQ
  • 检查我的编辑,它有更多建议,尤其是减小图像的大小。对我来说不到 2 秒就可以工作!
  • 所有阅读这篇文章的人请注意,它并不能解决我的问题。这是显示为解决问题付出了一些努力的唯一答案。
  • 感谢您接受我的回答。您是否更新了图像并尝试了上述任何一种方法?它们都应该在 3 秒内完成动画,我认为这是您的要求。
  • 不,我没有。对此感到抱歉。在我的动画试用的早期版本中,我提出了类似的代码。目前我正在尝试使用 NDK 是新的。我不允许缩小图像(管理决定),并且该应用程序在 iphone 中可用。为什么我没有尝试你的代码是因为在我展示了这个动画之后,我将不得不再次执行另一个本质上更复杂的动画,比如硬币翻转效果。无论如何感谢试用。我一定会回答这个问题并让你知道。
【解决方案2】:

我遇到了同样的问题,我通过覆盖 AnimationDrawable 解决了这个问题。 因此,如果问题是您无法加载数组中的所有图像,因为它太大而内存无法容纳它,然后在需要时加载图像。 我的 AnimationDrawable 是这样的:

public abstract class MyAnimationDrawable extends AnimationDrawable {

    private Context context;
    private int current;
    private int reqWidth;
    private int reqHeight;
    private int totalTime;

    public MyAnimationDrawable(Context context, int reqWidth, int reqHeight) {
        this.context = context;
        this.current = 0;
        //In my case size of screen to scale Drawable
        this.reqWidth = reqWidth;
        this.reqHeight = reqHeight;
        this.totalTime = 0;
    }

    @Override
    public void addFrame(Drawable frame, int duration) {
        super.addFrame(frame, duration);
        totalTime += duration;
    }

    @Override
    public void start() {
        super.start();
        new Handler().postDelayed(new Runnable() {

            public void run() {
                onAnimationFinish();
            }
        }, totalTime);
    }

    public int getTotalTime() {
        return totalTime;
    }

    @Override
    public void draw(Canvas canvas) {
        try {
            //Loading image from assets, you could make it from resources 
            Bitmap bmp = BitmapFactory.decodeStream(context.getAssets().open("presentation/intro_000"+(current < 10 ? "0"+current : current)+".jpg"));
            //Scaling image to fitCenter
            Matrix m = new Matrix();
            m.setRectToRect(new RectF(0, 0, bmp.getWidth(), bmp.getHeight()), new RectF(0, 0, reqWidth, reqHeight), Matrix.ScaleToFit.CENTER);
            bmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true);
            //Calculating the start 'x' and 'y' to paint the Bitmap 
            int x = (reqWidth - bmp.getWidth()) / 2;
            int y = (reqHeight - bmp.getHeight()) / 2;
            //Painting Bitmap in canvas
            canvas.drawBitmap(bmp, x, y, null);
            //Jump to next item
            current++;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    abstract void onAnimationFinish();

}

现在要播放动画你需要做什么

    //Get your ImageView
    View image = MainActivity.this.findViewById(R.id.presentation);

    //Create AnimationDrawable
    final AnimationDrawable animation = new MyAnimationDrawable(this, displayMetrics.widthPixels, displayMetrics.heightPixels) {

        @Override
        void onAnimationFinish() {
            //Do something when finish animation
        }

    };
    animation.setOneShot(true); //dont repeat animation
    //This is just to say that my AnimationDrawable has 72 frames with 50 milliseconds interval
    try {
        //Always load same bitmap, anyway you load the right one in draw() method in MyAnimationDrawable
        Bitmap bmp = BitmapFactory.decodeStream(MainActivity.this.getAssets().open("presentation/intro_00000.jpg"));
        for (int i = 0; i < 72; i++) {
            animation.addFrame(new BitmapDrawable(getResources(), bmp), 50);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

    //Set AnimationDrawable to ImageView
    if (Build.VERSION.SDK_INT < 16) {
        image.setBackgroundDrawable(animation);
    } else {
        image.setBackground(animation);
    }

    //Start animation
    image.post(new Runnable() {

        @Override
        public void run() {
            animation.start();
        }

    });

就是这样,对我来说工作正常!!!

【讨论】:

    【解决方案3】:

    尝试为您的ImageView 使用AnimationDrawable。例如,请参阅我的回答 here

    【讨论】:

    • 我已经使用了 AnimationDrawable,即使在像 nexus 5 这样的高端设备中,它也需要将近 8 秒的时间来加载和丢失帧。这是因为我使用的图像文件很大。抱歉,它没有解决我的问题。
    【解决方案4】:

    在 android 中逐帧显示动画的最有效方法是使用 OpenGLNDK

    【讨论】:

    • 嗯,我有点知道,但我是 NDK 和 openGl 的菜鸟。作为记录,我尝试在 opengl 中尝试没有成功,因为 opengl 仅在精灵动画(小图像)中有用。这就是我的想法
    • 无论如何这都是 Android 中最可能的解决方案 :) 尝试只重绘(更新)图片中发生变化的部分,不要尝试为每一帧重绘整个图片。
    • 这可能是一个选项,但您可以看到图层是从上到下径向向外添加的。图像也有阴影。我和我的设计师谈过,他听到这个想法很生气(笑)。很高兴知道我们的想法相同
    • @IllegalArgument 我会编写一个工具,如果这样的工具仍然不存在,它会计算必须在下一帧为 OpenGL 重新绘制的区域。即使这些层是从上到下径向向外添加的,也可以选择。
    • 我正在尝试在 ndk 中执行此操作,但任何类型的公式在我的情况下都不起作用,因为帧之间的相似性很少是图像,并且卡片之间的图层添加和阴影会造成问题.这也是这个动画之后的基础动画,我必须展示一个卡片翻转动画。我已经这样做了,卡在这个中
    【解决方案5】:

    使用编程创建一个imageView,然后将其旋转一个角度,然后再制作一个,然后旋转它.. 为要显示的imageView 数量执行此操作。您只需为其添加一张图片。 你可以像这样旋转图像..

    Matrix matrix=new Matrix();
    imageView.setScaleType(ScaleType.MATRIX);   //required
    matrix.postRotate((float) angle, pivX, pivY);
    imageView.setImageMatrix(matrix);
    

    【讨论】:

      【解决方案6】:

      也许是愚蠢的答案,但它可能会对你有所帮助。使用其他工具制作动画并将其保存为高质量视频,然后播放视频。

      【讨论】:

        【解决方案7】:

        尝试将图像导出为 GIF? Android 确实支持解码 GIF 文件。

        看看:Supported Media Formats.

        【讨论】:

          【解决方案8】:

          从资源而不是资源中加载位图为我节省了 40% 的解码时间。您可能想尝试一下。

          因此,在我的 2012 Nexus 7 上,资源从 5 秒变为 3 秒:

          long time = System.currentTimeMillis();
          try {
              for (int i = 0; i < 35; i++) {
                  InputStream assetStream = getAssets().open(
                          "_step" + (i + 1) + ".png");
                  try {
                      Bitmap bitmap = BitmapFactory.decodeStream(assetStream);
                      if (bitmap == null) {
                          throw new RuntimeException("Could not load bitmap");
                      }
                      mBitmaps.add(bitmap);
                  } finally {
                      assetStream.close();
                  }
              }
          } catch (Exception e) {
              throw new RuntimeException(e);
          }
          Log.d("ANIM", "Loading bitmaps elapsed "+(System.currentTimeMillis() - time)+"ms");
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2012-06-08
            • 1970-01-01
            • 2013-05-15
            • 1970-01-01
            • 1970-01-01
            • 2013-01-14
            • 2011-10-05
            相关资源
            最近更新 更多