Android, canvas: Como faço para limpar (excluir o conteúdo de) uma tela (= bitmaps), vivendo em um surfaceView?

92

Para fazer um jogo simples, usei um modelo que desenha uma tela com bitmaps 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); } }

(A tela é definida em "run ()" / o SurfaceView reside em um GameThread.)

Minha primeira pergunta é como faço para limpar (ou redesenhar) a tela inteira para um novo layout?
Em segundo lugar, como posso atualizar apenas uma parte da tela?

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

Respostas:

77

Como eu limpo (ou redesenho) a tela TODA para um novo layout (= experimente no jogo)?

Basta ligar Canvas.drawColor(Color.BLACK)ou qualquer cor com a qual você deseja limpar o seu Canvas.

E: como posso atualizar apenas uma parte da tela?

Não existe um método que apenas atualize uma "parte da tela", já que o sistema operacional Android está redesenhando cada pixel ao atualizar a tela. Mas, quando você não está limpando desenhos antigos em seuCanvas , os desenhos antigos ainda estão na superfície e essa é provavelmente uma maneira de "atualizar apenas uma parte" da tela.

Portanto, se você deseja "atualizar uma parte da tela", apenas evite chamar o Canvas.drawColor()método.

Wroclai
fonte
4
Não, se você não desenhar todos os pixels na superfície, obterá resultados muito estranhos (porque o buffer duplo é obtido trocando ponteiros, então nas partes onde você não está desenhando você não verá o que estava lá antes). Você tem que redesenhar cada pixel da superfície a cada iteração.
Guillaume Brunerie de
2
@Viktor Lannér: Se você ligar mSurfaceHolder.unlockCanvasAndPost(c)e então c = mSurfaceHolder.lockCanvas(null), o novo cnão contém a mesma coisa que o anterior c. Você não pode atualizar apenas uma parte de um SurfaceView, que é o que o OP estava pedindo, eu acho.
Guillaume Brunerie de
1
@Guillaume Brunerie: Verdade, mas não tudo. Você não pode atualizar uma parte da tela, como escrevi. Mas você pode manter desenhos antigos na tela, o que terá o efeito de apenas "atualizar uma parte da tela". Experimente você mesmo em um aplicativo de amostra. Canvasmantém desenhos antigos.
Wroclai de
2
QUE VERGONHA !!! Eu inicializei meu array apenas UMA VEZ no início do jogo, NÃO nos reinícios consecutivos - então TODAS as peças antigas permaneceram "na tela" - elas foram desenhadas novamente !!! Sinto muito pela minha "burrice"! Obrigado a vocês dois !!!
samClem de
1
Então, isso provavelmente se deve ao fato de os papéis de parede animados não funcionarem da mesma maneira que um SurfaceView normal. De qualquer forma, @samClem: sempre redesenhe cada pixel do Canvas em cada quadro (conforme declarado no documento) ou você terá uma tremulação estranha.
Guillaume Brunerie de
278

Desenhar cores transparentes com o modo de limpeza PorterDuff resolve o que eu queria.

Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
Sileria
fonte
2
Isso deve funcionar, mas parece estar com bug em 4.4 (pelo menos no N7.2) Use Bitmap#eraseColor(Color.TRANSPARENT), como na resposta do HeMac abaixo.
NMR
3
Na verdade, Color.TRANSPARENTé desnecessário. PorterDuff.Mode.CLEARé totalmente o suficiente para um ARGB_8888bitmap, o que significa definir o alfa e a cor como [0, 0]. Outra maneira é usar Color.TRANSPARENTcom PorterDuff.Mode.SRC.
jiasli
1
Isso não está funcionando para mim, deixando uma superfície em branco em vez de Transparente
pallav bohara
31

Encontrei isso em grupos do Google e funcionou para mim ..

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

Isso remove retângulos de desenhos etc., enquanto mantém o bitmap definido.

