Android, lienzo: ¿Cómo borro (elimino el contenido de) un lienzo (= mapas de bits) que vive en un surfaceView?

95

Para hacer un juego simple, utilicé una plantilla que dibuja un lienzo con mapas de bits como este:

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

(El lienzo se define en "run ()" / SurfaceView vive en un GameThread).

Mi primera pregunta es ¿cómo borro (o redibujo) todo el lienzo para un nuevo diseño?
En segundo lugar, ¿cómo puedo actualizar solo una parte de la pantalla?

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

Respuestas:

78

¿Cómo borro (o vuelvo a dibujar) TODO el lienzo para un nuevo diseño (= prueba en el juego)?

Simplemente llame Canvas.drawColor(Color.BLACK), o cualquier color con el que desee aclararlo Canvas.

Y: ¿cómo puedo actualizar solo una parte de la pantalla?

No existe tal método que simplemente actualice una "parte de la pantalla", ya que el sistema operativo Android está redibujando cada píxel al actualizar la pantalla. Pero, cuando no está borrando dibujos antiguos en su Canvas, los dibujos antiguos todavía están en la superficie y esa es probablemente una forma de "actualizar solo una parte" de la pantalla.

Por lo tanto, si desea "actualizar una parte de la pantalla", simplemente evite llamar al Canvas.drawColor()método.

Wroclai
fuente
4
No, si no dibuja cada píxel de la superficie obtendrá resultados muy extraños (porque el doble búfer se logra intercambiando punteros, por lo que en las partes donde no está dibujando no verá lo que había justo antes). Usted tiene que volver a dibujar cada píxel de la superficie en cada iteración.
Guillaume Brunerie
2
@Viktor Lannér: Si llamas mSurfaceHolder.unlockCanvasAndPost(c)y luego c = mSurfaceHolder.lockCanvas(null), entonces el nuevo cno contiene lo mismo que el anterior c. No puede actualizar solo una parte de SurfaceView, que es lo que estaba preguntando el OP, supongo.
Guillaume Brunerie
1
@Guillaume Brunerie: Cierto, pero no todos. No se puede actualizar una parte de la pantalla, como escribí. Pero puede mantener dibujos antiguos en la pantalla, lo que tendrá el efecto de simplemente "actualizar una parte de la pantalla". Pruébelo usted mismo en una aplicación de muestra. Canvasguarda dibujos antiguos.
Wroclai
2
ME AVERGÜENZA !!! Inicialicé mi matriz solo UNA VEZ al comienzo del juego, NO en los reinicios consecutivos, por lo que TODAS las piezas VIEJAS permanecieron "en pantalla", ¡¡¡simplemente se dibujaron de nuevo !!! ¡LO SIENTO MUCHO por mi "tontería"! Gracias a los dos !!!
samClem
1
Entonces esto probablemente se deba a que los fondos de pantalla en vivo no funcionan de la misma manera que un SurfaceView normal. De todos modos, @samClem: siempre vuelva a dibujar cada píxel del lienzo en cada cuadro (como se indica en el documento) o tendrá un parpadeo extraño.
Guillaume Brunerie
279

Dibujar color transparente con el modo claro de PorterDuff hace el truco para lo que quería.

Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
Sileria
fuente
2
Esto debería funcionar, pero parece tener errores en 4.4 (al menos en el N7.2) Use Bitmap#eraseColor(Color.TRANSPARENT), como en la respuesta de HeMac a continuación.
nmr
3
De hecho, Color.TRANSPARENTes innecesario. PorterDuff.Mode.CLEARes totalmente suficiente para un ARGB_8888mapa de bits, lo que significa establecer el alfa y el color en [0, 0]. Otra forma es usar Color.TRANSPARENTcon PorterDuff.Mode.SRC.
jiasli
1
Esto no funciona para mí, dejar una superficie en blanco en lugar de transparente
pallav bohara
31

Encontré esto en los grupos de Google y esto funcionó para mí.

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

Esto elimina los dibujos, rectángulos, etc. mientras se mantiene el mapa de bits establecido.

