Android,画布:如何清除(删除)位于surfaceView中的画布(=位图)内容?

111
为了制作一个简单的游戏,我使用了一个模板来绘制带有位图的画布,就像这样:

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);  }   }   }       }
21个回答

2
在Java Android中,通过画布进行擦除操作类似于使用javascript在HTML画布上执行的全局合成操作(globalCompositeOperation)。两者的逻辑相似。
您可以选择DST_OUT(目标区域外)逻辑。
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));

注意:DST_OUT更加实用,因为它可以擦除50%的颜色,如果涂料颜色有50%的透明度。所以,要完全清除为透明,颜色的Alpha必须是100%。建议使用paint.setColor(Color.WHITE)。并确保画布图像格式为RGBA_8888。
在擦除后,返回到正常绘图,并使用SRC_OVER(Source Over)。
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));

更新小区域显示实际上需要访问图形硬件,而且可能不被支持。

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


你能帮我吗?你的解决方案是唯一有效的,所以我投了赞成票,只有一个小问题需要解决。 - Hamza Khan
我正在使用带有表面持有者的画布,所以我这样清除画布(画布来自表面持有者): 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
@hamza khan 你尝试过使用SRC_OVER吗?因为SRC_OVER是默认的正常模式。SRC逻辑会覆盖旧像素数据,它会替换alpha! - phnghue

2
使用以下方法,您可以清除整个画布或仅清除其中的一部分。
请不要忘记禁用硬件加速,因为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); 

它能工作,但之后我的绘图不再可见。 - Dyno Cris
1
@DynoCris 也许你正在使用相同的 Paint 对象!尝试使用一个新的,并不要忘记点赞。 - ucMedia
这是我的代码 https://pastebin.com/GWBdtUP5 当我尝试重新绘制时,不幸的是我看不到线条了。但画布是清晰的。 - Dyno Cris
1
@DynoCris 请将 LAYER_TYPE_HARDWARE 更改为 LAYER_TYPE_SOFTWARE - ucMedia
1
哦,真的吗,那是我的错误。但不幸的是,它并没有帮助到我。 - Dyno Cris
@DynoCris 不是很...抱歉。 - ucMedia

1

I found my solution.

PaintView class:

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();
        }
    });

1
请勿忘记调用invalidate()方法;
canvas.drawColor(backgroundColor);
invalidate();
path.reset();

在绘制完某些内容后,为什么要调用 invalidate 函数? - RalfFriedl

0
在我的情况下,每次创建画布对我很有效,尽管它不太友好于内存。
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);

0
尝试在活动的onPause()方法中移除视图,并添加onRestart()方法。

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

当您添加视图时,onDraw() 方法将被调用。
YourCustomView 是一个扩展了 View 类的类。

0

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

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


0

在我的情况下,我将画布绘制到线性布局中。 要清除并重新绘制:

    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
    }
}

-1
我不得不使用单独的绘制过程来清除画布(锁定、绘制和解锁):
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);
    }
}

-1

以下方法适用于我:

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

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