Android, kanvas: Bagaimana cara menghapus (menghapus konten dari) kanvas (= bitmap), yang tinggal di surfaceView?

96

Untuk membuat game sederhana, saya menggunakan template yang menggambar kanvas dengan bitmap seperti ini:

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

(Kanvas didefinisikan dalam "run ()" / SurfaceView ada di GameThread.)

Pertanyaan pertama saya adalah bagaimana cara menghapus (atau menggambar ulang) seluruh kanvas untuk tata letak baru?
Kedua, bagaimana cara memperbarui hanya sebagian dari layar?

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

Jawaban:

78

Bagaimana cara menghapus (atau menggambar ulang) kanvas SELURUH untuk tata letak baru (= coba permainan)?

Panggil saja Canvas.drawColor(Color.BLACK), atau warna apa pun yang ingin Anda bersihkan Canvas.

Dan: bagaimana cara memperbarui hanya sebagian dari layar?

Tidak ada metode yang hanya memperbarui "bagian dari layar" karena OS Android menggambar ulang setiap piksel saat memperbarui layar. Tapi, saat Anda tidak menghapus gambar lama di Anda Canvas, gambar lama masih ada di permukaan dan itu mungkin salah satu cara untuk "memperbarui sebagian" layar.

Jadi, jika Anda ingin "mengupdate bagian dari layar", hindari saja Canvas.drawColor()metode panggilan .

Wroclai
sumber
4
Tidak, jika Anda tidak menggambar setiap piksel di permukaan, Anda akan mendapatkan hasil yang sangat aneh (karena buffering ganda dicapai dengan menukar pointer, jadi di bagian di mana Anda tidak menggambar, Anda tidak akan melihat apa yang ada sebelumnya). Anda harus menggambar ulang setiap piksel permukaan pada setiap iterasi.
Guillaume Brunerie
2
@Viktor Lannér: Jika Anda memanggil mSurfaceHolder.unlockCanvasAndPost(c)dan kemudian c = mSurfaceHolder.lockCanvas(null), maka yang baru ctidak berisi hal yang sama seperti sebelumnya c. Anda tidak dapat memperbarui hanya sebagian dari SurfaceView, yang menurut saya diminta OP.
Guillaume Brunerie
1
@ Guillaume Brunerie: Benar, tapi tidak semua. Anda tidak dapat memperbarui sebagian layar, seperti yang saya tulis. Tetapi Anda dapat menyimpan gambar-gambar lama di layar, yang akan memiliki efek "memperbarui sebagian layar". Cobalah sendiri dalam contoh aplikasi. Canvasmenyimpan gambar lama.
Wroclai
2
MEMALUKAN UNTUKKU !!! Saya melakukan inisialisasi array saya hanya SEKALI saat permainan dimulai BUKAN pada re-start berturut-turut - jadi SEMUA bidak LAMA tetap "di layar" - mereka baru saja ditarik !!! SAYA SANGAT MAAF atas "kebodohan" saya! Terima kasih untuk kalian berdua !!!
samClem
1
Maka ini mungkin karena Wallpaper animasi tidak berfungsi dengan cara yang sama seperti SurfaceView biasa. Bagaimanapun, @samClem: selalu menggambar ulang setiap piksel Canvas di setiap frame (seperti yang dinyatakan dalam dokumen) atau Anda akan mengalami kedipan aneh.
Guillaume Brunerie
280

Menggambar warna transparan dengan mode jelas PorterDuff melakukan trik untuk apa yang saya inginkan.

Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
Sileria
sumber
2
Ini harus bekerja, tetapi tampaknya disadap di 4.4 (setidaknya di N7.2) Gunakan Bitmap#eraseColor(Color.TRANSPARENT), seperti dalam jawaban HeMac di bawah ini.
nmr
4
Padahal, Color.TRANSPARENTitu tidak perlu. PorterDuff.Mode.CLEARbenar-benar cukup untuk ARGB_8888bitmap yang berarti menyetel alfa dan warna ke [0, 0]. Cara lain adalah dengan menggunakan Color.TRANSPARENTdengan PorterDuff.Mode.SRC.
jiasli
1
Ini tidak berhasil karena saya meninggalkan permukaan kosong alih-alih Transparan
pallav bohara
31

