Android, tuval: Bir SurfaceView'da yaşayan bir tuvali (= bitmap'leri) nasıl temizlerim (içeriğini silerim)?


97

Basit bir oyun yapmak için, bunun gibi bitmap'lerle bir tuval çizen bir şablon kullandım:

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 "run ()" içinde tanımlanmıştır / SurfaceView bir GameThread içinde yaşar.)

İlk sorum, yeni bir düzen için tüm tuvali nasıl temizlerim (veya yeniden çizerim) ?
İkincisi, ekranın sadece bir bölümünü nasıl güncelleyebilirim?

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

Yanıtlar:


79

Yeni bir düzen için BÜTÜN tuvali nasıl temizlerim (veya yeniden çizerim) (= oyunda deneyin)?

Sadece arayın Canvas.drawColor(Color.BLACK)veya istediğiniz renkle temizleyin Canvas.

Ve: ekranın sadece bir bölümünü nasıl güncelleyebilirim?

Android OS ekranı güncellerken her pikseli yeniden çizdiği için "ekranın bir bölümünü" güncelleyen böyle bir yöntem yoktur. Ancak, eski çizimlerinizi temizlemediğinizde Canvas, eski çizimler hala yüzeydedir ve bu muhtemelen ekranın "yalnızca bir bölümünü güncellemenin" bir yoludur.

Bu nedenle, "ekranın bir bölümünü güncellemek" istiyorsanız, Canvas.drawColor()yöntemi çağırmaktan kaçının .


4
Hayır, yüzeydeki her pikseli çizmezseniz çok garip sonuçlar elde edersiniz (çünkü çift ara belleğe işaretçilerin değiştirilmesi ile ulaşılır, bu nedenle çizmediğiniz kısımlarda daha önce ne olduğunu göremezsiniz). Sen var her tekrarda yüzeyinin her pikseli yeniden çizmek için.
Guillaume Brunerie

2
@Viktor LANNER: Eğer ararsanız mSurfaceHolder.unlockCanvasAndPost(c)ardından c = mSurfaceHolder.lockCanvas(null), daha sonra yeni cbir önceki ile aynı şeyi içermiyor c. Bir SurfaceView'in sadece bir bölümünü güncelleyemezsiniz, ki bu OP'nin sorduğu şeydi sanırım.
Guillaume Brunerie

1
@Gu Guillaume Brunerie: Doğru, ama hepsi değil. Yazdığım gibi ekranın bir bölümünü güncelleyemezsiniz. Ancak eski çizimleri ekranda tutabilirsiniz, bu sadece "ekranın bir bölümünü güncelleme" etkisine sahiptir. Örnek bir uygulamada kendiniz deneyin. Canvaseski çizimleri korur.
Wroclai

2
Utanç bana !!! Dizimi sadece oyun başlangıcında BİR KEZ başlattım, art arda yeniden başlatmalarda DEĞİL - yani TÜM ESKİ parçalar "ekranda" kaldı - onlar yeni çizildi !!! "Aptallığım" için çok üzgünüm! İkinize de teşekkürler !!!
samClem

1
O zaman bunun nedeni muhtemelen Canlı duvar kağıtlarının normal SurfaceView ile aynı şekilde çalışmamasıdır. Her neyse, @samClem: Tuvalin her pikselini her karede (belgede belirtildiği gibi) her zaman yeniden çizin, yoksa garip bir titreme yaşarsınız.
Guillaume Brunerie

280

PorterDuff net modu ile şeffaf renk çiz, istediğim şeyi yapıyor.

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

2
Bu işe yaramalı Bitmap#eraseColor(Color.TRANSPARENT), ancak aşağıda HeMac'in cevabında olduğu gibi 4.4 (en azından N7.2'de) Kullanımda sorunlu görünmektedir.
nmr

4
Aslında Color.TRANSPARENTgereksizdir. PorterDuff.Mode.CLEARbir ARGB_8888bitmap için tamamen yeterlidir, bu da alfa ve rengin [0, 0] olarak ayarlanması anlamına gelir. Bir başka yolu kullanmaktır Color.TRANSPARENTile PorterDuff.Mode.SRC.
jiasli

