Android, canvas: Làm cách nào để xóa (xóa nội dung của) canvas (= bitmaps), sống trong surfaceView?


93

Để tạo một trò chơi đơn giản, tôi đã sử dụng một mẫu vẽ canvas với bitmap như sau:

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

(Khung được định nghĩa trong "run ()" / SurfaceView nằm trong GameThread.)

Câu hỏi đầu tiên của tôi là làm cách nào để xóa (hoặc vẽ lại) toàn bộ canvas cho một bố cục mới?
Thứ hai, làm cách nào tôi có thể cập nhật chỉ một phần của màn hình?

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

Câu trả lời:


77

Làm cách nào để xóa (hoặc vẽ lại) canvas TOÀN BỘ để có bố cục mới (= thử trong trò chơi)?

Chỉ cần gọi Canvas.drawColor(Color.BLACK), hoặc bất kỳ màu nào bạn muốn xóa Canvas.

Và: làm thế nào tôi có thể cập nhật chỉ một phần của màn hình?

Không có phương pháp nào chỉ cập nhật một "phần của màn hình" vì hệ điều hành Android đang vẽ lại mọi pixel khi cập nhật màn hình. Tuy nhiên, khi bạn không xóa các bản vẽ cũ trên của mình Canvas, các bản vẽ cũ vẫn còn trên bề mặt và đó có thể là một cách để "chỉ cập nhật một phần" của màn hình.

Vì vậy, nếu bạn muốn "cập nhật một phần của màn hình", chỉ cần tránh Canvas.drawColor()phương thức gọi .


4
Không. Bạn phải vẽ lại mọi pixel của bề mặt ở mỗi lần lặp lại.
Guillaume Brunerie

2
@Viktor Lannér: Nếu bạn gọi mSurfaceHolder.unlockCanvasAndPost(c)và sau đó c = mSurfaceHolder.lockCanvas(null), thì cái mới ckhông chứa những thứ giống như cái trước c. Bạn không thể cập nhật chỉ một phần của SurfaceView, đó là những gì OP đã hỏi tôi đoán.
Guillaume Brunerie

1
@Guillaume Brunerie: Đúng, nhưng không phải tất cả. Bạn không thể cập nhật một phần của màn hình, như tôi đã viết. Nhưng bạn có thể giữ các bản vẽ cũ trên màn hình, điều này sẽ có tác dụng chỉ là "cập nhật một phần của màn hình". Hãy tự mình thử trong một ứng dụng mẫu. Canvasgiữ các bản vẽ cũ.
Wroclai

2
GẶP LẠI TÔI !!! Tôi đã khởi tạo mảng của mình CHỈ MỘT LẦN khi trò chơi bắt đầu KHÔNG ở những lần bắt đầu lại liên tiếp - vì vậy TẤT CẢ các quân CŨ vẫn "trên màn hình" - chúng chỉ được vẽ lại !!! TÔI RẤT XIN LỖI vì sự "câm" của tôi! Cám ơn hai bạn !!!
samClem

1
Sau đó, điều này có thể là do Hình nền động không hoạt động theo cách giống như SurfaceView thông thường. Dù sao, @samClem: luôn vẽ lại mọi pixel của Canvas tại mỗi khung hình (như đã nêu trong tài liệu) nếu không bạn sẽ có hiện tượng nhấp nháy lạ.
Guillaume Brunerie

278

Vẽ màu trong suốt với chế độ xóa PorterDuff thực hiện thủ thuật cho những gì tôi muốn.

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

2
Điều này sẽ hoạt động, nhưng dường như đã bị lỗi trong Sử dụng 4.4 (ít nhất là trên N7.2) Bitmap#eraseColor(Color.TRANSPARENT), như trong câu trả lời của HeMac bên dưới.
nmr

3
Trong thực tế, Color.TRANSPARENTlà không cần thiết. PorterDuff.Mode.CLEARlà hoàn toàn đủ cho một ARGB_8888bitmap có nghĩa là đặt alpha và màu thành [0, 0]. Một cách khác là sử dụng Color.TRANSPARENTvới PorterDuff.Mode.SRC.
jiasli

1
Đây không phải là làm việc cho tôi để lại một bề mặt trống thay vì Transparent
Bohara pallav

31