Menemukan ini di grup google dan ini berhasil untuk saya ..

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

Ini menghapus gambar persegi panjang, dll. Sambil tetap menyetel bitmap ..

Rukmal Dias
sumber
4
Ini memberi saya area hitam - bukan bitmap yang saya miliki di belakangnya :(
slott
Ini jelas baik-baik saja, tetapi bitmap juga dihapus tidak seperti yang Anda katakan.
Omar Rehman
di stackoverflow.com/questions/9691985/… dijelaskan cara menggambar persegi panjang dari bitmap pada beberapa persegi panjang kanvas. Ubah gambar dalam persegi panjang sehingga berfungsi: lear konten sebelumnya, gambar gambar baru
Martin
18

menggunakan metode reset kelas Path

Path.reset();
Rakesh
sumber
ini benar-benar jawaban terbaik jika Anda menggunakan jalur. yang lain dapat membuat pengguna layar hitam. Terima kasih.
j2emanue
18

Saya mencoba jawaban @mobistry:

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

Tapi itu tidak berhasil untukku.

Solusinya, bagi saya, adalah:

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

Mungkin seseorang memiliki masalah yang sama.

Derzu
sumber
4
Mengalikan dengan transparansi adalah jawabannya. Jika tidak, itu bisa berakhir berwarna hitam di beberapa perangkat.
Andreas Rudolph
12
mBitmap.eraseColor(Color.TRANSPARENT);

canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
heMac
sumber
2
bagaimana ini yang paling benar? mengapa menggunakan bitmap sama sekali saat Anda bisa menggambarColor?
RichieHH
Meskipun ini mungkin tidak berfungsi untuk poster asli (mereka tidak selalu memiliki akses ke bitmap), ini pasti berguna untuk membersihkan konten bitmap saat itu tersedia. Dalam kasus tersebut, hanya baris pertama yang diperlukan. Teknik yang berguna, +1
kode
4

silakan tempel kode di bawah ini pada surfaceview memperpanjang konstruktor kelas .............

pengkodean konstruktor

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

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

pengkodean 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" />

coba kode di atas ...

Hemant Chand Dungriyal
sumber
holder.setFormat (PixelFormat.TRANSPARENT); Ini bekerja untuk saya.
Aaron Lee
3

Berikut adalah kode contoh minimal yang menunjukkan bahwa Anda harus selalu menggambar ulang setiap piksel Canvas di setiap frame.

Aktivitas ini menggambar Bitmap baru setiap detik di SurfaceView, tanpa membersihkan layar sebelumnya. Jika Anda mengujinya, Anda akan melihat bahwa bitmap tidak selalu ditulis ke buffer yang sama, dan layar akan bergantian di antara kedua buffer tersebut.

Saya mengujinya di ponsel saya (Nexus S, Android 2.3.3), dan di emulator (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();
                }
            }
        }   
    }
}
Guillaume Brunerie
sumber
Sepertinya Anda salah, lihat tangkapan layar saya di sini: img12.imageshack.us/i/devicey.png/# Saat Anda menambahkan penundaan seperti satu detik, buffering ganda lebih diperhatikan, tetapi (!) masih ada gambar sebelumnya di layar. Selain itu, kode Anda salah: seharusnya SurfaceHolder.Callback, bukan hanya Callback.
Wroclai
1
Saya pikir Anda tidak mengerti apa yang saya maksud. Apa yang bisa diharapkan adalah bahwa perbedaan antara frame n dan frame n + 1 adalah ada satu Bitmap lagi. Tapi ini sepenuhnya salah, ada satu Bitmap lagi antara frame n dan frame n + 2 , tetapi frame n dan frame n + 1 sama sekali tidak terkait bahkan jika saya baru saja menambahkan Bitmap.
Guillaume Brunerie
Tidak, saya sepenuhnya memahami Anda. Namun, seperti yang Anda lihat, kode Anda tidak berfungsi. Setelah beberapa saat, layar penuh dengan ikon. Oleh karena itu, kita perlu menghapus Canvasjika kita ingin menggambar ulang seluruh layar sepenuhnya. Itu membuat pernyataan saya tentang "gambar sebelumnya" menjadi benar. Itu Canvasmenyimpan gambar sebelumnya.
Wroclai
4
Ya jika Anda mau, a Canvaspertahankan gambar sebelumnya. Tapi ini sama sekali tidak berguna, masalahnya adalah ketika Anda menggunakan lockCanvas()Anda tidak tahu apa itu "gambar sebelumnya", dan tidak bisa berasumsi apa-apa tentang mereka. Mungkin jika ada dua aktivitas dengan SurfaceView dengan ukuran yang sama, keduanya akan menggunakan Canvas yang sama. Mungkin Anda mendapatkan sepotong RAM yang tidak diinisialisasi dengan byte acak di dalamnya. Mungkin selalu ada logo Google yang tertulis di Canvas. Anda tidak bisa tahu. Aplikasi apa pun yang tidak menggambar setiap piksel setelahnya lockCanvas(null)rusak.
Guillaume Brunerie
1
@GuillaumeBrunerie 2.5 thn setelah saya menemukan postingan Anda. Dokumentasi resmi Android mendukung saran Anda tentang "menggambar setiap piksel". Hanya ada satu kasus di mana sebagian dari kanvas dijamin masih ada di sana dengan panggilan berikutnya ke lockCanvas (). Lihat dokumentasi Android untuk SurfaceHolder dan dua metode lockCanvas () -nya. developer.android.com/reference/android/view/… dan developer.android.com/reference/android/view/…
UpLate
3

