如何在Android中绘制动画视图?

8

我从头开始创建了一个自定义视图。扩展了View并覆盖了onDraw()方法。 在进行视图动画时,我使用偏移量生成了自定义动画。 例如:

while(!isOnTop){
mOffset++;

//draw the component a a it higher using the offset

if(position == 0)
isOnTop==true;
invalidate();

}

思路是我的框架来自于invalidate本身。问题在于,这个视图的无效化可以通过在同一屏幕上滚动listview来实现。
这种“共享的无效化”会导致我的动画延迟。那么有没有什么方法可以避免这种延迟呢?
您有没有其他关于在共享环境中执行动画的建议?使用单独的线程创建动画并计算偏移量也需要强制无效化()调用才能显示动画(如果我说错了,请纠正我)。
唯一的解决方案是通过10次无效请求执行动画,并且步长更大吗?这将缓解延迟,但我认为我可以采用不同的方法来解决这个问题。
2个回答

22
当然,“最好的”取决于你想要做什么。由于你没有说明你想要达到什么目标,所以我们只能猜测哪种方法对你来说是最好的。
以下是一些简单的方法:
- 如果您想要动画位图帧,请使用AnimationDrawable:http://developer.android.com/reference/android/graphics/drawable/AnimationDrawable.html - 如果您想要在层次结构中动画视图的移动,请使用视图动画框架:http://developer.android.com/guide/topics/graphics/view-animation.html - 新的更通用的动画框架可以做更多的事情,并且通常更容易使用:http://developer.android.com/guide/topics/graphics/animation.html。这在Android 3.0+中本地可用,但也可以在支持v7库的Android API级别7中使用。 - 如果您想编写自定义小部件作为其视图层次结构的集成部分,并手动执行自己的动画绘制,则可以使用Handler定时更新(通常您会想要60fps或每个invalidate()之间的20ms),然后在onDraw()方法中基于SystemClock.uptimeMillis()将您的视图状态作为与动画开始时间的增量进行绘制。
以下是一个简单的重复无效使用Handler:
long mAnimStartTime;

Handler mHandler = new Handler();
Runnable mTick = new Runnable() {
    public void run() {
        invalidate();
        mHandler.postDelayed(this, 20); // 20ms == 60fps
    }
}

void startAnimation() {
    mAnimStartTime = SystemClock.uptimeMillis();
    mHandler.removeCallbacks(mTick);
    mHandler.post(mTick);
}

void stopAnimation() {
    mHandler.removeCallbacks(mTick);
}

1
invalidate() 不是即时的。可能需要超过20毫秒,因为有很多视图需要使无效。视图必须等待直到它们全部完成(至少在3.0之前)。postDelayed会引入更多的延迟。通过这种方式限制fps并不好。如果我错了,请纠正我。 - weakwire
2
不是真的。如果invalidate()需要超过20毫秒,那么您在构建视图层次结构方面存在严重问题。标准平台中窗口内的绝大多数动画都使用带有invalidate()的常规视图,并且可以实现60fps。您只需要确保在视图层次结构中不要做一些像将多个大的不透明位图放在彼此之上或缩放使得绘制它们变慢的事情...但您也不想这样做。 - hackbod
FPS是否不取决于硬件和当前的CPU/GPU/内存负载?也就是说,如果一致的动画时间很重要,我认为应该在运行时(高效地)测量FPS并相应地确定延迟时间。 - ılǝ

4

由于此问题受到了一定的关注,我将回复。

最好的方法是拥有一个独立的画布线程。这只能通过 SurfaceView 实现。LunarLanding 是这种用法的一个极好的例子。每个帧都是单独计算的,与主视图共享 CPU 时间,而不是绘制时间。因此,即使在顶部有常规视图,在底部有动画视图的情况下,它也更快。

但是,如果您处于共享环境中,您必须设置间隔。该间隔用于 FPS 上限。如果您不设置 FPS 上限,则 CPU 将在尽可能让 SurfaceView 单独运行良好的动画时疯狂运转。将其限制在 60fps 或更低的速度下,可以有效地绘制所有视图,而不会导致 CPU 超载。

因此,请参见 API 演示中 Lunar Landing 的绘图线程,并设置 FPS 上限。

private long timeNow;
    private long timeDelta;
    private long timePrevFrame;

    private void capFps(int fps) {  
            timeNow = System.currentTimeMillis();
            timeDelta = timeNow - timePrevFrame;        

            try {

//提示:如果您想避免每次计算,可以始终将16设置为60FPS而不是1000 / FPS

Thread.sleep((1000 / fps) - timeDelta);
            } catch (InterruptedException e) {

            }   

            timePrevFrame = System.currentTimeMillis();
    }

然后,绘画线程看起来会像这样:
@Override
    public void run() {
        Canvas c;

        while (run) {
            c = null;
            sleepFps(60, false);
            try {
                synchronized (surfaceHolder) {
                    c = surfaceHolder.lockCanvas(null);
                    widgetView.doDraw(c);
                }
            } finally {
                if (c != null) {
                    surfaceHolder.unlockCanvasAndPost(c);
                }
            }

        }
    }

抱歉,这比通常需要的要复杂得多,而且SurfaceView有许多妥协,使它通常不是最佳解决方案。 - hackbod
是的,但它是最好的选择,可以同时拥有多个动画视图。问题是什么是“最佳”方式,虽然感谢您的回答。 - weakwire
这个实际上是我从j2me熟悉的方式。如果你不喜欢计算,你可以跳过它并睡眠一段固定的时间。 - Vitali Pom

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