Android,画布:如何清除(删除)画布(=位图),生活在surfaceView中?

Posted

技术标签:

【中文标题】Android,画布:如何清除(删除)画布(=位图),生活在surfaceView中?【英文标题】:Android, canvas: How do I clear (delete contents of) a canvas (= bitmaps), living in a 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);               

【问题讨论】:

【参考方案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); 

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

【讨论】:

这给了我一个黑色区域——不是我在它后面的位图:( 它清除得很好,但位图也像你说的那样被删除了。 在***.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_
        android:layout_
        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 只会清除当前剪辑(就像在画布上打孔一样)。

以上是关于Android,画布:如何清除(删除)画布(=位图),生活在surfaceView中?的主要内容,如果未能解决你的问题,请参考以下文章

如何让用户在Android中随意绘制长画布?

当其中的移动位图靠近屏幕的右边框时,android会翻译画布坐标?

我如何清除画布? [复制]

“画布:Android Studio中试图绘制太大的位图”问题

如何清除画布以进行重绘

如何设置图像以填充画布