Android, canvas: come posso cancellare (eliminare il contenuto di) una tela (= bitmap), vivendo in un surfaceView?


92

Per realizzare un gioco semplice, ho utilizzato un modello che disegna una tela con bitmap come questo:

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

(La tela è definita in "run ()" / SurfaceView risiede in un GameThread.)

La mia prima domanda è: come posso cancellare (o ridisegnare) l' intera tela per un nuovo layout?
Secondo, come posso aggiornare solo una parte dello schermo?

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

Risposte:


77

Come posso cancellare (o ridisegnare) l'intera tela per un nuovo layout (= provare il gioco)?

Chiama Canvas.drawColor(Color.BLACK)o qualsiasi colore con cui vuoi cancellare il tuo Canvas.

E: come posso aggiornare solo una parte dello schermo?

Non esiste un metodo simile che aggiorni solo una "parte dello schermo" poiché il sistema operativo Android sta ridisegnando ogni pixel durante l'aggiornamento dello schermo. Ma, quando non cancelli i vecchi disegni sul tuo Canvas, i vecchi disegni sono ancora in superficie e questo è probabilmente un modo per "aggiornare solo una parte" dello schermo.

Quindi, se vuoi "aggiornare una parte dello schermo", evita di chiamare il Canvas.drawColor()metodo.


4
No, se non disegni tutti i pixel della superficie otterrai risultati molto strani (perché il doppio buffering si ottiene scambiando i puntatori, quindi nelle parti in cui non stai disegnando non vedrai cosa c'era appena prima). È necessario ridisegnare ogni pixel della superficie ad ogni iterazione.
Guillaume Brunerie

2
@Viktor Lannér: Se chiami mSurfaceHolder.unlockCanvasAndPost(c)e poi c = mSurfaceHolder.lockCanvas(null), il nuovo cnon contiene la stessa cosa del precedente c. Non è possibile aggiornare solo una parte di SurfaceView, che è ciò che l'OP stava chiedendo immagino.
Guillaume Brunerie

1
@Guillaume Brunerie: Vero, ma non solo. Non puoi aggiornare una parte dello schermo, come ho scritto. Ma puoi mantenere i vecchi disegni sullo schermo, il che avrà l'effetto di "aggiornare solo una parte dello schermo". Provalo tu stesso in un'applicazione di esempio. Canvasconserva i vecchi disegni.
Wroclai

2
MI VERGOGNO !!! Ho inizializzato il mio array solo UNA VOLTA all'inizio del gioco NON ai riavvii consecutivi - quindi TUTTI I VECCHI pezzi sono rimasti "sullo schermo" - sono stati solo disegnati di nuovo !!! MI DISPIACE MOLTO per la mia "stupidità"! Grazie a tutti e due !!!
samClem

1
Quindi questo è probabilmente dovuto al fatto che gli sfondi animati non funzionano allo stesso modo di un normale SurfaceView. Ad ogni modo, @samClem: ridisegna sempre ogni pixel della tela in ogni fotogramma (come indicato nel documento) o avrai uno strano sfarfallio.
Guillaume Brunerie

278

Disegna colori trasparenti con la modalità trasparente di PorterDuff fa il trucco per quello che volevo.

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

2
Questo dovrebbe funzionare, ma sembra essere disturbato in 4.4 (almeno su N7.2) Usa Bitmap#eraseColor(Color.TRANSPARENT), come nella risposta di HeMac di seguito.
nmr

3
In effetti, Color.TRANSPARENTnon è necessario. PorterDuff.Mode.CLEARè assolutamente sufficiente per una ARGB_8888bitmap, il che significa impostare l'alfa e il colore su [0, 0]. Un altro modo è usare Color.TRANSPARENTcon PorterDuff.Mode.SRC.
jiasli

1
Questo non funziona per me lasciando una superficie vuota invece di Trasparente
pallav bohara

31

Ho trovato questo nei gruppi di Google e questo ha funzionato per me ..

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

Questo rimuove i rettangoli dei disegni ecc. Mantenendo la bitmap impostata.


4
Questo mi dà un'area nera - non la bitmap che ho dietro :(
slott

È chiaro, ma anche la bitmap viene rimossa a differenza di quanto hai detto.
Omar Rehman

in stackoverflow.com/questions/9691985/… viene spiegato come disegnare un rettangolo di una bitmap su qualche rettangolo della tela. Modificare un'immagine in un rettangolo funziona così: impara i contenuti precedenti, disegna la nuova immagine
Martin

18

Ho provato la risposta di @mobistry:

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

Ma non ha funzionato per me.

La soluzione, per me, era:

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

Forse qualcuno ha lo stesso problema.


4
Moltiplicare con la trasparenza è la risposta. Altrimenti può finire di colore nero su alcuni dispositivi.
Andreas Rudolph

16

utilizzare il metodo di ripristino della classe Path

Path.reset();

questa è davvero la risposta migliore se stai usando un percorso. gli altri possono lasciare l'utente con uno schermo nero. Grazie.
j2emanue

12
mBitmap.eraseColor(Color.TRANSPARENT);

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

2
come è questo il più corretto? perché usare una bitmap quando puoi semplicemente drawColor?
RichieHH

Sebbene questo potrebbe non funzionare per il poster originale (non hanno necessariamente accesso alla bitmap), questo è sicuramente utile per cancellare il contenuto di una bitmap quando è disponibile. In questi casi, è richiesta solo la prima riga. Tecnica utile, +1
testa nei codici

puoi scrivere un intero codice per favore?
Dyno Cris

4

si prega di incollare sotto il codice nel costruttore della classe estendere la vista di superficie .............

codifica del costruttore

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

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

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

prova il codice sopra ...


holder.setFormat (PixelFormat.TRANSPARENT); Per me funziona.
Aaron Lee,

3

Ecco il codice di un esempio minimo che mostra che devi sempre ridisegnare ogni pixel della tela in ogni fotogramma.

Questa attività disegna una nuova bitmap ogni secondo in SurfaceView, senza prima cancellare lo schermo. Se lo provi, vedrai che la bitmap non viene sempre scritta nello stesso buffer e lo schermo si alternerà tra i due buffer.

L'ho testato sul mio telefono (Nexus S, Android 2.3.3) e sull'emulatore (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();
                }
            }
        }   
    }
}