1
Şeffaf yerine boş bir yüzey bırakmak benim için
işe yaramıyor

32

Bunu google gruplarında buldum ve bu benim için çalıştı ..

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

Bu, ayarlanan bitmap'i korurken çizim dikdörtgenlerini vb. Kaldırır.


4
Bu bana siyah bir alan veriyor - arkasındaki bit eşlem değil :(
slott

İyi anlaşılır, ancak söylediğiniz gibi bit eşlem de kaldırılır.
Omar Rehman

içinde stackoverflow.com/questions/9691985/... tuval bazı dikdörtgen bir bitmap bir dikdörtgen çizmek için nasıl açıklanmıştır. Dikdörtgendeki bir resmi değiştirmek şu şekilde çalışır: önceki içeriği öğrenin, yeni resmi çizin
Martin

18

Path sınıfının sıfırlama yöntemini kullanın

Path.reset();

bir yol kullanıyorsanız bu gerçekten en iyi cevaptır. diğerleri kullanıcıyı siyah bir ekranla terk edebilir. Teşekkürler.
j2emanue

18

@Mobistry'nin cevabını denedim:

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

Ama benim için işe yaramadı.

Benim için çözüm şuydu:

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

Belki birinin sorunu aynıdır.


4
Cevap şeffaflıkla çarpmaktır. Aksi takdirde bazı cihazlarda siyah renkli olabilir.
Andreas Rudolph

12
mBitmap.eraseColor(Color.TRANSPARENT);

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

2
bu nasıl en doğru? sadece drawColor varken neden bir bitmap kullanasınız ki?
RichieHH

Bu, orijinal poster için işe yaramasa da (bitmap'e erişimleri olması gerekmez), bu, mevcut olduğunda bir bitmap'in içeriğini temizlemek için kesinlikle yararlıdır. Bu durumlarda sadece ilk satır gereklidir. Faydalı teknik, +1
kodları girin

4

lütfen aşağıdaki kodu yüzey görünümünde genişletme sınıf yapıcısına yapıştırın .............

yapıcı kodlama

    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 kodlaması

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

yukarıdaki kodu deneyin ...


holder.setFormat (PixelFormat.TRANSPARENT); Benim için çalışıyor.
Aaron Lee

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

2
Lütfen kodunuzun sorunu nasıl çözdüğüne dair bir açıklama ekleyin. Bu, düşük kaliteli gönderi olarak işaretlendi - Yorumdan
Suraj Rao

3

İşte her karede Canvas'ın her pikselini her zaman yeniden çizmeniz gerektiğini gösteren minimal bir örneğin kodu.

Bu etkinlik, daha önce ekranı temizlemeden SurfaceView üzerinde her saniye yeni bir Bitmap çizer. Test ederseniz, bit eşlemin her zaman aynı arabelleğe yazılmadığını ve ekranın iki arabellek arasında değişeceğini göreceksiniz.

Telefonumda (Nexus S, Android 2.3.3) ve emülatörde (Android 2.2) test ettim.

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

Görünüşe göre yanılıyorsunuz, buradaki ekran görüntüsüne bakın: img12.imageshack.us/i/devicey.png/# Bir saniye gibi bir gecikme eklediğinizde çift arabelleğe alma daha çok fark edilir, ancak (!) ekranda hala önceki çizimler var. Ayrıca, kodunuz yanlış: öyle olmalı SurfaceHolder.Callback, sadece değil Callback.
Wroclai

1
Sanırım ne demek istediğimi anlamıyorsun. Beklenebilecek şey, çerçeve n ile çerçeve n + 1 arasındaki farkın bir tane daha Bitmap olmasıdır. Ancak bu tamamen yanlış, çerçeve n ve çerçeve n + 2 arasında bir Bitmap daha var , ancak çerçeve n ve çerçeve n + 1 , bir Bitmap eklesem bile tamamen ilgisiz.
Guillaume Brunerie

Hayır, seni tamamen anlıyorum. Ancak gördüğünüz gibi kodunuz çalışmıyor. Bir süre sonra ekran simgelerle dolar. Bu nedenle, Canvastüm ekranı tamamen yeniden çizmek istiyorsak, temizlememiz gerekiyor . Bu, "önceki çizimler" hakkındaki ifademi doğru kılıyor. CanvasÖnceki çizim tutar.
Wroclai

4
Evet isterseniz a Canvasönceki çizimleri saklar. Ama bu tamamen işe yaramaz, sorun şu ki, kullandığınızda lockCanvas()o “önceki çizimin” ne olduğunu bilmiyorsunuz ve onlar hakkında hiçbir şey varsayamıyorsunuz. Belki aynı boyutta SurfaceView ile iki etkinlik varsa, bunlar aynı Tuvali paylaşacaklardır. Belki de içinde rastgele baytlar olan bir yığın başlatılmamış RAM elde edersiniz. Belki de Canvas'ta her zaman Google logosu yazılıdır. Bilemezsin. Sonrasında her pikseli çizmeyen herhangi bir uygulama lockCanvas(null)bozulur.
Guillaume Brunerie

1
@Gu GuillaumeBrunerie 2.5 yıl sonra gönderinizle karşılaştım. Resmi Android belgeleri, "her pikseli çizme" ile ilgili tavsiyelerinizi destekler. Daha sonra lockCanvas () çağrısıyla tuvalin bir kısmının hala orada olmasının garanti edildiği yalnızca bir durum vardır. SurfaceHolder ve iki lockCanvas () yöntemi için Android belgelerine bakın. developer.android.com/reference/android/view/… ve developer.android.com/reference/android/view/…
UpLate

3

Benim için aramak Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)veya benzeri bir şey sadece ekrana dokunduktan sonra işe yarayacaktır. Bu yüzden yukarıdaki kod satırını çağırırdım, ancak ekran ancak ekrana dokunduktan sonra temizlenirdi. Bu yüzden benim için işe yarayan şey, invalidate()onu çağırmaktı ve ardından bunu init()yaratma sırasında görüşü başlatmak için çağırdı.

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

1

Java android'de Canvas'ta silmek, globalCompositeOperation ile javascript aracılığıyla HTML Canvas'ı silmeye benzer. Mantık benzerdi.

U, DST_OUT (Hedef Çıkış) mantığını seçecektir.

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

Not: DST_OUT daha kullanışlıdır çünkü boya rengi% 50 alfa içeriyorsa% 50 silebilir. Bu nedenle, tamamen şeffaf hale getirmek için renk alfa% 100 olmalıdır. Paint.setColor (Color.WHITE) uygulanması önerilir. Ve tuval görüntü formatının RGBA_8888 olduğundan emin olun.

Silme işleminden sonra SRC_OVER (Kaynak Bitti) ile normal çizime geri dönün.

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

Küçük alan ekranını tam anlamıyla güncellemek, grafik donanımına erişmeye ihtiyaç duyacaktır ve desteklenmeyebilir.

En yüksek performans için en yakın olanı çoklu görüntü katmanı kullanmaktır.


Bana yardım edebilir misin Davam için çözülen tek çözüm sizinki, bu yüzden oy verdim, çözülmesi gereken küçük bir sorun var
hamza khan

Yüzey tutuculu kanvas kullanıyorum, bu yüzden tuvali temizlemek için yapıyorum (yüzey tutucudan alınan kanvas): boya !!. SetXfermode (PorterDuffXfermode (PorterDuff.Mode.DST_OUT)) kanvas !!. DrawPaint (boya !!) yüzey tutucu! ! .unlockCanvasAndPost (tuval) sonra yeniden çizebilmek için: boya !!. setXfermode (PorterDuffXfermode (PorterDuff.Mode.SRC)) tuval !!. drawPaint (boya !!) yüzeyHolder !!. unlockCanvasAndPost (tuval) şimdi sorun tuvali bu şekilde temizledikten sonra, tuval üzerine bir bitmap çizdiğimde can sıkıcı bir Renk göz açıp kapayıncaya kadar çiziliyor, bana yolu gösterebilir misin? @phnghue
hamza khan

@hamza khan SRC_OVER ile denediniz mi? Çünkü SRC_OVER varsayılan normaldir. SRC mantığı eski piksel verilerinin üzerine yazılır, alpha!
phnghue

0

Validate () işlevini çağırmayı unutmayın;

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

Neden bir invalidateşey çizdikten sonra arıyorsun?
RalfFriedl

0

Görünümü onPause () 'da kaldırmayı ve onRestart () eklemeyi deneyin.

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

Görünümünüzü eklediğiniz an, onDraw () yöntemi çağrılır.

YourCustomView, View sınıfını genişleten bir sınıftır.


0

Benim durumumda, tuvalimi doğrusal düzende çiziyorum. Yeniden temizlemek ve yeniden çizmek için:

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

ve sonra sınıfı yeni değerlerle çağırırım:

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

Lienzo sınıfı budur:

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

Aşağıdaki yaklaşımla tuvali tamamen veya sadece bir kısmını temizleyebilirsiniz.
Lütfen, PorterDuff.Mode.CLEAR donanım hızlandırma ile çalışmadığı için Donanım hızlandırmayı devre dışı bırakmayı unutmayın ve en sonunda yöntemi setWillNotDraw(false)geçersiz kıldığımız için çağırıyoruz 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); 