Bagi saya menelepon Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)atau sesuatu yang serupa hanya akan berfungsi setelah saya menyentuh layar. JADI saya akan memanggil baris kode di atas tetapi layar hanya akan bersih setelah saya menyentuh layar. Jadi yang berhasil bagi saya adalah memanggil yang invalidate()diikuti oleh init()yang dipanggil pada saat pembuatan untuk menginisialisasi tampilan.

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();
}
Jason Crosby
sumber
3
canvas.drawColor(Color.TRANSPARENT, Mode.MULTIPLY);
aliakbarian
sumber
2
Tambahkan penjelasan tentang bagaimana kode Anda menyelesaikan masalah. Ini ditandai sebagai kiriman berkualitas rendah - Dari Ulasan
Suraj Rao
1

Menghapus pada Canvas di java android sama seperti menghapus HTML Canvas melalui javascript dengan globalCompositeOperation. Logikanya serupa.

U akan memilih logika DST_OUT (Tujuan Keluar).

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

Catatan: DST_OUT lebih berguna karena dapat menghapus 50% jika warna cat memiliki 50% alpha. Jadi, untuk benar-benar menjadi transparan, alpha warna harus 100%. Disarankan menggunakan paint.setColor (Color.WHITE). Dan pastikan bahwa format gambar kanvas adalah RGBA_8888.

Setelah terhapus, kembali ke gambar normal dengan SRC_OVER (Source Over).

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

Perbarui tampilan area kecil secara harfiah perlu mengakses perangkat keras grafis, dan mungkin tidak didukung.

Yang paling mendekati untuk performa tertinggi adalah menggunakan lapisan multi gambar.

phnghue.dll
sumber
Bisakah kamu membantuku di sini? Solusi Anda adalah satu-satunya solusi yang berhasil untuk kasus saya, jadi saya memilih, hanya ada sedikit masalah yang perlu diselesaikan
hamza khan
Saya menggunakan kanvas dengan dudukan permukaan, jadi saya melakukan ini untuk membersihkan kanvas (kanvas diambil dari dudukan permukaan): cat !!. SetXfermode (PorterDuffXfermode (PorterDuff.Mode.DST_OUT)) kanvas !!. DrawPaint (paint !!) surfaceHolder! ! .unlockCanvasAndPost (kanvas) kemudian dapat menggambar ulang: paint !!. setXfermode (PorterDuffXfermode (PorterDuff.Mode.SRC)) kanvas !!. drawPaint (paint !!) surfaceHolder !!. unlockCanvasAndPost (kanvas) sekarang masalahnya adalah setelah membersihkan kanvas seperti ini, ketika saya menggambar bitmap di atas kanvas, itu digambar setelah sekejap Warna, yang menjengkelkan, dapatkah Anda menunjukkan jalannya ke sini? @phnghue
hamza khan
@hamza khan Sudahkah Anda mencoba dengan SRC_OVER? Karena SRC_OVER adalah default normal. Logika SRC menimpa data piksel lama, ini menggantikan alfa!
phnghue
0

