ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

S
Sileria

使用 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 一起使用。
这不适合我留下空白表面而不是透明
W
Wroclai

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

只需调用 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:总是在每一帧重绘画布的每个像素(如文档中所述),否则你会出现奇怪的闪烁。
R
Rukmal Dias

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

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

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


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

使用 Path 类的 reset 方法

Path.reset();

如果您使用路径,这确实是最好的答案。其他的可能会给用户留下黑屏。谢谢。
超级骗子真棒。谢谢。我的情况是通过从用户那里获取触摸事件来绘制带有路径的三角形。所以我想在绘制新三角形的同时清除以前绘制的三角形来模拟它的移动。非常感谢。
对不起,我不明白如何使用这个。如果我在画布上绘制路径,这不会擦除它。那怎么抹掉呢?
D
Derzu

我尝试了@mobistry 的答案:

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

但这对我不起作用。

对我来说,解决方案是:

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

也许有人有同样的问题。


与透明度相乘就是答案。否则,它可能会在某些设备上变成黑色。
d
duggu
mBitmap.eraseColor(Color.TRANSPARENT);

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

这是最正确的?当您可以绘制颜色时,为什么还要使用位图?
尽管这可能不适用于原始海报(他们不一定有权访问位图),但这对于清除可用位图的内容绝对有用。在这些情况下,只需要第一行。有用的技术,+1
B
Buddy
canvas.drawColor(Color.TRANSPARENT, Mode.MULTIPLY);

请添加有关您的代码如何解决问题的说明。这被标记为低质量帖子 - From Review
H
Hemant Chand Dungriyal

请在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);这个对我有用。
G
Guillaume Brunerie

这是一个最小示例的代码,显示您总是必须在每一帧重绘 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帧是完全不相关的,即使我只是加了一个Bitmap。
不,我完全理解你。但是,如您所见,您的代码不起作用。过了一会儿,屏幕上满是图标。因此,如果我们想完全重绘整个屏幕,我们需要清除 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/…
J
Jason Crosby

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

p
phnghue

在 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!
u
ucMedia

使用以下方法,您可以清除整个画布或只是其中的一部分。
请不要忘记禁用硬件加速,因为 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
哦,真的,这是我的错。但不幸的是,它并没有帮助我。
D
Dharman

不要忘记调用 invalidate();

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

为什么在画完东西后要调用 invalidate
C
Chandu

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

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


R
Rishabh Jain

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

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

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

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


C
Carlos Gómez

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

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

n
n-y

就我而言,每次创建画布都对我有用,即使它对内存不友好

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

J
John Doherty

以下对我有用:

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

E
Elikill58

我找到了我的解决方案。

油漆视图类:

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

F
Farid Z

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

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

r
reznic

打电话

canvas.drawColor(颜色。透明)


这不起作用,因为它只会在当前剪辑的顶部绘制透明度......实际上什么都不做。但是,您可以更改 Porter-Duff 传输模式以获得所需的效果:Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)。如果我没记错的话,颜色实际上可以是任何东西(不必是透明的),因为 PorterDuff.Mode.CLEAR 只会清除当前剪辑(就像在画布上打一个洞)。