işe yarıyor, ancak bundan sonra çekimlerim artık görünmüyor
Dyno Cris

1
@DynoCris Belki de aynı Paint nesnesini kullanıyorsunuzdur !? Yeni bir tane deneyin ve lütfen ek oy vermeyi unutmayın.
ucMedia

Bu benim kodum pastebin.com/GWBdtUP5 , tekrar çizmeye çalıştığımda satırları daha tatsız bir şekilde görmüyorum. ama cansvas temiz.
Dyno Cris

1
Lütfen değişiklik @DynoCris LAYER_TYPE_HARDWAREiçinLAYER_TYPE_SOFTWARE
ucMedia

1
gerçekten, bu benim hatam. Ama yine de bana yardımcı olmadı.
Dyno Cris

-1

İlk gereksiniminiz, tüm tuvali nasıl temizleyeceğiniz veya yeniden çizeceğiniz - Cevapla - ekranı siyah bir renkle veya belirttiğiniz herhangi bir şeyle temizlemek için canvas.drawColor (color.Black) yöntemini kullanın.

İkinci gereksiniminiz, ekranın bir bölümünü nasıl güncelleyeceğiniz - Cevapla - örneğin, ekranda diğer her şeyi değişmeden tutmak istiyorsanız, ancak ekranın küçük bir alanında her beş saniyeden sonra artan bir tamsayı (sayacı diyelim) göstermek istiyorsanız. ardından, sol üst sağ alt ve boyayı belirterek bu küçük alanı çizmek için canvas.drawrect yöntemini kullanın. daha sonra sayaç değerinizi hesaplayın (Handler.postDelayed (Runnable_Object, 5000); gibi, 5 saniye için postdalayed kullanarak), bunu metin dizesine dönüştürün, x ve y koordinatını bu küçük doğrultuda hesaplayın ve değişiklikleri görüntülemek için metin görünümünü kullanın sayaç değeri.


-1

Tuvali temizlemek için ayrı bir çizim geçişi kullanmak zorunda kaldım (kilitle, çiz ve kilidi 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

Sadece ara

canvas.drawColor (Renk.TRANSPARENT)


16
Bu işe yaramaz çünkü sadece geçerli klibin üzerine şeffaflık çekecek ... etkili bir şekilde hiçbir şey yapmadan. Ancak, istenen etkiyi elde etmek için Porter-Duff aktarım modunu değiştirebilirsiniz: Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR). Yanılmıyorsam, renk aslında herhangi bir şey olabilir (ŞEFFAF olması gerekmez) çünkü PorterDuff.Mode.CLEARsadece geçerli klibi temizler (tuvalde bir delik açmak gibi).
ashughes
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.