Rukmal Dias
fonte
4
Isso me dá uma área preta - não o bitmap que tenho por trás dele :(
slott
Está claro, mas o bitmap também é removido, ao contrário do que você disse.
Omar Rehman
em stackoverflow.com/questions/9691985/… é explicado como desenhar um retângulo de um bitmap em algum retângulo da tela. Alterar uma imagem em um retângulo funciona assim: aprenda o conteúdo anterior, desenhe a nova imagem
Martin
18

Tentei a resposta de @mobistry:

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

Mas não funcionou para mim.

A solução, para mim, foi:

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

Talvez alguém tenha o mesmo problema.

Derzu
fonte
4
Multiplicar com transparência é a resposta. Caso contrário, pode acabar na cor preta em alguns dispositivos.
Andreas Rudolph
16

use o método de redefinição da classe Path

Path.reset();
Rakesh
fonte
esta realmente é a melhor resposta se você estiver usando um caminho. os outros podem deixar o usuário com uma tela preta. obrigado.
j2emanue
12
mBitmap.eraseColor(Color.TRANSPARENT);

canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
heMac
fonte
2
como isso é o mais correto? por que usar um bitmap quando você pode apenas drawColor?
RichieHH
Embora isso possa não funcionar para o autor da postagem original (eles não têm necessariamente acesso ao bitmap), é definitivamente útil para limpar o conteúdo de um bitmap quando estiver disponível. Nesses casos, apenas a primeira linha é necessária. Técnica útil, +1
cabeça nos códigos
você pode escrever um código inteiro, por favor?
Dyno Cris
4

cole o código abaixo em surfaceview extend class constructor .............

codificação do construtor

    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ção 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" />

tente o código acima ...

Hemant Chand Dungriyal
fonte
holder.setFormat (PixelFormat.TRANSPARENT); Funciona para mim.
Aaron Lee
3

Aqui está o código de um exemplo mínimo, mostrando que você sempre tem que redesenhar cada pixel do Canvas em cada quadro.

Esta atividade desenha um novo Bitmap a cada segundo no SurfaceView, sem limpar a tela antes. Se você testá-lo, verá que o bitmap nem sempre é gravado no mesmo buffer e a tela alternará entre os dois buffers.

Testei no meu telefone (Nexus S, Android 2.3.3) e no 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
fonte
Bem, parece que você está errado, olhe minha captura de tela aqui: img12.imageshack.us/i/devicey.png/# Quando você adiciona um atraso de um segundo, o buffer duplo é mais notado, mas (!) ainda há desenhos anteriores na tela. Além disso, seu código está errado: deveria estar SurfaceHolder.Callback, não apenas Callback.
Wroclai de
1
Acho que você não entende o que quero dizer. O que se poderia esperar é que a diferença entre o quadro n e o quadro n + 1 é que há mais um bitmap. Mas isso está completamente errado, há mais um bitmap entre o quadro ne o quadro n + 2 , mas o quadro n e o quadro n + 1 são completamente independentes, mesmo que eu apenas tenha adicionado um bitmap.
Guillaume Brunerie de
Não, eu te entendo perfeitamente. Mas, como você vê, seu código não funciona. Depois de um tempo, a tela fica cheia de ícones. Portanto, precisamos limpar o Canvasse quisermos redesenhar totalmente a tela inteira. Isso torna verdadeira minha afirmação sobre "desenhos anteriores". O Canvasguarda desenhos anteriores.
Wroclai de
4
Sim, se quiser, Canvasmantém os desenhos anteriores. Mas isso é completamente inútil, o problema é que quando você usa lockCanvas()você não sabe o que são aqueles “desenhos anteriores”, e não pode presumir nada sobre eles. Talvez se houver duas atividades com um SurfaceView do mesmo tamanho, elas compartilharão o mesmo Canvas. Talvez você obtenha um pedaço de RAM não inicializado com bytes aleatórios nele. Talvez haja sempre o logotipo do Google escrito no Canvas. Você não pode saber. Qualquer aplicativo que não estiver desenhando todos os pixels depois lockCanvas(null)será quebrado.
Guillaume Brunerie de
1
@GuillaumeBrunerie 2,5 anos após o fato de eu ter encontrado sua postagem. A documentação oficial do Android apóia seu conselho sobre "desenhar cada pixel". Há apenas um caso em que uma parte da tela tem a garantia de que ainda estará lá com uma chamada subsequente para lockCanvas (). Consulte a documentação do Android para SurfaceHolder e seus dois métodos lockCanvas (). developer.android.com/reference/android/view/… e developer.android.com/reference/android/view/…
UpLate
3

Para mim, ligar Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)ou algo semelhante só funcionaria depois de tocar na tela. Então, eu chamaria a linha de código acima, mas a tela só limparia depois que eu toquei na tela. Então, o que funcionou para mim foi chamar invalidate()seguido de init()que é chamado no momento da criação para inicializar a visualização.

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
fonte
3
canvas.drawColor(Color.TRANSPARENT, Mode.MULTIPLY);
aliakbarian
fonte
2
Adicione uma explicação de como seu código resolve o problema. Isto é sinalizado como postagem de baixa qualidade - Da avaliação
Suraj Rao
1

Apagar no Canvas em java android é semelhante ao apagar HTML Canvas via javascript com globalCompositeOperation. A lógica era semelhante.

U escolherá a lógica DST_OUT (Destination Out).

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

Nota: DST_OUT é mais útil porque pode apagar 50% se a cor da tinta tiver 50% alfa. Portanto, para clarear completamente para transparente, o alfa da cor deve ser 100%. Aplicar paint.setColor (Color.WHITE) é recomendado. E certifique-se de que o formato da imagem da tela era RGBA_8888.

