【问题标题】:Android, canvas: How do I clear (delete contents of) a canvas (= bitmaps), living in a surfaceView?Android,画布:如何清除(删除)画布(=位图),生活在surfaceView中?
【发布时间】:2011-08-09 10:03:51
【问题描述】:

为了制作一个简单的游戏,我使用了一个模板,该模板使用这样的位图绘制画布:

private void doDraw(Canvas canvas) {
    for (int i=0;i<8;i++)
        for (int j=0;j<9;j++)
            for (int k=0;k<7;k++)   {
    canvas.drawBitmap(mBits[allBits[i][j][k]], i*50 -k*7, j*50 -k*7, null); } }

(画布在“run()”中定义/SurfaceView 位于 GameThread 中。)

我的第一个问题是如何清除(或重绘)整个画布以进行新布局?
其次,我怎样才能只更新屏幕的一部分?

// This is the routine that calls "doDraw":
public void run() {
    while (mRun) {
        Canvas c = null;
        try {
            c = mSurfaceHolder.lockCanvas(null);
            synchronized (mSurfaceHolder) {
                if (mMode == STATE_RUNNING) 
                    updateGame();
                doDraw(c);          }
        } finally {
            if (c != null) {
                mSurfaceHolder.unlockCanvasAndPost(c);  }   }   }       }