Bene, sembra che ti sbagli, guarda la mia cattura dello schermo qui: img12.imageshack.us/i/devicey.png/# Quando hai aggiunto un ritardo come un secondo, il doppio buffering è più notato, ma (!) ci sono ancora disegni precedenti sullo schermo. Inoltre, il tuo codice è sbagliato: dovrebbe esserlo SurfaceHolder.Callback, non solo Callback.
Wroclai

1
Penso che tu non capisca cosa intendo. Cosa ci si può aspettare è che la differenza tra il telaio n e telaio n + 1 è che c'è un altro bitmap. Ma questo è completamente sbagliato, c'è un altro bitmap tra telaio n e telaio n + 2 , ma telaio n e telaio n + 1 sono completamente indipendenti, anche se ho solo aggiunto un bitmap.
Guillaume Brunerie

No, ti capisco perfettamente. Ma, come vedi, il tuo codice non funziona. Dopo un po ', lo schermo è pieno di icone. Pertanto, dobbiamo cancellare il Canvasse vogliamo ridisegnare completamente l'intero schermo. Ciò rende vera la mia affermazione sui "disegni precedenti". La Canvasmantiene disegni precedenti.
Wroclai

4
Sì, se vuoi, Canvasconserva i disegni precedenti. Ma questo è del tutto inutile, il problema è che quando li usi lockCanvas()non sai cosa sono quei “disegni precedenti”, e non puoi dare per scontato nulla al riguardo. Forse che se ci sono due attività con un SurfaceView della stessa dimensione, condivideranno lo stesso Canvas. Forse che ottieni un pezzo di RAM non inizializzata con byte casuali al suo interno. Forse che c'è sempre il logo di Google scritto nella tela. Non puoi saperlo. Qualsiasi applicazione che non disegna tutti i pixel dopo lockCanvas(null)è danneggiata.
Guillaume Brunerie

1
@GuillaumeBrunerie 2,5 anni dopo il fatto che ho letto il tuo post. La documentazione ufficiale di Android supporta i tuoi consigli riguardo al "disegno di ogni pixel". C'è solo un caso in cui è garantito che una parte dell'area di disegno sia ancora lì con una successiva chiamata a lockCanvas (). Fare riferimento alla documentazione di Android per SurfaceHolder e i suoi due metodi lockCanvas (). developer.android.com/reference/android/view/… e developer.android.com/reference/android/view/…
UpLate

3

Per me la chiamata Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)o qualcosa di simile funzionerebbe solo dopo aver toccato lo schermo. Quindi chiamerei la riga di codice sopra, ma lo schermo si cancellerebbe solo dopo aver toccato lo schermo. Quindi quello che ha funzionato per me è stato chiamare invalidate()seguito da init()cui viene chiamato al momento della creazione per inizializzare la vista.

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
Aggiungi una spiegazione su come il tuo codice risolve il problema. Questo è contrassegnato come post di bassa qualità - Dalla recensione
Suraj Rao

1

La cancellazione su Canvas in java Android è simile alla cancellazione di HTML Canvas tramite javascript con globalCompositeOperation. La logica era simile.

U sceglierà la logica DST_OUT (Destination Out).

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

Nota: DST_OUT è più utile perché può cancellare il 50% se il colore della vernice ha il 50% alfa. Quindi, per passare completamente al trasparente, l'alfa del colore deve essere 100%. Si consiglia di applicare paint.setColor (Color.WHITE). E assicurati che il formato dell'immagine su tela fosse RGBA_8888.

