Android, canevas: Comment effacer (supprimer le contenu de) un canevas (= bitmaps), vivant dans une surfaceView?

93

Afin de créer un jeu simple, j'ai utilisé un modèle qui dessine un canevas avec des bitmaps comme celui-ci:

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

(Le canevas est défini dans "run ()" / le SurfaceView vit dans un GameThread.)

Ma première question est de savoir comment effacer (ou redessiner) l' ensemble du canevas pour une nouvelle mise en page?
Deuxièmement, comment puis-je mettre à jour une partie seulement de l'écran?

// 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
la source

Réponses:

77

Comment effacer (ou redessiner) le canevas ENTIER pour une nouvelle mise en page (= essayer le jeu)?

Appelez simplement Canvas.drawColor(Color.BLACK), ou quelle que soit la couleur que vous souhaitez utiliser Canvas.

Et: comment puis-je mettre à jour juste une partie de l'écran?

Il n’existe pas de méthode qui consiste simplement à mettre à jour une «partie de l’écran» car Android OS redessine chaque pixel lors de la mise à jour de l’écran. Mais, lorsque vous n'effacez pas d'anciens dessins sur votre Canvas, les anciens dessins sont toujours à la surface et c'est probablement une façon de "mettre à jour juste une partie" de l'écran.

Donc, si vous voulez "mettre à jour une partie de l'écran", évitez simplement d'appeler Canvas.drawColor()method.

Wroclai
la source
4
Non, si vous ne dessinez pas tous les pixels de la surface, vous obtiendrez des résultats très étranges (car le double tampon est obtenu en échangeant des pointeurs, donc dans les parties où vous ne dessinez pas, vous ne verrez pas ce qu'il y avait juste avant). Vous devez redessiner chaque pixel de la surface à chaque itération.
Guillaume Brunerie
2
@Viktor Lannér: Si vous appelez mSurfaceHolder.unlockCanvasAndPost(c)et puis c = mSurfaceHolder.lockCanvas(null), alors le nouveau cne contient pas la même chose que le précédent c. Vous ne pouvez pas mettre à jour uniquement une partie d'un SurfaceView, ce que l'OP demandait, je suppose.
Guillaume Brunerie
1
@Guillaume Brunerie: C'est vrai, mais pas tous. Vous ne pouvez pas mettre à jour une partie de l'écran, comme je l'ai écrit. Mais vous pouvez conserver d'anciens dessins à l'écran, ce qui n'aura pour effet que de "mettre à jour une partie de l'écran". Essayez-le vous-même dans un exemple d'application. Canvasgarde de vieux dessins.
Wroclai
2
HONTE SUR MOI !!! Je n'ai initialisé mon tableau qu'UNE SEULE FOIS au début du jeu PAS aux redémarrages consécutifs - donc TOUTES LES ANCIENNES pièces sont restées "à l'écran" - elles ont juste été dessinées à nouveau !!! JE SUIS TRÈS DÉSOLÉ pour ma "stupidité"! Merci à vous deux !!!
samClem
1
Ensuite, c'est probablement parce que les fonds d'écran animés ne fonctionnent pas de la même manière qu'un SurfaceView normal. Quoi qu'il en soit, @samClem: redessinez toujours chaque pixel du canevas à chaque image (comme indiqué dans la doc) ou vous aurez un scintillement étrange.
Guillaume Brunerie
278

Dessiner une couleur transparente avec le mode clair PorterDuff fait l'affaire pour ce que je voulais.

Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
Sileria
la source
2
Cela devrait fonctionner, mais semble être bogué dans 4.4 (au moins sur le N7.2) Utilisation Bitmap#eraseColor(Color.TRANSPARENT), comme dans la réponse de HeMac ci-dessous.
nmr
3
En fait, Color.TRANSPARENTc'est inutile. PorterDuff.Mode.CLEARest totalement suffisant pour un ARGB_8888bitmap, ce qui signifie définir l'alpha et la couleur sur [0, 0]. Une autre façon est d'utiliser Color.TRANSPARENTavec PorterDuff.Mode.SRC.
jiasli
1
Cela ne fonctionne pas pour moi en laissant une surface vierge au lieu de Transparent
pallav bohara
31

J'ai trouvé ceci dans des groupes Google et cela a fonctionné pour moi.

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

Cela supprime les rectangles de dessins, etc. tout en conservant le bitmap défini.

Rukmal Dias
la source
4
Cela me donne une zone noire - pas le bitmap que j'ai derrière :(
slott
Son efface bien, mais le bitmap est également supprimé contrairement à ce que vous avez dit.
Omar Rehman
dans stackoverflow.com/questions/9691985/ ... est expliqué comment dessiner un rectangle d'un bitmap sur un rectangle du canevas. Changer une image dans un rectangle fonctionne donc: apprenez le contenu précédent, dessinez la nouvelle image
Martin
18

J'ai essayé la réponse de @mobistry:

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

Mais cela n'a pas fonctionné pour moi.

La solution, pour moi, était:

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

Peut-être que quelqu'un a le même problème.

Derzu
la source
4
Multiplier par la transparence est la réponse. Sinon, il peut devenir noir sur certains appareils.
Andreas Rudolph
17

utilisez la méthode de réinitialisation de la classe Path

Path.reset();
Rakesh
la source
c'est vraiment la meilleure réponse si vous utilisez un chemin. les autres peuvent laisser à l'utilisateur un écran noir. Merci.
j2emanue
12
mBitmap.eraseColor(Color.TRANSPARENT);

canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
heMac
la source
2
comment est-ce le plus correct? pourquoi utiliser un bitmap quand vous pouvez simplement drawColor?
RichieHH
Bien que cela puisse ne pas fonctionner pour l'affiche originale (ils n'ont pas nécessairement accès au bitmap), cela est certainement utile pour effacer le contenu d'un bitmap lorsqu'il est disponible. Dans ces cas, seule la première ligne est requise. Technique utile, +1
tête dans les codes
4