Depois de apagado, volte ao desenho normal com SRC_OVER (Source Over).

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

Atualizar a exibição de pequena área literalmente precisará acessar o hardware gráfico e talvez não seja compatível.

O mais próximo para o melhor desempenho é usar a camada de várias imagens.

phnghue
fonte
Você pode me ajudar aqui? A sua é a única solução que funcionou para o meu caso, então votei, há apenas um pequeno problema que precisa ser
resolvido
Estou usando tela com suporte de superfície, então faço isso para limpar tela (tela retirada do suporte de superfície): pintura !!. SetXfermode (PorterDuffXfermode (PorterDuff.Mode.DST_OUT)) canvas !!. DrawPaint (pintura !!) surfaceHolder! ! .unlockCanvasAndPost (canvas) então para poder redesenhar: paint !!. setXfermode (PorterDuffXfermode (PorterDuff.Mode.SRC)) canvas !!. drawPaint (paint !!) surfaceHolder !!. unlockCanvasAndPost (canvas) agora o problema é depois de limpar a tela assim, quando eu desenho um bitmap sobre a tela, ele é desenhado após um piscar de cores, o que é irritante, você pode me mostrar o caminho até aqui? @phnghue
hamza khan
@hamza khan Você já tentou com SRC_OVER? Porque SRC_OVER é o padrão normal. A lógica SRC é sobrescrever dados de pixel antigos, ela substitui alfa!
phnghue
0

Não se esqueça de chamar invalidate ();

canvas.drawColor(backgroundColor);
invalidate();
path.reset();
jazz chakraborty
fonte
Por que você ligaria invalidatedepois de desenhar algo?
RalfFriedl de
0

Tente remover a visualização em onPause () de uma atividade e adicionar onRestart ()

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

No momento em que você adiciona sua visão, o método onDraw () é chamado.

YourCustomView, é uma classe que estende a classe View.

Rishabh Jain
fonte
0

No meu caso, desenho minha tela em layout linear. Para limpar e redesenhar novamente:

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

e então chamo a classe com os novos valores:

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

Esta é a 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
fonte
0

Com a abordagem a seguir, você pode limpar toda a tela ou apenas uma parte dela.
Não se esqueça de desabilitar a aceleração de hardware, pois PorterDuff.Mode.CLEAR não funciona com aceleração de hardware e, finalmente, chamar setWillNotDraw(false)porque substituímos o 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
fonte
é trabalho, mas depois disso meus desenhos não são mais visíveis
Dyno Cris
1
@DynoCris Talvez você esteja usando o mesmo objeto Paint !? Experimente com um novo e não se esqueça de votar positivamente.
ucMedia
é o meu código pastebin.com/GWBdtUP5 quando tento desenhar de novo, não vejo linhas menos regulares. mas cansvas é claro.
Dyno Cris
1
@DynoCris Por favor, mude LAYER_TYPE_HARDWAREparaLAYER_TYPE_SOFTWARE
ucMedia
1
oh, sério, é meu erro. Mas isso não me ajudou de maneira nada cortês.
Dyno Cris
-1

Seu primeiro requisito, como limpar ou redesenhar a tela inteira - Resposta - use o método canvas.drawColor (color.Black) para limpar a tela com uma cor preta ou o que você especificar.

Seu segundo requisito, como atualizar parte da tela - Resposta - por exemplo, se você deseja manter todas as outras coisas inalteradas na tela, mas em uma pequena área da tela para mostrar um número inteiro (digamos, contador) que aumenta a cada cinco segundos. em seguida, use o método canvas.drawrect para desenhar essa pequena área especificando a parte superior esquerda inferior direita e pintar. em seguida, calcule o valor do contador (usando postdalayed por 5 segundos etc., como Handler.postDelayed (Runnable_Object, 5000);), converta-o em string de texto, calcule a coordenada xey neste pequeno retângulo e use a visualização de texto para exibir a alteração valor do contador.

Chandu
fonte
-1

Tive que usar um passe de desenho separado para limpar a tela (bloquear, desenhar e 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
fonte
-3

Apenas ligue

canvas.drawColor (Color.TRANSPARENT)

Reznic
fonte
16
Isso não funciona porque vai apenas desenhar transparência sobre o clipe atual ... efetivamente sem fazer nada. Você pode, no entanto, alterar o modo de transferência Porter-Duff para alcançar o efeito desejado: Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR). Se não me engano, a cor pode ser qualquer coisa (não precisa ser TRANSPARENTE) porque PorterDuff.Mode.CLEARapenas limpará o clipe atual (como fazer um furo na tela).
ashughes 05 de