Jangan lupa untuk memanggil invalidate ();

canvas.drawColor(backgroundColor);
invalidate();
path.reset();
jazz chakraborty
sumber
Mengapa Anda menelepon invalidatesetelah menggambar sesuatu?
RalfFriedl
0

Coba hapus tampilan di onPause () dari suatu aktivitas dan tambahkan onRestart ()

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

Saat Anda menambahkan tampilan, metode onDraw () akan dipanggil.

YourCustomView, adalah kelas yang memperluas kelas View.

Rishabh Jain
sumber
0

Dalam kasus saya, saya menggambar kanvas saya menjadi linearlayout. Untuk membersihkan dan menggambar ulang lagi:

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

dan kemudian, saya memanggil kelas dengan nilai baru:

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

Ini adalah kelas 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
    }
}
Carlos Gómez
sumber
0

Dengan pendekatan berikut, Anda dapat menghapus seluruh kanvas atau hanya sebagian saja.
Harap jangan lupa untuk menonaktifkan akselerasi Perangkat Keras karena PorterDuff.Mode.CLEAR tidak berfungsi dengan akselerasi perangkat keras dan akhirnya memanggil setWillNotDraw(false)karena kami mengganti onDrawmetode tersebut.

//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); 
ucMedia
sumber
itu berhasil, tetapi setelah itu hasil imbang saya tidak terlihat lagi
Dyno Cris
1
@DynoCris Mungkin Anda menggunakan objek Paint yang sama !? Coba dengan yang baru, dan jangan lupa upvote ya.
ucMedia
itu kode saya pastebin.com/GWBdtUP5 ketika saya mencoba menggambar lagi, saya tidak melihat garis lebih tidak teratur. tapi cansvas jelas.
Dyno Cris
1
@DynoCris Silakan ubah LAYER_TYPE_HARDWAREkeLAYER_TYPE_SOFTWARE
ucMedia
1
oh, sungguh, itu kesalahanku. Tapi itu tidak membantu saya secara tidak wajar.
Dyno Cris
-1

Persyaratan pertama Anda, cara menghapus atau menggambar ulang seluruh kanvas - Jawaban - gunakan metode canvas.drawColor (color.Black) untuk membersihkan layar dengan warna hitam atau apa pun yang Anda tentukan.

Persyaratan kedua Anda, bagaimana memperbarui bagian layar - Jawab - misalnya jika Anda ingin menyimpan semua hal lain tidak berubah di layar tetapi di area kecil layar untuk menunjukkan bilangan bulat (katakanlah penghitung) yang meningkat setiap lima detik. kemudian gunakan metode canvas.drawrect untuk menggambar area kecil itu dengan menentukan kiri atas kanan bawah dan cat. kemudian hitung nilai penghitung Anda (menggunakan postdalayed selama 5 detik dll., Seperti Handler.postDelayed (Runnable_Object, 5000);), konversikan ke string teks, hitung koordinat x dan y dalam kotak kecil ini dan gunakan tampilan teks untuk menampilkan perubahan nilai counter.

Chandu
sumber
-1

Saya harus menggunakan kartu gambar terpisah untuk membersihkan kanvas (mengunci, menggambar, dan membuka kunci):

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);
    }
}
Farid Z
sumber
-3

Telepon saja

canvas.drawColor (Color.TRANSPARENT)

reznic
sumber
16
Itu tidak berhasil karena itu hanya akan menarik transparansi di atas klip saat ini ... secara efektif tidak melakukan apa-apa. Anda bisa, bagaimanapun, mengubah modus transfer Porter-Duff untuk mencapai efek yang diinginkan: Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR). Kalau tidak salah, warna sebenarnya bisa apa saja (tidak harus TRANSPARAN) karena PorterDuff.Mode.CLEARhanya akan menghapus klip yang sedang diputar (seperti melubangi kanvas).
ashugh