2013-10-31 19 views
2

我做在本地代码中的一些位图动画,所以我需要重绘ImageView的每一帧:View.invalidate()非常慢。备择方案?

class MyImageView extends ImageView 
{ 
    ... 

    protected void onDraw(Canvas canvas) 
    { 
     native_drawStuff(handle, canvas); 
     invalidate(); 
    } 
} 

似乎一切都很好,除了Traceview表明这一点:

enter image description here

我不了解你,但这似乎有点荒谬。 native_drawStuff()设置位图的每个像素,然后将位图绘制到画布上,并且简单的invalidate()调用几乎需要4倍于?位图也不小(RGBA_8888和从50k到300k像素的任何位置)。

鉴于我需要重新绘制整个ImageView的每一帧,有没有更好的方法来做到这一点?我所看到的唯一建议是仅使视图中需要重绘的部分无效,但对我而言,这是整个事情。

+0

你为什么在onDraw()失效?这表明你可能不知道如何使无效的作品? – Simon

+1

我从onDraw()失效,因为在逻辑上它应该实现我的动画的最大刷新率。该文档对无效()进行了说明:“如果视图可见,onDraw(android.graphics.Canvas)将在未来的某个时刻调用”,这正是我想要的行为。如果不经常失效,动画不会更新。 – foo64

回答

2

API 14引入此完美类:TextureView

甲TextureView可用于显示的内容流。这样的内容流例如可以是视频或OpenGL场景。流内容 可以来自应用程序的流程以及远程的 流程。

Romain Guy发布了一个如何从另一个线程将内容流式传输到TextureView的示例:https://groups.google.com/forum/#!topic/android-developers/_Ogjc8sozpA。我在这里复制它的后代:

import android.app.Activity; 
import android.graphics.Canvas; 
import android.graphics.Paint; 
import android.graphics.PorterDuff; 
import android.graphics.SurfaceTexture; 
import android.os.Bundle; 
import android.view.Gravity; 
import android.view.TextureView; 
import android.widget.FrameLayout; 

@SuppressWarnings({"UnusedDeclaration"}) 
public class CanvasTextureViewActivity extends Activity 
     implements TextureView.SurfaceTextureListener { 
    private TextureView mTextureView; 
    private CanvasTextureViewActivity.RenderingThread mThread; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 

     FrameLayout content = new FrameLayout(this); 

     mTextureView = new TextureView(this); 
     mTextureView.setSurfaceTextureListener(this); 
     mTextureView.setOpaque(false); 

     content.addView(mTextureView, new FrameLayout.LayoutParams(500, 500, Gravity.CENTER)); 
     setContentView(content); 
    } 

    @Override 
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 
     mThread = new RenderingThread(mTextureView); 
     mThread.start(); 
    } 

    @Override 
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 
     // Ignored 
    } 

    @Override 
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 
     if (mThread != null) mThread.stopRendering(); 
     return true; 
    } 

    @Override 
    public void onSurfaceTextureUpdated(SurfaceTexture surface) { 
     // Ignored 
    } 

    private static class RenderingThread extends Thread { 
     private final TextureView mSurface; 
     private volatile boolean mRunning = true; 

     public RenderingThread(TextureView surface) { 
      mSurface = surface; 
     } 

     @Override 
     public void run() { 
      float x = 0.0f; 
      float y = 0.0f; 
      float speedX = 5.0f; 
      float speedY = 3.0f; 

      Paint paint = new Paint(); 
      paint.setColor(0xff00ff00); 

      while (mRunning && !Thread.interrupted()) { 
       final Canvas canvas = mSurface.lockCanvas(null); 
       try { 
        canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR); 
        canvas.drawRect(x, y, x + 20.0f, y + 20.0f, paint); 
       } finally { 
        mSurface.unlockCanvasAndPost(canvas); 
       } 

       if (x + 20.0f + speedX >= mSurface.getWidth() || x + speedX <= 0.0f) { 
        speedX = -speedX; 
       } 
       if (y + 20.0f + speedY >= mSurface.getHeight() || y + speedY <= 0.0f) { 
        speedY = -speedY; 
       } 

       x += speedX; 
       y += speedY; 

       try { 
        Thread.sleep(15); 
       } catch (InterruptedException e) { 
        // Interrupted 
       } 
      } 
     } 

     void stopRendering() { 
      interrupt(); 
      mRunning = false; 
     } 
    } 
} 
+1

当RenderingThread执行大量绘制时,在TextureView被销毁后仍然可以进行绘制操作。这可能会导致分段错误并导致应用程序崩溃。我解决问题的方法是通过onSurfaceTextureDestroyed方法中的mThread.Join()。这将阻止TexureView的毁坏,直到框架绘制完成。 –

-1

你应该不要在onDraw中调用invalidate(),因为invalidate()会在UI线程上调用onDraw()ASAP。你应该使用postInvalidateDelayed(delayMilliseconds); (如果要间隔刷新视图或使用不同的线程或处理程序,则不是故意在onDraw()中)。 如果你试图频繁地刷新视图,使用:

YourView v; 

//starting refreshing view , prefer to start it in onresume 
Runnable updateRunnable = new Runnable(){ 
     @Override 
     public void run(){ 
      v.invalidate(); 
      v.postDelayed(updateRunnable, 1000/30);//1000 ms/30fps 
     } 
}; 
v.post(updateRunnable); 
... 
//stopping it 
v.removeCallbacks(updateRunnable);//prefer to do it in onpause 

这将更新30fps的你的看法。如果你的表现可以处理它。

+2

“invalidate()调用onDraw()尽快”正是我想要的! invalidate()肯定不会直接调用onDraw()或者我会得到堆栈溢出。无论我将尝试移动onDraw()之外的invalidate调用,但是文档没有提到这种限制:“如果视图可见,onDraw(android.graphics.Canvas)将在未来的某个时刻调用” 。 – foo64

+0

更新了我的答案 – Sipka

+2

我将无效转移到TimerTask中,但它没有产生任何可衡量的差异。看起来像invalidate()无论你从哪里调用它都很慢。 – foo64