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

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.


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)

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.


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.


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

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

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 ...


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

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

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

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.


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

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.


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

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

é 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.


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

-3

Apenas ligue

canvas.drawColor (Color.TRANSPARENT)


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.