veuillez coller ci-dessous le code sur le constructeur de classe d'extension de surfaceview .............

codage constructeur

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

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

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

essayez le code ci-dessus ...

Hemant Chand Dungriyal
la source
holder.setFormat (PixelFormat.TRANSPARENT); Ça marche pour moi.
Aaron Lee
3

Voici le code d'un exemple minimal montrant que vous devez toujours redessiner chaque pixel du canevas à chaque image.

Cette activité dessine un nouveau Bitmap toutes les secondes sur SurfaceView, sans effacer l'écran auparavant. Si vous le testez, vous verrez que le bitmap n'est pas toujours écrit dans le même tampon, et l'écran alternera entre les deux tampons.

Je l'ai testé sur mon téléphone (Nexus S, Android 2.3.3) et sur l'émulateur (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
la source
Eh bien, il semble que vous vous trompez, regardez ma capture d'écran ici: img12.imageshack.us/i/devicey.png/# Lorsque vous avez ajouté un délai comme une seconde, le double tampon est plus remarqué, mais (!) il y a encore des dessins précédents à l'écran. De plus, votre code est erroné: il devrait l'être SurfaceHolder.Callback, pas seulement Callback.
Wroclai
1
Je pense que tu ne comprends pas ce que je veux dire. Ce à quoi on peut s'attendre, c'est que la différence entre la trame n et la trame n + 1 est qu'il y a encore un Bitmap. Mais c'est complètement faux, il y a encore un Bitmap entre l'image n et l'image n + 2 , mais l'image n et l'image n + 1 sont complètement indépendantes même si je viens d'ajouter un Bitmap.
Guillaume Brunerie
Non, je vous comprends parfaitement. Mais, comme vous le voyez, votre code ne fonctionne pas. Au bout d'un moment, l'écran est plein d'icônes. Par conséquent, nous devons effacer le Canvassi nous voulons redessiner complètement l'écran entier. Cela rend vrai ma déclaration sur les "dessins précédents". Le Canvasconserve les dessins précédents.
Wroclai
4
Oui si vous le souhaitez, a Canvasconserve les dessins précédents. Mais c'est totalement inutile, le problème est que lorsque vous utilisez lockCanvas()vous ne savez pas ce que sont ces "dessins précédents", et ne pouvez rien supposer à leur sujet. Peut-être que s'il y a deux activités avec un SurfaceView de la même taille, elles partageront le même Canvas. Peut-être que vous obtenez un morceau de RAM non initialisée avec des octets aléatoires. Peut-être qu'il y a toujours le logo Google écrit dans le canevas. Tu ne peux pas savoir. Toute application qui ne dessine pas chaque pixel après lockCanvas(null)est interrompue.
Guillaume Brunerie
1
@GuillaumeBrunerie 2,5 ans après le fait que je suis tombé sur votre message. La documentation officielle d'Android soutient vos conseils concernant "dessiner chaque pixel". Il n'y a qu'un seul cas où une partie du canevas est garantie d'être toujours là avec un appel ultérieur à lockCanvas (). Reportez-vous à la documentation Android pour SurfaceHolder et ses deux méthodes lockCanvas (). developer.android.com/reference/android/view/… et developer.android.com/reference/android/view
...
3

Pour moi, appeler Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)ou quelque chose de similaire ne fonctionnerait qu'après avoir touché l'écran. Donc, j'appellerais la ligne de code ci-dessus, mais l'écran ne s'effacerait qu'après avoir touché l'écran. Donc ce qui a fonctionné pour moi était d'appeler invalidate()suivi de init()qui est appelé au moment de la création pour initialiser la vue.

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
la source
3
canvas.drawColor(Color.TRANSPARENT, Mode.MULTIPLY);
Aliakbarian
la source
2
Veuillez ajouter une explication de la manière dont votre code résout le problème. Ceci est signalé comme un message de mauvaise qualité - De l'avis
Suraj Rao
1