Rukmal Dias
fuente
4
Esto me da un área negra, no el mapa de bits que tengo detrás :(
slott
Se borra bien, pero el mapa de bits también se elimina como dijiste.
Omar Rehman
en stackoverflow.com/questions/9691985/… se explica cómo dibujar un rectángulo de un mapa de bits en algún rectángulo del lienzo. Cambiar una imagen en un rectángulo funciona así: aprende los contenidos anteriores, dibuja la nueva imagen
Martin
18

utilizar el método de reinicio de la clase Path

Path.reset();
Rakesh
fuente
esta es realmente la mejor respuesta si usa una ruta. los otros pueden dejar al usuario con una pantalla negra. Gracias.
j2emanue
18

Probé la respuesta de @mobistry:

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

Pero no funcionó para mí.

La solución, para mí, fue:

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

Quizás alguien tenga el mismo problema.

Derzu
fuente
4
Multiplicar con transparencia es la respuesta. De lo contrario, puede terminar de color negro en algunos dispositivos.
Andreas Rudolph
12
mBitmap.eraseColor(Color.TRANSPARENT);

canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
heMac
fuente
2
¿Cómo es esto más correcto? ¿Por qué usar un mapa de bits cuando solo puedes dibujarColor?
RichieHH
Aunque esto podría no funcionar para el póster original (no necesariamente tienen acceso al mapa de bits), definitivamente es útil para borrar el contenido de un mapa de bits cuando está disponible. En esos casos, solo se requiere la primera línea. Técnica útil, +1
encabezado en los códigos
4

pegue el código debajo en el constructor de clase de extensión de surfaceview .............

codificación del constructor

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

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

codificación 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" />

prueba el código anterior ...

Hemant Chand Dungriyal
fuente
holder.setFormat (PixelFormat.TRANSPARENT); Esto funciona para mi.
Aaron Lee
3

Aquí está el código de un ejemplo mínimo que muestra que siempre debe volver a dibujar cada píxel del lienzo en cada cuadro.

Esta actividad dibuja un nuevo mapa de bits cada segundo en SurfaceView, sin borrar la pantalla antes. Si lo prueba, verá que el mapa de bits no siempre se escribe en el mismo búfer y la pantalla alternará entre los dos búferes.

Lo probé en mi teléfono (Nexus S, Android 2.3.3) y en el emulador (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
fuente
Bueno, parece que estás equivocado, mira mi captura de pantalla aquí: img12.imageshack.us/i/devicey.png/# Cuando agregas un retraso como un segundo, el doble búfer se nota más, pero (!) todavía hay dibujos anteriores en la pantalla. Además, su código es incorrecto: debería serlo SurfaceHolder.Callback, no solo Callback.
Wroclai
1
Creo que no entiendes lo que quiero decir. Lo que se podría esperar es que la diferencia entre el cuadro n y el cuadro n + 1 es que hay un mapa de bits más. Pero esto es completamente incorrecto, hay un mapa de bits más entre el cuadro n y el cuadro n + 2 , pero el cuadro n y el cuadro n + 1 tienen ninguna relación incluso si acabo de agregar un mapa de bits.
Guillaume Brunerie
No, te entiendo completamente. Pero, como ve, su código no funciona. Después de un tiempo, la pantalla se llena de iconos. Por lo tanto, necesitamos borrar elCanvas si queremos volver a dibujar completamente toda la pantalla. Eso hace que mi afirmación sobre los "dibujos anteriores" sea cierta. El Canvasmantiene dibujos anteriores.
Wroclai
4
Sí, si lo desea, Canvasconserva los dibujos anteriores. Pero esto es completamente inútil, el problema es que cuando usaslockCanvas() usas no sabes cuáles son esos “dibujos anteriores”, y no puedes asumir nada sobre ellos. Quizás si hay dos actividades con un SurfaceView del mismo tamaño, compartirán el mismo Canvas. Tal vez obtenga una parte de la RAM no inicializada con bytes aleatorios. Quizás que siempre esté escrito el logo de Google en el Canvas. No puedes saberlo. Cualquier aplicación que no dibuje cada píxel después lockCanvas(null)está rota.
Guillaume Brunerie
1
@GuillaumeBrunerie 2.5 años después del hecho de que encontré tu publicación. La documentación oficial de Android respalda sus consejos sobre "dibujar cada píxel". Solo hay un caso en el que se garantiza que una parte del lienzo seguirá allí con una llamada posterior a lockCanvas (). Consulte la documentación de Android para SurfaceHolder y sus dos métodos lockCanvas (). developer.android.com/reference/android/view/… y developer.android.com/reference/android/view/…
UpLate
3

Para mí, llamar Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)o algo similar solo funcionaría después de tocar la pantalla. Entonces, llamaría a la línea de código anterior, pero la pantalla solo se borraría después de tocar la pantalla. Entonces, lo que funcionó para mí fue llamar invalidate()seguido de lo init()que se llama en el momento de la creación para inicializar 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();
}
Jason Crosby
fuente
3
canvas.drawColor(Color.TRANSPARENT, Mode.MULTIPLY);
aliakbarian
fuente
2
Agregue una explicación de cómo su código resuelve el problema. Esto está marcado como publicación de baja calidad - De la revisión
Suraj Rao
1

Borrar en Canvas en java android es similar al borrar HTML Canvas a través de javascript con globalCompositeOperation. La lógica era similar.

U elegirá la lógica DST_OUT (salida de destino).

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

Nota: DST_OUT es más útil porque puede borrar un 50% si el color de la pintura tiene un 50% de alfa. Entonces, para aclarar completamente a transparente, el alfa de color debe ser 100%. Se recomienda aplicar paint.setColor (Color.WHITE). Y asegúrese de que el formato de la imagen del lienzo sea RGBA_8888.