Dopo la cancellazione, torna al disegno normale con SRC_OVER (Source Over).

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

Aggiornare letteralmente il display di una piccola area dovrà accedere all'hardware grafico e potrebbe non essere supportato.

Il livello più vicino per ottenere le massime prestazioni sta utilizzando il livello multi immagine.


Puoi aiutarmi qui? La tua è l'unica soluzione che ha funzionato per il mio caso, quindi ho votato, c'è solo un piccolo problema che deve essere risolto
hamza khan

Sto usando la tela con il supporto della superficie, quindi lo faccio per cancellare la tela (tela presa dal supporto della superficie): paint !!. SetXfermode (PorterDuffXfermode (PorterDuff.Mode.DST_OUT)) canvas !!. DrawPaint (paint !!) surfaceHolder! ! .unlockCanvasAndPost (canvas) quindi per poterlo ridisegnare: paint !!. setXfermode (PorterDuffXfermode (PorterDuff.Mode.SRC)) canvas !!. drawPaint (paint !!) surfaceHolder !!. unlockCanvasAndPost (canvas) ora il problema è dopo aver cancellato la tela in questo modo, quando disegno una bitmap sulla tela, viene disegnata dopo un lampo di colore, il che è fastidioso, puoi mostrarmi la strada qui? @phnghue
hamza khan

@hamza khan Hai provato con SRC_OVER? Perché SRC_OVER è normale di default. La logica SRC sovrascrive i vecchi dati dei pixel, sostituisce alpha!
phnghue

0

Non dimenticare di chiamare invalidate ();

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

Perché dovresti chiamare invalidatedopo aver disegnato qualcosa?
RalfFriedl

0

Prova a rimuovere la visualizzazione in onPause () di un'attività e aggiungi onRestart ()

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

Nel momento in cui aggiungi la tua vista, verrà chiamato il metodo onDraw ().

YourCustomView, è una classe che estende la classe View.


0

Nel mio caso, disegno la mia tela in linearlayout. Per pulire e ridisegnare di nuovo:

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

e poi, chiamo la classe con i nuovi valori:

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

Questa è la classe 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

Con il seguente approccio, puoi cancellare l'intera tela o solo una parte di essa.
Non dimenticare di disabilitare l'accelerazione hardware poiché PorterDuff.Mode.CLEAR non funziona con l'accelerazione hardware e infine chiama setWillNotDraw(false)perché sovrascriviamo il onDrawmetodo.

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

funziona, ma dopo di che i miei disegni non sono più visibili
Dyno Cris

1
@DynoCris Forse stai usando lo stesso oggetto Paint !? Prova con uno nuovo e non dimenticare di votare per favore.
ucMedia

è il mio codice pastebin.com/GWBdtUP5 quando provo a disegnare di nuovo non vedo le righe in modo più sfacciato. ma cansvas è chiaro.
Dyno Cris

1
@DynoCris Si prega di passare LAYER_TYPE_HARDWAREaLAYER_TYPE_SOFTWARE
ucMedia

1
oh, davvero, è un mio errore. Ma non mi ha aiutato in ogni caso in modo sfacciato.
Dyno Cris

-1

Il tuo primo requisito, come cancellare o ridisegnare l'intera tela - Risposta - usa il metodo canvas.drawColor (color.Black) per cancellare lo schermo con un colore nero o qualsiasi cosa tu specifichi.

Il tuo secondo requisito, come aggiornare parte dello schermo - Rispondi - ad esempio se vuoi mantenere tutte le altre cose invariate sullo schermo ma in una piccola area dello schermo per mostrare un numero intero (diciamo contatore) che aumenta ogni cinque secondi. quindi usa il metodo canvas.drawrect per disegnare quella piccola area specificando sinistra in alto a destra in basso e dipingi. quindi calcola il valore del tuo contatore (usando postdalayed per 5 secondi ecc., come Handler.postDelayed (Runnable_Object, 5000);), convertilo in stringa di testo, calcola le coordinate xey in questo piccolo rettangolo e usa la visualizzazione del testo per visualizzare la modifica controvalore.


-1

Ho dovuto utilizzare un passaggio di disegno separato per cancellare la tela (bloccare, disegnare e sbloccare):

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

Chiama soltanto

canvas.drawColor (Color.TRANSPARENT)


16
Questo non funziona perché disegnerà solo la trasparenza sopra la clip corrente ... effettivamente non facendo nulla. È possibile, tuttavia, cambiare la modalità di trasferimento Porter-Duff per ottenere l'effetto desiderato: Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR). Se non sbaglio, il colore può effettivamente essere qualsiasi cosa (non deve essere TRASPARENTE) perché PorterDuff.Mode.CLEARcancellerà semplicemente la clip corrente (come fare un buco nella tela).
Ashughes
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.