Tìm thấy điều này trong các nhóm của Google và điều này đã làm việc cho tôi ..

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

Thao tác này sẽ xóa các hình chữ nhật trong bản vẽ, v.v. trong khi vẫn giữ bản đồ bit đã đặt ..


4
Điều này mang lại cho tôi một vùng màu đen - không phải bitmap mà tôi có đằng sau nó :(
slott

Nó rõ ràng là tốt, nhưng bitmap cũng bị loại bỏ như bạn đã nói.
Omar Rehman

trong stackoverflow.com/questions/9691985/… được giải thích cách vẽ hình chữ nhật bitmap trên một số hình chữ nhật của canvas. Thay đổi hình ảnh trong hình chữ nhật như vậy sẽ hoạt động: học nội dung trước đó, vẽ hình ảnh mới
Martin

18

Tôi đã thử câu trả lời của @mobistry:

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

Nhưng nó không hiệu quả với tôi.

Giải pháp, đối với tôi, là:

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

Có thể một số người có cùng một vấn đề.


4
Nhân với minh bạch là câu trả lời. Nếu không, nó có thể trở thành màu đen trên một số thiết bị.
Andreas Rudolph

17

sử dụng phương pháp đặt lại của lớp Path

Path.reset();

đây thực sự là câu trả lời tốt nhất nếu bạn sử dụng một đường dẫn. những cái khác có thể khiến người dùng bị màn hình đen. cảm ơn.
j2emanue

12
mBitmap.eraseColor(Color.TRANSPARENT);

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

2
Làm thế nào là điều này là chính xác nhất? tại sao lại sử dụng bitmap trong khi bạn chỉ có thể drawColor?
RichieHH

Mặc dù điều này có thể không hoạt động đối với người đăng ban đầu (họ không nhất thiết phải có quyền truy cập vào bitmap), điều này chắc chắn hữu ích để xóa nội dung của bitmap khi có sẵn. Trong những trường hợp đó, chỉ dòng đầu tiên được yêu cầu. Kỹ thuật hữu ích, +1
đứng đầu trong các mã.

4

vui lòng dán đoạn mã dưới đây vào hàm tạo lớp mở rộng surfaceview .............

mã hóa nhà xây dựng

    SurfaceHolder holder = getHolder();
    holder.addCallback(this);

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

mã hóa 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" />

thử mã trên ...


Holder.setFormat (PixelFormat.TRANSPARENT); Nó làm việc cho tôi.
Aaron Lee,

3

Đây là mã của một ví dụ tối thiểu cho thấy rằng bạn luôn phải vẽ lại mọi pixel của Canvas tại mỗi khung.

Hoạt động này vẽ một Bitmap mới mỗi giây trên SurfaceView mà không cần xóa màn hình trước đó. Nếu bạn kiểm tra nó, bạn sẽ thấy rằng bitmap không phải lúc nào cũng được ghi vào cùng một bộ đệm và màn hình sẽ xen kẽ giữa hai bộ đệm.

Tôi đã thử nghiệm nó trên điện thoại của mình (Nexus S, Android 2.3.3) và trên trình giả lập (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();
                }
            }
        }   
    }
}

Chà, có vẻ như bạn đã nhầm, hãy xem ảnh chụp màn hình của tôi ở đây: img12.imageshack.us/i/devicey.png/# Khi bạn thêm độ trễ như một giây, bộ đệm kép được chú ý nhiều hơn, nhưng (!) vẫn còn các bản vẽ trước đó trên màn hình. Ngoài ra, mã của bạn sai: nó phải như vậy SurfaceHolder.Callback, không chỉ Callback.
Wroclai

1
Tôi nghĩ bạn không hiểu ý tôi. Điều người ta có thể mong đợi là sự khác biệt giữa khung hình n và khung hình n + 1 là có thêm một Bitmap. Nhưng điều này hoàn toàn sai lầm, giữa frame n và frame n + 2 có thêm một Bitmap nữa , nhưng frame n và frame n + 1 hoàn toàn không liên quan dù mình vừa thêm Bitmap.
Guillaume Brunerie