Effacer sur Canvas dans java android est similaire à l'effacement de HTML Canvas via javascript avec globalCompositeOperation. La logique était similaire.

U choisira la logique DST_OUT (Destination Out).

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

Remarque: DST_OUT est plus utile car il peut effacer 50% si la couleur de la peinture a 50% alpha. Donc, pour être complètement transparent, l'alpha de la couleur doit être de 100%. Il est recommandé d'appliquer paint.setColor (Color.WHITE). Et assurez-vous que le format de l'image du canevas était RGBA_8888.

Une fois effacé, revenez au dessin normal avec SRC_OVER (Source Over).

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

La mise à jour de l'affichage de petite zone devra littéralement accéder au matériel graphique, et peut-être n'est-elle pas prise en charge.

Le plus proche pour des performances optimales est l'utilisation de plusieurs couches d'images.

phnghue
la source
Pouvez-vous m'aider ici? La vôtre est la seule solution qui a fonctionné pour mon cas, alors j'ai voté, il y a juste un petit problème à régler
hamza khan
J'utilise une toile avec support de surface, donc je le fais pour effacer la toile (toile extraite du support de surface): paint !!. SetXfermode (PorterDuffXfermode (PorterDuff.Mode.DST_OUT)) canvas !!. DrawPaint (paint !!) surfaceHolder! ! .unlockCanvasAndPost (canvas) puis pour pouvoir le redessiner: paint !!. setXfermode (PorterDuffXfermode (PorterDuff.Mode.SRC)) canvas !!. drawPaint (paint !!) surfaceHolder !!. unlockCanvasAndPost (canvas) maintenant le problème est-ce qu'après avoir effacé le canevas comme ceci, quand je dessine un bitmap sur un canevas, il est dessiné après un clin d'œil de couleur, ce qui est ennuyeux, pouvez-vous me montrer le chemin ici? @phnghue
hamza khan
@hamza khan Avez-vous essayé avec SRC_OVER? Parce que SRC_OVER est normal par défaut. La logique SRC écrase les anciennes données de pixels, elle remplace l'alpha!
phnghue
0