Después de borrar, vuelva al dibujo normal con SRC_OVER (Source Over).

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

Actualizar la pantalla de área pequeña literalmente necesitará acceder al hardware gráfico, y tal vez no sea compatible.

Lo más cercano para un mayor rendimiento es el uso de capas de imágenes múltiples.

phnghue
fuente
Puedes ayudarme aqui? La suya es la única solución que ha funcionado para mi caso, así que voté a favor, solo hay un pequeño problema que debe resolverse
hamza khan
Estoy usando lienzo con soporte de superficie, así que hago esto para limpiar el lienzo (lienzo tomado del soporte de superficie): paint !!. SetXfermode (PorterDuffXfermode (PorterDuff.Mode.DST_OUT)) canvas !!. DrawPaint (paint !!) surfaceHolder! ! .unlockCanvasAndPost (lienzo) luego para poder volver a dibujarlo: paint !!. setXfermode (PorterDuffXfermode (PorterDuff.Mode.SRC)) canvas !!. drawPaint (paint !!) surfaceHolder !!. unlockCanvasAndPost (canvas) ahora el problema Después de limpiar el lienzo de esta manera, cuando dibujo un mapa de bits sobre el lienzo, se dibuja después de un parpadeo de color, lo cual es molesto, ¿me pueden mostrar el camino aquí? @phnghue
hamza khan
@hamza khan ¿Has probado con SRC_OVER? Porque SRC_OVER es normal por defecto. La lógica SRC sobrescribe los datos de píxeles antiguos, ¡reemplaza a alfa!
phnghue
0

No olvide llamar a invalidate ();

canvas.drawColor(backgroundColor);
invalidate();
path.reset();
chakraborty de jazz
fuente
¿Por qué llamarías invalidatedespués de dibujar algo?
RalfFriedl
0

Intente eliminar la vista en onPause () de una actividad y agregue onRestart ()

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

En el momento en que agregue su vista, se llamará al método onDraw ().

YourCustomView, es una clase que amplía la clase View.

Rishabh Jain
fuente
0

En mi caso, dibujo mi lienzo en un diseño lineal. Para limpiar y volver a dibujar:

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

y luego, llamo a la clase con los nuevos valores:

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

Esta es la clase 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
fuente
0

Con el siguiente enfoque, puede borrar todo el lienzo o solo una parte.
No olvide deshabilitar la aceleración de hardware, ya que PorterDuff.Mode.CLEAR no funciona con la aceleración de hardware y, finalmente, llama setWillNotDraw(false)porque anulamos el onDrawmétodo.

//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
fuente
es trabajo, pero después de eso mis dibujos no son visibles más
Dyno Cris
1
@DynoCris ¿¡Quizás estás usando el mismo objeto Paint !? Pruebe con uno nuevo y no olvide votar a favor.
ucMedia
es mi código pastebin.com/GWBdtUP5 cuando trato de dibujar de nuevo, no veo líneas más desafortunadamente. pero cansvas es claro.
Dyno Cris
1
@DynoCris Por favor cambie LAYER_TYPE_HARDWAREaLAYER_TYPE_SOFTWARE
ucMedia
1
oh, de verdad, es mi error. Pero, de todos modos, no me ayudó, lamentablemente.
Dyno Cris
-1

Su primer requisito, cómo borrar o volver a dibujar todo el lienzo - Respuesta - use el método canvas.drawColor (color.Black) para limpiar la pantalla con un color negro o lo que especifique.

Su segundo requisito, cómo actualizar parte de la pantalla - Respuesta - por ejemplo, si desea mantener todas las demás cosas sin cambios en la pantalla pero en un área pequeña de la pantalla para mostrar un número entero (digamos contador) que aumenta después de cada cinco segundos. luego use el método canvas.drawrect para dibujar esa pequeña área especificando la parte superior derecha inferior izquierda y pintar. luego calcule el valor de su contador (usando postdalayed durante 5 segundos, etc., como Handler.postDelayed (Runnable_Object, 5000);), conviértalo en una cadena de texto, calcule las coordenadas xey en este pequeño rect y use la vista de texto para mostrar el cambio valor del contador.

Chandu
fuente
-1

Tuve que usar un pase de dibujo separado para limpiar el lienzo (bloquear, dibujar y desbloquear):

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
fuente
-3

Solo llama

canvas.drawColor (Color.TRANSPARENT)

reznic
fuente
16
Eso no funciona porque solo dibujará transparencia sobre el clip actual ... efectivamente sin hacer nada. Puede, sin embargo, cambiar el modo de transferencia de Porter-Duff para lograr el efecto deseado: Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR). Si no me equivoco, el color puede ser cualquier cosa (no tiene por qué ser TRANSPARENTE) porque PorterDuff.Mode.CLEARsimplemente borrará el clip actual (como hacer un agujero en el lienzo).
Ashughes