【问题讨论】:

    标签: android android-canvas surfaceview


    【解决方案1】:

    使用 PorterDuff 清除模式绘制透明颜色可以达到我想要的效果。

    Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
    

    【讨论】:

    • 这应该可以,但似乎在 4.4 中存在错误(至少在 N7.2 上)使用Bitmap#eraseColor(Color.TRANSPARENT),如下面的 HeMac 回答中所示。
    • 其实Color.TRANSPARENT是不必要的。 PorterDuff.Mode.CLEAR 对于ARGB_8888 位图来说完全足够了,这意味着将 alpha 和颜色设置为 [0, 0]。另一种方法是使用Color.TRANSPARENTPorterDuff.Mode.SRC
    • 这不适合我留下空白表面而不是透明
    【解决方案2】:

    我如何清除(或重绘)整个画布以获得新布局(= 尝试游戏)?

    只需致电Canvas.drawColor(Color.BLACK),或者您想用任何颜色清除您的Canvas

    还有:如何只更新屏幕的一部分?

    没有这样的方法可以只更新“屏幕的一部分”,因为 Android 操作系统在更新屏幕时会重绘每个像素。但是,当您不清除 Canvas 上的旧图纸时,旧图纸仍然在表面上,这可能是“仅更新一部分”屏幕的一种方式。

    所以,如果你想“更新屏幕的一部分”,请避免调用Canvas.drawColor() 方法。

    【讨论】:

    • 不,如果你不绘制表面的每个像素你会得到非常奇怪的结果(因为双缓冲是通过交换指针实现的,所以在你不绘制的部分你不会看到什么之前在那里)。您必须在每次迭代时重绘表面的每个像素。
    • @Viktor Lannér:如果您调用mSurfaceHolder.unlockCanvasAndPost(c),然后调用c = mSurfaceHolder.lockCanvas(null),那么新的c 与之前的c 包含的内容不同。你不能只更新 SurfaceView 的一部分,我猜这是 OP 要求的。
    • @Guillaume Brunerie:是的,但不是全部。正如我所写,您无法更新屏幕的一部分。但是您可以将旧图纸保留在屏幕上,这将具有“更新屏幕的一部分”的效果。在示例应用程序中亲自尝试。 Canvas 保留旧图纸。
    • 羞辱我!!!我确实只在游戏开始时初始化了我的数组,而不是在连续重新开始时 - 所以所有旧作品仍然“在屏幕上” - 它们只是重新绘制!我为我的“愚蠢”感到非常抱歉!谢谢你们两个!!!
    • 那么这可能是因为动态壁纸与常规 SurfaceView 的工作方式不同。无论如何,@samClem:总是在每一帧重绘画布的每个像素(如文档中所述),否则你会有奇怪的闪烁。
    【解决方案3】:

    在谷歌群组中找到了这个,这对我有用..

    Paint clearPaint = new Paint();
    clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
    canvas.drawRect(0, 0, width, height, clearPaint); 
    

    这会删除绘图矩形等,同时保持设置的位图..

    【讨论】:

    • 这给了我一个黑色区域——不是我在它后面的位图:(
    • 它清除得很好,但位图也像你说的那样被删除了。
    • stackoverflow.com/questions/9691985/… 中解释了如何在画布的某个矩形上绘制位图的矩形。更改矩形中的图像因此有效:学习以前的内容,绘制新图像
    【解决方案4】:

    使用Path类的reset方法

    Path.reset();
    

    【讨论】:

    • 如果您使用路径,这确实是最佳答案。其他的可能会给用户留下黑屏。谢谢。
    • 超级骗子真棒。谢谢。我的情况是通过从用户那里获取触摸事件来绘制一个带路径的三角形。所以我想在绘制新三角形的同时清除以前绘制的三角形来模拟它的移动。非常感谢。
    【解决方案5】:

    我尝试了@mobistry 的答案:

    canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);

    但这对我不起作用。

    对我来说,解决方案是:

    canvas.drawColor(Color.TRANSPARENT, Mode.MULTIPLY);

    也许有人有同样的问题。

    【讨论】:

    • 乘以透明度就是答案。否则它可能会在某些设备上变成黑色。
    【解决方案6】:
    mBitmap.eraseColor(Color.TRANSPARENT);
    
    canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
    

    【讨论】:

    • 这怎么算最正确?既然可以绘制颜色,为什么还要使用位图?
    • 虽然这可能不适用于原始海报(他们不一定有权访问位图),但这对于清除可用位图的内容绝对有用。在这些情况下,只需要第一行。有用的技术,+1
    【解决方案7】:
    canvas.drawColor(Color.TRANSPARENT, Mode.MULTIPLY);
    

    【讨论】:

    • 请说明您的代码如何解决问题。这被标记为低质量帖子 - From Review
    【解决方案8】:

    请在surfaceview扩展类构造函数上粘贴下面的代码......

    构造函数编码

        SurfaceHolder holder = getHolder();
        holder.addCallback(this);
    
        SurfaceView sur = (SurfaceView)findViewById(R.id.surfaceview);
        sur.setZOrderOnTop(true);    // necessary
        holder = sur.getHolder();
        holder.setFormat(PixelFormat.TRANSPARENT);
    

    xml编码

        <com.welcome.panelview.PanelViewWelcomeScreen
            android:id="@+id/one"
            android:layout_width="600px"
            android:layout_height="312px"
            android:layout_gravity="center"
            android:layout_marginTop="10px"
            android:background="@drawable/welcome" />
    

    试试上面的代码...

    【讨论】:

    • holder.setFormat(PixelFormat.TRANSPARENT);它对我有用。
    【解决方案9】:

    这是一个最小示例的代码,显示您总是必须在每一帧重绘 Canvas 的每个像素。

    这个活动每秒在 SurfaceView 上绘制一个新的 Bitmap,之前没有清屏。 如果你测试一下,你会发现位图并不总是写入同一个缓冲区,屏幕会在两个缓冲区之间交替显示。

    我在我的手机(Nexus S、Android 2.3.3)和模拟器(Android 2.2)上对其进行了测试。

    public class TestCanvas extends Activity {
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(new TestView(this));
        }
    }
    
    class TestView extends SurfaceView implements SurfaceHolder.Callback {
    
        private TestThread mThread;
        private int mWidth;
        private int mHeight;
        private Bitmap mBitmap;
        private SurfaceHolder mSurfaceHolder;
    
        public TestView(Context context) {
            super(context);
            mThread = new TestThread();
            mBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.icon);
            mSurfaceHolder = getHolder();
            mSurfaceHolder.addCallback(this);
        }
    
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                int height) {
            mWidth = width;
            mHeight = height;
            mThread.start();
        }
    
        @Override
        public void surfaceCreated(SurfaceHolder holder) {/* Do nothing */}
    
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            if (mThread != null && mThread.isAlive())
                mThread.interrupt();
        }
    
        class TestThread extends Thread {
            @Override
            public void run() {
                while (!isInterrupted()) {
                    Canvas c = null;
                    try {
                        c = mSurfaceHolder.lockCanvas(null);
                        synchronized (mSurfaceHolder) {
                            c.drawBitmap(mBitmap, (int) (Math.random() * mWidth), (int) (Math.random() * mHeight), null);
                        }
                    } finally {
                        if (c != null)
                            mSurfaceHolder.unlockCanvasAndPost(c);
                    }
    
                    try {
                        sleep(1000);
                    } catch (InterruptedException e) {
                        interrupt();
                    }
                }
            }   
        }
    }
    

    【讨论】:

    • 好吧,看来你错了,看看我的屏幕截图:img12.imageshack.us/i/devicey.png/# 当你添加了一秒钟的延迟时,双缓冲会更明显,但是(!)有屏幕上仍然是以前的图纸。另外,您的代码是错误的:应该是SurfaceHolder.Callback,而不仅仅是Callback
    • 我想你不明白我的意思。可以预料的是,帧 n 和帧 n+1 之间的区别在于多了一个位图。但这是完全错误的,帧n和帧n+2之间多了一个Bitmap,但是帧n和帧n +1 完全不相关,即使我只是添加了位图。
    • 不,我完全理解你。但是,如您所见,您的代码不起作用。过了一会儿,屏幕上满是图标。因此,如果我们想完全重绘整个屏幕,我们需要清除Canvas。这使得我关于“以前的图纸”的说法是正确的。 Canvas 保留以前的图纸。
    • 是的,如果您愿意,Canvas 会保留以前的图纸。但这完全没用,问题是当你使用lockCanvas() 时,你不知道那些“以前的绘图”是什么,并且不能假设它们。也许如果有两个具有相同大小的 SurfaceView 的活动,它们将共享相同的 Canvas。也许您会得到一大块未初始化的 RAM,其中包含随机字节。也许总有写在画布上的谷歌标志。你无法知道。 lockCanvas(null) 之后没有绘制每个像素的任何应用程序都会损坏。
    • @GuillaumeBrunerie 2.5 年后,我看到了你的帖子。 Android 官方文档支持您关于“绘制每个像素”的建议。只有一种情况是通过随后调用 lockCanvas() 可以保证画布的一部分仍然存在。有关 SurfaceHolder 及其两个 lockCanvas() 方法,请参阅 Android 文档。 developer.android.com/reference/android/view/…developer.android.com/reference/android/view/…
    【解决方案10】:

    对我来说,拨打Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR) 或类似的电话只有在我触摸屏幕后才会起作用。所以我会调用上面的代码行,但屏幕只有在我触摸屏幕后才会清除。所以对我有用的是调用invalidate(),然后调用init(),它在创建时调用以初始化视图。

    private void init() {
        setFocusable(true);
        setFocusableInTouchMode(true);
        setOnTouchListener(this);
    
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setColor(Color.BLACK);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeWidth(6);
    
        mCanvas = new Canvas();
        mPaths = new LinkedList<>();
    
        addNewPath();
    }
    

    【讨论】:

      【解决方案11】:

      在 java android 中擦除 Canvas 类似于通过 javascript 使用 globalCompositeOperation 擦除 HTML Canvas。逻辑类似。

      U 将选择 DST_OUT(目的地输出)逻辑。

      paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
      

      注意:DST_OUT 更有用,因为如果油漆颜色具有 50% 的 alpha,它可以擦除 50%。因此,要完全清除到透明,颜色的 alpha 必须为 100%。建议应用paint.setColor(Color.WHITE)。并确保画布图像格式为 RGBA_8888。

      擦除后,使用 SRC_OVER (Source Over) 回到正常绘图。

      paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
      

      从字面上更新小区域显示需要访问图形硬件,并且可能不受支持。

      最接近最高性能的是使用多图像层。

      【讨论】:

      • 你能帮我吗?你的解决方案是我的唯一解决方案,所以我投了赞成票,只是有一个小问题需要解决
      • 我正在使用带有表面支架的画布,所以我这样做是为了清除画布(从表面支架上取下的画布):paint!!.setXfermode(PorterDuffXfermode(PorterDuff.Mode.DST_OUT)) canvas!!。 drawPaint(paint!!) surfaceHolder!!.unlockCanvasAndPost(canvas) 然后能够重绘它:paint!!.setXfermode(PorterDuffXfermode(PorterDuff.Mode.SRC)) canvas!!.drawPaint(paint!!) surfaceHolder!! .unlockCanvasAndPost(canvas) 现在的问题是像这样清除画布后,当我在画布上绘制位图时,它是在颜色闪烁后绘制的,这很烦人,你能告诉我这里的路吗? @phnghue
      • @hamza khan 你试过 SRC_OVER 吗?因为 SRC_OVER 是默认正常的。 SRC 逻辑是覆盖旧的像素数据,它取代了 alpha!
      【解决方案12】:

      别忘了调用 invalidate();

      canvas.drawColor(backgroundColor);
      invalidate();
      path.reset();
      

      【讨论】:

      • 画完东西为什么要打invalidate
      【解决方案13】:

      通过以下方法,您可以清除整个画布或仅清除其中的一部分。
      请不要忘记禁用硬件加速,因为 PorterDuff.Mode.CLEAR 不适用于硬件加速,最后调用 setWillNotDraw(false) 因为我们覆盖了 onDraw 方法。

      //view's constructor
      setWillNotDraw(false);
      setLayerType(LAYER_TYPE_SOFTWARE, null);
      
      //view's onDraw
      Paint TransparentPaint = new Paint();
      TransparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
      canvas.drawRect(0, 0, width, height, TransparentPaint); 
      

      【讨论】:

      • 它的工作,但在那之后我的画不再可见
      • @DynoCris 也许您使用的是同一个 Paint 对象!?尝试一个新的,不要忘记点赞。
      • 这是我的代码pastebin.com/GWBdtUP5,当我再次尝试绘制时,我没有看到更不幸的线条。但画布很清晰。
      • @DynoCris 请把LAYER_TYPE_HARDWARE改成LAYER_TYPE_SOFTWARE
      • 哦,真的,这是我的错。但不幸的是,它并没有帮助我。
      【解决方案14】:

      尝试在一个activity的onPause()处移除view并添加onRestart()

      LayoutYouAddedYourView.addView(YourCustomView); LayoutYouAddedYourView.removeView(YourCustomView);

      添加视图的那一刻,onDraw() 方法就会被调用。

      YourCustomView,是一个扩展 View 类的类。

      【讨论】:

        【解决方案15】:

        就我而言,我将画布绘制成线性布局。 再次清理和重绘:

            LinearLayout linearLayout = findViewById(R.id.myCanvas);
            linearLayout.removeAllViews();
        

        然后,我用新值调用类:

            Lienzo fondo = new Lienzo(this,items);
            linearLayout.addView(fondo);
        

        这是 Lienzo 类:

        class Lienzo extends View {
            Paint paint;
            RectF contenedor;
            Path path;
            ArrayList<Items>elementos;
        
            public Lienzo(Context context,ArrayList<Items> elementos) {
                super(context);
                this.elementos=elementos;
                init();
            }
        
            private void init() {
                path=new Path();
                paint = new Paint();
                contenedor = new RectF();
                paint.setStyle(Paint.Style.FILL);
            }
        
        
            @Override
            protected void onDraw(Canvas canvas) {
                super.onDraw(canvas);
        
                contenedor.left = oneValue;
                contenedor.top = anotherValue;
                contenedor.right = anotherValue;
                contenedor.bottom = anotherValue;
        
                float angulo = -90; //starts drawing at 12 o'clock
                //total= sum of all element values
                for (int i=0;i<elementos.size();i++){
                    if (elementos.get(i).angulo!=0 && elementos.get(i).visible){
                        paint.setColor(elementos.get(i).backColor);
                        canvas.drawArc(contenedor,angulo,(float)(elementos.get(i).value*360)/total,true,paint);
        
                        angulo+=(float)(elementos.get(i).value*360)/total;
                    }
                } //for example
            }
        }
        

        【讨论】:

          【解决方案16】:

          在我的情况下,每次创建画布都对我有用,即使它对内存不友好

          Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.image);
          imageBitmap = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), bm.getConfig());
          canvas = new Canvas(imageBitmap);
          canvas.drawBitmap(bm, 0, 0, null);
          

          【讨论】:

            【解决方案17】:

            以下内容对我有用:

            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.SCREEN);
            

            【讨论】:

              【解决方案18】:

              我找到了解决办法。

              PaintView 类:

              public void clear() {
                  mPath.reset();
                  mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
                  paths.clear();
              }
              

              和 MainActivity:

                clear_canvas_.setOnClickListener(new View.OnClickListener() {
                      @Override
                      public void onClick(View view) {
                          paintView.clear();
                      }
                  });
              

              【讨论】:

                【解决方案19】:

                您的第一个要求,如何清除或重绘整个画布 - 答案 - 使用 canvas.drawColor(color.Black) 方法以黑色或您指定的任何颜色清除屏幕。

                您的第二个要求,如何更新屏幕的一部分 - 回答 - 例如,如果您想在屏幕上保持所有其他内容不变,但在屏幕的一小块区域中显示一个整数(比如计数器),该整数在每五秒钟。然后使用 canvas.drawrect 方法通过指定左上右下并绘制来绘制那个小区域。然后计算您的计数器值(使用 postdalayed 5 秒等,就像 Handler.postDelayed(Runnable_Object, 5000);) ,将其转换为文本字符串,计算这个小矩形中的 x 和 y 坐标并使用文本视图显示变化计数器值。

                【讨论】:

                  【解决方案20】:

                  我必须使用单独的绘图通道来清除画布(锁定、绘制和解锁):

                  Canvas canvas = null;
                  try {
                      canvas = holder.lockCanvas();
                      if (canvas == null) {
                          // exit drawing thread
                          break;
                      }
                      canvas.drawColor(colorToClearFromCanvas, PorterDuff.Mode.CLEAR);
                  } finally {
                      if (canvas != null) {
                          holder.unlockCanvasAndPost(canvas);
                      }
                  }
                  

                  【讨论】:

                    【解决方案21】:

                    打电话就行了

                    canvas.drawColor(Color.TRANSPARENT)

                    【讨论】:

                    • 这不起作用,因为它只会在当前剪辑的顶部绘制透明度......实际上什么都不做。但是,您可以更改 Porter-Duff 传输模式以达到所需的效果:Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)。如果我没记错的话,颜色实际上可以是任何东西(不一定是透明的),因为PorterDuff.Mode.CLEAR 只会清除当前剪辑(就像在画布上打孔一样)。
                    猜你喜欢
                    • 1970-01-01
                    • 2013-03-16
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2012-07-26
                    • 2013-03-28
                    • 1970-01-01
                    相关资源
                    最近更新 更多