Không, tôi hoàn toàn hiểu bạn. Nhưng, như bạn thấy, mã của bạn không hoạt động. Sau một lúc, màn hình đầy các biểu tượng. Do đó, chúng ta cần xóa Canvasnếu chúng ta muốn vẽ lại toàn bộ màn hình. Điều đó làm cho tuyên bố của tôi về "bản vẽ trước đây" là đúng. Các Canvasbản vẽ trước đó vẫn giữ.
Wroclai

4
Có nếu bạn muốn, a Canvasgiữ lại các bản vẽ trước đó. Nhưng điều này là hoàn toàn vô ích, vấn đề là khi sử dụng lockCanvas()bạn không biết những “bản vẽ trước” đó là gì, và không thể giả định gì về chúng. Có lẽ rằng nếu có hai hoạt động với SurfaceView có cùng kích thước, chúng sẽ dùng chung Canvas. Có lẽ rằng bạn nhận được một phần RAM chưa được khởi tạo với các byte ngẫu nhiên trong đó. Có lẽ rằng luôn có logo của Google được viết trong Canvas. Bạn không thể biết. Bất kỳ ứng dụng nào không vẽ từng pixel lockCanvas(null)đều bị hỏng.
Guillaume Brunerie

1
@GuillaumeBrunerie 2,5 năm sau thực tế là tôi đã xem bài đăng của bạn. Tài liệu chính thức của Android hỗ trợ lời khuyên của bạn về "vẽ từng pixel". Chỉ có một trường hợp trong đó một phần của canvas được đảm bảo vẫn ở đó với lệnh gọi tiếp theo tới lockCanvas (). Tham khảo tài liệu Android về SurfaceHolder và hai phương thức lockCanvas () của nó. developer.android.com/reference/android/view/...developer.android.com/reference/android/view/...
UpLate

3

Đối với tôi, gọi điện Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)hoặc một cái gì đó tương tự sẽ chỉ hoạt động sau khi tôi chạm vào màn hình. VẬY, tôi sẽ gọi dòng mã trên nhưng màn hình sẽ chỉ rõ ràng sau khi tôi chạm vào màn hình. Vì vậy, những gì làm việc cho tôi là gọi invalidate()theo sau init()là gọi tại thời điểm tạo để khởi tạo chế độ xem.

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
Vui lòng thêm giải thích về cách mã của bạn giải quyết vấn đề. Này được gắn cờ là bài chất lượng thấp - Từ xét
Suraj Rao

1

Xóa trên Canvas trong java android cũng tương tự như xóa HTML Canvas thông qua javascript với globalCompositeOperation. Logic tương tự.

U sẽ chọn logic DST_OUT (Destination Out).

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

Lưu ý: DST_OUT hữu ích hơn vì nó có thể xóa 50% nếu màu sơn có 50% alpha. Vì vậy, để rõ ràng hoàn toàn đến trong suốt, alpha của màu phải là 100%. Nên áp dụng paint.setColor (Color.WHITE). Và đảm bảo rằng định dạng hình ảnh canvas là RGBA_8888.

Sau khi xóa, quay lại bản vẽ bình thường với SRC_OVER (Nguồn Over).

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

Cập nhật màn hình hiển thị khu vực nhỏ theo nghĩa đen sẽ cần truy cập phần cứng đồ họa và nó có thể không được hỗ trợ.

Gần nhất để có hiệu suất cao nhất là sử dụng nhiều lớp hình ảnh.


Bạn có thể giúp tôi ở đây? Yours là giải pháp duy nhất đã đề ra cho trường hợp của tôi vì vậy tôi lên bình chọn, đó chỉ là một vấn đề nhỏ mà cần phải được sắp xếp ra
hamza khan

Tôi đang sử dụng canvas với giá đỡ bề mặt, vì vậy tôi làm điều này để xóa canvas (canvas lấy từ bề mặt giữ): paint !!. SetXfermode (PorterDuffXfermode (PorterDuff.Mode.DST_OUT)) canvas !!. DrawPaint (paint !!) surfaceHolder! ! .unlockCanvasAndPost (canvas) thì để có thể vẽ lại nó: paint !!. setXfermode (PorterDuffXfermode (PorterDuff.Mode.SRC)) canvas !!. drawPaint (paint !!) surfaceHolder !!. unlockCanvasAndPost (canvas) bây giờ là vấn đề là sau khi xóa canvas như thế này, khi tôi vẽ một bitmap trên canvas, nó được vẽ sau một lần nhấp nháy Màu, điều này thật khó chịu, bạn có thể chỉ cho tôi cách ở đây được không? @phnghue
hamza khan