N'oubliez pas d'appeler invalidate ();

canvas.drawColor(backgroundColor);
invalidate();
path.reset();
jazz chakraborty
la source
Pourquoi appelleriez-vous invalidateaprès avoir dessiné quelque chose?
RalfFriedl
0

Essayez de supprimer la vue à onPause () d'une activité et ajoutez onRestart ()

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

Au moment où vous ajoutez votre vue, la méthode onDraw () est appelée.

YourCustomView, est une classe qui étend la classe View.

Rishabh Jain
la source
0

Dans mon cas, je dessine ma toile en linéaire. Pour nettoyer et redessiner:

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

puis, j'appelle la classe avec les nouvelles valeurs:

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

C'est 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
    }
}
Carlos Gómez
la source
0

Avec l'approche suivante, vous pouvez effacer toute la toile ou juste une partie de celle-ci.
N'oubliez pas de désactiver l'accélération matérielle car PorterDuff.Mode.CLEAR ne fonctionne pas avec l'accélération matérielle et enfin appeler setWillNotDraw(false)car nous surchargons la onDrawméthode.

//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
la source
c'est du travail, mais après ça mes tirages ne sont plus visibles
Dyno Cris
1
@DynoCris Vous utilisez peut-être le même objet Paint !? Essayez avec un nouveau, et n'oubliez pas de voter s'il vous plaît.
ucMedia
c'est mon code pastebin.com/GWBdtUP5 quand j'essaye de dessiner à nouveau, je ne vois pas les lignes de manière plus imprudente. mais cansvas est clair.
Dyno Cris
1
@DynoCris Veuillez changer LAYER_TYPE_HARDWAREpourLAYER_TYPE_SOFTWARE
ucMedia
1
oh, vraiment, c'est mon erreur. Mais ça ne m'a pas aidé de toute façon sans court terme.
Dyno Cris
-1

Votre première exigence, comment effacer ou redessiner la toile entière - Réponse - utilisez la méthode canvas.drawColor (color.Black) pour effacer l'écran avec une couleur de noir ou tout ce que vous spécifiez.

Votre deuxième exigence, comment mettre à jour une partie de l'écran - Réponse - par exemple si vous voulez garder toutes les autres choses inchangées à l'écran mais dans une petite zone de l'écran pour afficher un entier (par exemple un compteur) qui augmente toutes les cinq secondes. puis utilisez la méthode canvas.drawrect pour dessiner cette petite zone en spécifiant gauche en haut à droite en bas et peignez. puis calculez la valeur de votre compteur (en utilisant postdalayed pendant 5 secondes etc., llike Handler.postDelayed (Runnable_Object, 5000);), convertissez-la en chaîne de texte, calculez les coordonnées x et y dans ce petit rect et utilisez la vue texte pour afficher le changement valeur de compteur.

Chandu
la source
-1

J'ai dû utiliser une passe de dessin distincte pour effacer la toile (verrouiller, dessiner et déverrouiller):

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
la source
-3

Il suffit d'appeler

canvas.drawColor (Couleur.TRANSPARENT)

reznic
la source
16
Cela ne fonctionne pas car cela ne fera que dessiner de la transparence par-dessus le clip actuel ... en fait, ne rien faire. Vous pouvez toutefois modifier le mode de transfert Porter-Duff pour obtenir l'effet désiré: Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR). Si je ne me trompe pas, la couleur peut en fait être n'importe quoi (ne doit pas être TRANSPARENTE) car PorterDuff.Mode.CLEARelle effacera simplement le clip actuel (comme percer un trou dans la toile).
ashughes