2013-10-31 20 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