在安卓上绘制高分辨率、高帧率的动画

6
我有30多个单色位图(320x240像素),希望在Android设备上全屏显示,形成动画效果。目前我使用ImageView和Timer实现了动画,Timer设置下一帧并发送消息应用下一帧。结果帧速率非常低:<2 fps。
计时器:
animationTimer.scheduleAtFixedRate(new TimerTask() {
    @Override
    public void run() {
        Drawable frame = getNextFrame();
            if (frame != null) {
                Message message = animationFrameHandler.obtainMessage(1, frame);
                animationFrameHandler.sendMessage(message);
            }
        }
    }, 0, (int) (1000.0d / fps));

处理程序:
final Handler animationFrameHandler = new Handler() {
    @Override
    public void handleMessage(Message message) {
        setImageDrawable((Drawable) message.obj);
    }
};

为了达到每秒30帧的帧率,我需要使用另一种机制,听说过Canvas.drawBitmapMesh()OpenGL

如果可能的话,我想避免使用OpenGL。

感谢分享你的经验!

4个回答

5

我的现在的工作方法如下:

在开始动画之前,将每一帧加载到一个List<Bitmap>中。重要提示:如果出现OutOfMemoryError错误,请调用System.gc(),这真的有助于将更多的位图加载到内存中。然后运行一个线程,将下一帧发布到一个View实例,然后更新它的画布。

加载帧并开始动画

// Loading the frames before starting the animation
List<Bitmap> frames = new ArrayList<Bitmap>();
for (int i = 0; i < 30; i++) {
    // Load next frame (e. g. from drawable or assets folder)
    frames.add(...);
    // Do garbage collection every 3rd frame; really helps loading all frames into memory
    if (i %% 3 == 0) {
        System.gc();
    }
}

// Start animation
frameIndex = 0;
animationThread.start();

应用下一帧的线程

private final class AnimationThread extends Thread {
    @Override
    public void run() {
        while (!isInterrupted()) {
            // Post next frame to be displayed
            animationView.postFrame(frames.get(frameIndex));

            // Apply next frame (restart if last frame has reached)
            frameIndex++;
            if (frameIndex >= frames.size()) {
                frameIndex = 0;
            }

            try {
                sleep(33); // delay between frames in msec (33 msec mean 30 fps)
            } catch (InterruptedException e) {
                break;
            }
        }
    }
}

动画视图

class AnimationView extends View {
    Bitmap frame = null;

    public void postFrame(Bitmap frame) {
        Message message = frameHandler.obtainMessage(0, frame);
        frameHandler.sendMessage(message);
    }

    protected final Handler frameHandler = new Handler() {
        @Override
        public void handleMessage(Message message) {
            if (message.obj != null) {
                frame = (Bitmap) message.obj;
            } else {
                frame = null;
            }
            invalidate();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (frame == null) return;
        canvas.drawARGB(0, 0, 0, 0);
        canvas.drawBitmap(frame, null, null, null);
    }
}

如何让一个计时器运行,并向视图实例发布下一帧?能否提供更详细的信息,谢谢。 - pengwang
1
你好pengwang,我更新了我的答案,以便更深入地解释我的方法。如果还有不清楚的地方,请随时提问。 - Lars Blumberg
感谢您的热心。 - pengwang
嗨,Lars Blumberg。它抛出了内存异常。 以下是我添加位图到列表的方法: int[] splashdrawables={R.drawable.splashframe1,R.drawable.splashframe2.....}; Drawable drawable=getResources().getDrawable(splashdrawables[i]); frames.add(Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888)); - sandy
亲爱的Sandy, 请在StackOverflow上创建一个新的问题,这样更多的人可以分析你的问题。发布代码时,随意链接到我的答案。 - Lars Blumberg

2
你可以查看FrameAnimation类来使用Androids动画进行帧动画。请参考http://developer.android.com/guide/topics/graphics/2d-graphics.html#frame-animation

不过,这种方法可能仍然太慢了。

如果你不想使用OpenGL ES,则另一个选择是像你提到的那样绘制到Canvas上。但只需使用.drawBitmap而不是drawBitmapMesh。创建SurfaceView,并在其线程中绘制您想要的任何间隔的Canvas。

它相当简单,只需要阅读Android文档,所有信息都在那里。


确实,FrameAnimation 太慢了 - 它显示的速度与我的实现相同。由于我必须遵循一些特定的规则来重复帧,所以我自己做了一个实现。我会研究 SurfaceView/.drawBitmap 的组合,并在这里给出反馈。到目前为止,谢谢你! - Lars Blumberg
感谢C0deAttack,学习网页http://developer.android.com/guide/topics/graphics/index.html#on-surfaceview(在SurfaceView上)帮助我实现了一个线程来绘制下一帧,以使用SurfaceView实现动画。 - Lars Blumberg
不幸的是,透明的SurfaceView不能被叠加在另一个用于相机预览的SurfaceView上,“getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS”如此说道,因此我现在只使用一个View,该View通过从线程发送的消息更新动画图形。不幸的是,这并不能提供所需的性能,在低预算的Android设备上很难实现帧率> 10 fps。因此,接下来几天我将深入研究OpenGL。感谢提供的所有帮助! - Lars Blumberg

0
我会让其他人谈论最佳方法,但是从您的帖子中立刻想到的一件事情是,使用TimerTask是一个可怕的方式来做这件事情,而且不适用于动画。

你是对的!对于我的测试以及如何制作动画来说,这已经足够了。正如我在这里提到的,FrameAnimation 不会更快,也不允许你指定从哪一帧开始循环,因此对我来说使用 TimerTask 很方便。 - Lars Blumberg

0

这可能对性能没有帮助,但如果这些位图是资源,您可能需要考虑使用AnimationDrawable。如果不是,请尝试扩展Drawable并实现Animatable接口。视图已经内置支持动画绘制,无需使用处理程序。

提高性能的一种方法可能是将可绘制对象的位深度与当前窗口的位深度匹配。Romain Guy曾经在一个主题演讲中谈到过这个问题以及动画方面的内容:http://www.youtube.com/watch?v=duefsFTJXzc


你说得对,使用AnimationDrawable和我的实现一样慢。我会看一下主题演讲。到目前为止谢谢你! - Lars Blumberg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接