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


92

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

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

Answers:


77

如何清除(或重新绘制)整个画布以使用新布局(=在游戏中尝试)?

只需致电Canvas.drawColor(Color.BLACK),或您要清除颜色的任何颜色即可Canvas

并且:如何仅更新屏幕的一部分?

没有这样的方法仅更新“屏幕的一部分”,因为Android OS在更新屏幕时会重绘每个像素。但是,当您不清除上的旧图形时Canvas,旧图形仍保留在表面上,这可能是“仅更新屏幕的一部分”的一种方法。

因此,如果要“更新屏幕的一部分”,只需避免调用Canvas.drawColor()方法。


4
不,如果不在表面上绘制每个像素,将会得到非常奇怪的结果(因为通过交换指针实现了双缓冲,因此在未绘制的部分中,您将不会看到之前的内容)。您必须在每次迭代时重新绘制曲面的每个像素。
Guillaume Brunerie

2
@Viktor兰纳:如果你打电话mSurfaceHolder.unlockCanvasAndPost(c),然后c = mSurfaceHolder.lockCanvas(null),那么新的c不包含同样的事情以前c。您不能仅更新SurfaceView的一部分,我猜这就是OP要求的。
Guillaume Brunerie

1
@Guillaume Brunerie:是的,但不是全部。正如我所写的,您无法更新屏幕的一部分。但是您可以将旧的图形保留在屏幕上,这样的效果仅仅是“更新屏幕的一部分”。在示例应用程序中自己尝试。Canvas保留旧图纸。
Wroclai 2011年

2
羞辱我!我只是在游戏开始时才初始化数组,而不是在连续重启时初始化数组-所以所有旧块都保持在“屏幕上”-它们只是被重新绘制!很抱歉我的“哑巴”!感谢你们俩 !!!
samClem 2011年

1
则可能是因为动态壁纸与常规SurfaceView的工作方式不同。无论如何,@samClem:总是在每帧上重画Canvas的每个像素(如文档中所述),否则您会产生奇怪的闪烁。
Guillaume Brunerie

278

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

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

2
这应该可以,但是似乎在4.4(至少在N7.2上)使用中存在错误Bitmap#eraseColor(Color.TRANSPARENT),如以下HeMac的答案所示。
nmr 2014年

3
其实Color.TRANSPARENT是没有必要的。PorterDuff.Mode.CLEAR对于一个ARGB_8888位图来说已经足够了,这意味着将Alpha和颜色设置为[0,0]。另一种方法是使用Color.TRANSPARENTPorterDuff.Mode.SRC
贾斯利2014年

1
这对我不起作用,而是留下空白表面而不是透明表面
pallav bohara

31

在Google网上论坛中找到了这个,对我有用。

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

这将删除图形矩形等,同时保留设置的位图。


4
这给了我一个黑色区域-而不是我背后的位图:(
slott

它清除得很好,但是与您所说的不同,位图也被删除了。
奥马尔·雷曼

stackoverflow.com/questions/9691985/…中说明了如何在画布的某个矩形上绘制位图的矩形。更改矩形中的图像即可:保留先前的内容,绘制新图像
Martin

18

我尝试了@mobistry的答案:

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

但这对我不起作用。

对我来说,解决方案是:

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

也许有人遇到同样的问题。


4
答案就是透明。否则,在某些设备上可能会变成黑色。
安德烈亚斯·鲁道夫

16

使用Path类的reset方法

Path.reset();

如果您使用路径,这确实是最佳答案。其他的可能会使用户黑屏。谢谢。
j2emanue

12
mBitmap.eraseColor(Color.TRANSPARENT);

canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);

2
这最正确吗?当您可以只使用drawColor时,为什么还要使用位图?
RichieHH 2014年

尽管这对于原始海报可能不起作用(他们不一定有权访问位图),但对于清除位图的内容(如果可用)绝对有用。在这些情况下,仅需要第一行。有用的技术+1
进入代码

你能写完整的代码吗?
Dyno Cris

4

请在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); 这个对我有用。
亚伦·李

3

这是一个最小示例的代码,该示例显示始终必须在每个帧处重新绘制Canvas的每个像素。

此活动每秒在SurfaceView上绘制一个新的位图,而无需清除屏幕。如果您对其进行测试,您将看到位图并不总是写入同一缓冲区,并且屏幕将在两个缓冲区之间交替显示。

我在手机(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
Wroclai 2011年

1
我想你不明白我的意思。可以预期的是,帧n和帧n + 1之间的区别是还有一个位图。但是,这是完全错误的,有框之间多了一个位图ñ和帧N + 2,但帧ñ和帧N + 1的,即使我只是增加了一个位图完全无关。
Guillaume Brunerie

不,我完全了解您。但是,如您所见,您的代码不起作用。一段时间后,屏幕上充满了图标。因此,Canvas如果要完全重绘整个屏幕,则需要清除。这使我对“以前的图纸”的陈述是正确的。在Canvas保持以前的图纸。
Wroclai 2011年

4
是的,如果您愿意,可以Canvas保留以前的图纸。但这是完全没有用的,问题是,当您使用时,lockCanvas()您不知道这些“先前的图纸”是什么,并且无法对它们作任何假设。也许如果两个活动的SurfaceView大小相同,它们将共享相同的Canvas。也许您会得到一块未初始化的RAM,其中包含随机字节。也许总是在Canvas中写有Google徽标。你不知道 任何未绘制完所有像素的应用程序都lockCanvas(null)将损坏。
Guillaume Brunerie

1
@GuillaumeBrunerie在我遇到您的帖子后2.5年。Android官方文档为您提供有关“绘制每个像素”的建议。只有一种情况可以保证画布的一部分仍然存在,并随后调用lockCanvas()。有关SurfaceHolder及其两个lockCanvas()方法,请参阅Android文档。developer.android.com/reference/android/view/...developer.android.com/reference/android/view/...
UpLate

3

对我来说,呼叫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();
}

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

2
请添加有关您的代码如何解决此问题的说明。这是标记为低质量的帖子- 来自评论
Suraj Rao

1

在Java android中的Canvas上擦除与通过globalCompositeOperation通过javascript擦除HTML Canvas相似。逻辑是相似的。

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

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

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

擦除后,使用SRC_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)现在是问题所在是在这样清除画布后,当我在画布上绘制位图时,在眨眼的Color之后绘制的,这很烦人,您能告诉我这里的方法吗?@phnghue
hamza汗

@hamza khan您是否尝试过SRC_OVER?因为SRC_OVER是默认的普通模式。SRC逻辑会覆盖旧的像素数据,它将替换为alpha!
phnghue


0

尝试删除活动的onPause()上的视图并添加onRestart()

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

添加视图后,将调用onDraw()方法。

YourCustomView是扩展View类的类。


0

就我而言,我将画布绘制为linearlayout。要清洁并重新绘制:

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

0

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

当我尝试再次绘制时,这是我的代码pastebin.com/GWBdtUP5,我看不到更多的线条。但是画布很清楚。
Dyno Cris

@DynoCris请更改LAYER_TYPE_HARDWARELAYER_TYPE_SOFTWARE
ucMedia

-1

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

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


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

-3

刚打电话

canvas.drawColor(Color.TRANSPARENT)


16
那是行不通的,因为它只会在当前剪辑的顶部绘制透明度...实际上什么也没做。但是,您可以更改Porter-Duff传输模式以实现所需的效果:Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)。如果我没记错的话,颜色实际上可以是任何颜色(不必是透明的),因为它PorterDuff.Mode.CLEAR可以清除当前剪辑(例如在画布上打孔)。
ashughes 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.