@hamza khan Bạn đã thử với SRC_OVER chưa? Bởi vì SRC_OVER là mặc định bình thường. Logic SRC đang ghi đè dữ liệu pixel cũ, nó thay thế alpha!
phnghue

0

Đừng quên gọi voidate ();

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

Tại sao bạn sẽ gọi invalidatesau khi vẽ một cái gì đó?
RalfFriedl

0

Cố gắng xóa chế độ xem tại onPause () của một hoạt động và thêm onRestart ()

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

Thời điểm bạn thêm chế độ xem của mình, phương thức onDraw () sẽ được gọi.

YourCustomView, là một lớp mở rộng lớp View.


0

Trong trường hợp của tôi, tôi vẽ canvas của mình thành tuyến tính. Để làm sạch và vẽ lại một lần nữa:

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

và sau đó, tôi gọi lớp với các giá trị mới:

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

Đây là lớp 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

Với cách tiếp cận sau, bạn có thể xóa toàn bộ canvas hoặc chỉ một phần của nó.
Xin đừng quên tắt Tăng tốc phần cứng vì PorterDuff.Mode.CLEAR không hoạt động với tăng tốc phần cứng và cuối cùng sẽ gọi setWillNotDraw(false)vì chúng tôi ghi đè onDrawphương thức.

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

nó hoạt động, nhưng sau đó các bản vẽ của tôi không hiển thị nhiều hơn
Dyno Cris

1
@DynoCris Có lẽ bạn đang sử dụng cùng một đối tượng Paint !? Hãy thử với một cái mới và đừng quên ủng hộ.
ucMedia

đó là mã của tôi pastebin.com/GWBdtUP5 khi tôi cố gắng vẽ lại, tôi không thấy các đường thẳng không rõ ràng hơn. nhưng lonvas là rõ ràng.
Dyno Cris

1
@DynoCris Vui lòng đổi LAYER_TYPE_HARDWAREthànhLAYER_TYPE_SOFTWARE
ucMedia

1
ồ, thực sự, đó là sai lầm của tôi. Nhưng dù sao nó cũng không giúp ích được gì cho tôi.
Dyno Cris

-1

Yêu cầu đầu tiên của bạn, cách xóa hoặc vẽ lại toàn bộ canvas - Trả lời - sử dụng phương thức canvas.drawColor (color.Black) để xóa màn hình bằng màu đen hoặc bất kỳ màu gì bạn chỉ định.

Yêu cầu thứ hai của bạn, làm thế nào để cập nhật một phần của màn hình - Trả lời - ví dụ: nếu bạn muốn giữ tất cả những thứ khác không thay đổi trên màn hình nhưng trong một vùng nhỏ của màn hình để hiển thị một số nguyên (giả sử là bộ đếm) tăng sau mỗi năm giây. sau đó sử dụng phương thức canvas.drawrect để vẽ vùng nhỏ đó bằng cách chỉ định bên trái trên cùng bên phải và tô. sau đó tính toán giá trị bộ đếm của bạn (sử dụng postdalayed trong 5 giây, v.v., giống như Handler.postDelayed (Runnable_Object, 5000);), chuyển đổi nó thành chuỗi văn bản, tính toán tọa độ x và y trong trực tràng nhỏ này và sử dụng chế độ xem văn bản để hiển thị sự thay đổi giá trị truy cập.


-1

Tôi phải sử dụng một pass vẽ riêng để xóa canvas (khóa, vẽ và mở khóa):

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

Chỉ cần gọi

canvas.drawColor (Color.TRANSPARENT)


16
Điều đó không hiệu quả bởi vì nó sẽ chỉ vẽ minh bạch trên đầu clip hiện tại ... không làm gì cả. Bạn có thể, tuy nhiên, thay đổi chế độ chuyển Porter-Duff để đạt được hiệu quả mong muốn: Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR). Nếu tôi không nhầm, màu thực sự có thể là bất cứ thứ gì (không nhất thiết phải TRUYỀN CẢM HỨNG) vì PorterDuff.Mode.CLEARsẽ xóa clip hiện tại (giống như đục một lỗ trên canvas).
ashughes
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.