Como criar clipes de objetos na tela

Este codelab faz parte do curso Android avançado no Kotlin. Você aproveitará mais o curso se fizer os codelabs em sequência, mas isso não é obrigatório. Todos os codelabs do curso estão listados na página de destino dos codelabs avançados do Android em Kotlin (link em inglês).

Introdução

Para os fins deste codelab, o clipe é uma maneira de definir regiões de uma imagem, tela ou bitmap que são desenhados seletivamente ou não na tela. Uma das finalidades do recorte é reduzir o excesso de renderização. O overdraw ocorre quando um pixel na tela é desenhado mais de uma vez para exibir a imagem final. Ao reduzir o overdraw, você minimiza o número de vezes que um pixel ou uma região da tela é desenhado, a fim de maximizar o desempenho do desenho. Também é possível usar clipes para criar efeitos interessantes no design e na animação da interface do usuário.

Por exemplo, quando você desenha uma pilha de cards sobrepostos, como mostrado abaixo, em vez de desenhar todos os cards de baixo para cima, geralmente é mais eficiente desenhar apenas as partes visíveis. "Normalmente, porque as operações de recorte também têm um custo. No geral, o sistema Android faz muitas otimizações de desenho.

Para desenhar apenas as partes visíveis dos cards, especifique uma região de clipes para cada card. Por exemplo, no diagrama abaixo, quando um retângulo de corte é aplicado a uma imagem, apenas a parte dentro desse retângulo é exibida.

A região de corte geralmente é um retângulo, mas pode ser qualquer forma ou combinação de formas, até mesmo texto. Você também pode especificar se quer que a região dentro da região de recorte seja incluída ou excluída. Por exemplo, você pode criar uma região de recorte circular e exibir apenas o que está fora do círculo.

Neste codelab, você vai testar várias formas de recorte.

O que você já precisa saber

Você precisa:

  • Como criar um app com um Activity e executá-lo usando o Android Studio.
  • Como criar e desenhar em uma Canvas.
  • Como criar um View personalizado e substituir onDraw() e onSizeChanged().

O que você vai aprender

  • Como cortar objetos para desenhar em uma Canvas.
  • Como salvar e restaurar estados de desenho de uma tela.
  • Como aplicar transformações a uma tela e ao texto.

Atividades do laboratório

  • Crie um app que desenhe formas cortadas na tela demonstrando diferentes maneiras de recortar e o resultado na visibilidade dessas formas.
  • Você também desenhará textos traduzidos e distorcidos.

O app ClippingExample demonstra como usar e combinar formas para especificar quais partes de uma tela são exibidas em uma visualização. O app final ficará assim:

Você vai criar esse app do zero, então precisará configurar um projeto, definir dimensões e strings e declarar algumas variáveis.

Etapa 1: criar o projeto ClippingExample

  1. Crie um projeto Kotlin chamado ClippingExample com o modelo Empty Activity. Use com.example.android como o prefixo do nome do pacote.
  2. Abra o MainActivity.kt
  3. No método onCreate(), substitua a visualização de conteúdo padrão e defina-a como uma nova instância de ClippedView. Esta será sua visualização personalizada para os exemplos de recorte que você criará a seguir.
setContentView(ClippedView(this))
  1. No mesmo nível que MainActivity.kt, crie um novo arquivo e uma classe Kotlin para uma visualização personalizada chamada ClippedView que estenda o View. Atribua a assinatura abaixo. O restante do seu trabalho estará nesse ClippedView. A anotação @JvmOverloads instrui o compilador Kotlin a gerar sobrecargas para essa função que substituem valores de parâmetros padrão.
class ClippedView @JvmOverloads constructor(
   context: Context,
   attrs: AttributeSet? = null,
   defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
}

Etapa 2: adicionar dimensões e recursos de string

  1. Defina as dimensões que você usará para as visualizações cortadas em um novo arquivo de recursos no res/values/dimens.xml. Essas dimensões padrão são fixadas no código e dimensionadas para caber em uma tela muito pequena.
<?xml version="1.0" encoding="utf-8"?>
<resources>
   <dimen name="clipRectRight">90dp</dimen>
   <dimen name="clipRectBottom">90dp</dimen>
   <dimen name="clipRectTop">0dp</dimen>
   <dimen name="clipRectLeft">0dp</dimen>

   <dimen name="rectInset">8dp</dimen>
   <dimen name="smallRectOffset">40dp</dimen>

   <dimen name="circleRadius">30dp</dimen>
   <dimen name="textOffset">20dp</dimen>
   <dimen name="strokeWidth">4dp</dimen>

   <dimen name="textSize">18sp</dimen>
</resources>

Para que o app tenha uma boa aparência em uma tela maior e veja detalhes com mais facilidade, crie um arquivo dimens com valores maiores que só se apliquem a telas maiores.

  1. No Android Studio, clique com o botão direito do mouse na pasta values e escolha New > Values resource file.
  2. Na caixa de diálogo New Resource File, chame o arquivo dimens. Em Available qualifiers, selecione Smallest Screen Width e clique no botão >> para adicioná-lo a Chosen qualifiers. Digite 480 na caixa Menor largura da tela e clique em OK.

  1. O arquivo aparecerá na sua pasta de valores, conforme mostrado abaixo.

  1. Caso você não consiga ver o arquivo, mude para a visualização Project Files do app. O caminho completo do novo arquivo está como mostrado abaixo: ClippingExample/app/src/main/res/values-sw480dp/dimens.xml.

  1. Substitua o conteúdo padrão do arquivo values-sw480dp/dimens.xml pelas dimensões abaixo.
<?xml version="1.0" encoding="utf-8"?>
<resources>
   <dimen name="clipRectRight">120dp</dimen>
   <dimen name="clipRectBottom">120dp</dimen>

   <dimen name="rectInset">10dp</dimen>
   <dimen name="smallRectOffset">50dp</dimen>

   <dimen name="circleRadius">40dp</dimen>
   <dimen name="textOffset">25dp</dimen>
   <dimen name="strokeWidth">6dp</dimen>
</resources>
  1. Em strings.xml, adicione as seguintes strings. Eles serão usados para exibir o texto na tela.
<string name="clipping">Clipping</string>
<string name="translated">translated text</string>
<string name="skewed">"Skewed and "</string>

Etapa 3: criar e inicializar os objetos Paint e Path

  1. Volte para a visualização Android do seu projeto.
  2. Em ClippedView, defina uma variável Paint para desenhar. Ative o anti-aliasing e use a largura do traço e o tamanho do texto definidos nas dimensões, como mostrado abaixo.
private val paint = Paint().apply {
   // Smooth out edges of what is drawn without affecting shape.
   isAntiAlias = true
   strokeWidth = resources.getDimension(R.dimen.strokeWidth)
   textSize = resources.getDimension(R.dimen.textSize)
}
  1. Em ClippedView, crie e inicialize um Path para armazenar localmente o caminho do que foi desenhado. Importe android.graphics.Path.
private val path = Path()

Etapa 4: configurar as formas

Neste app, você está exibindo várias linhas e duas colunas de formas cortadas de várias maneiras.

Todos eles têm em comum:

  • Um retângulo grande (quadrado) que funciona como contêiner
  • Uma linha diagonal no retângulo grande
  • Círculo
  • Uma string curta de texto

Nesta etapa, você configurará dimensões para essas formas a partir dos recursos, de modo que só precise acessar as dimensões uma vez quando usá-las mais tarde.

  1. No ClippedView, abaixo do path, adicione variáveis para as dimensões de um retângulo de corte ao redor de todo o conjunto de formas.
private val clipRectRight = resources.getDimension(R.dimen.clipRectRight)
private val clipRectBottom = resources.getDimension(R.dimen.clipRectBottom)
private val clipRectTop = resources.getDimension(R.dimen.clipRectTop)
private val clipRectLeft = resources.getDimension(R.dimen.clipRectLeft)
  1. Adicione variáveis para a inserção de um retângulo e o deslocamento de um pequeno.
private val rectInset = resources.getDimension(R.dimen.rectInset)
private val smallRectOffset = resources.getDimension(R.dimen.smallRectOffset)
  1. Adicione uma variável para o raio de um círculo. É o raio do círculo desenhado dentro do retângulo.
private val circleRadius = resources.getDimension(R.dimen.circleRadius)
  1. Adicione um deslocamento e um tamanho para o texto que é desenhado dentro do retângulo.
private val textOffset = resources.getDimension(R.dimen.textOffset)
private val textSize = resources.getDimension(R.dimen.textSize)

Etapa 4: configurar locais de linhas e colunas

As formas deste app são exibidas em duas colunas e quatro linhas, determinadas pelos valores das dimensões configuradas acima. A matemática não faz parte deste codelab, mas dê uma olhada nele conforme você copia para o código fornecido nesta etapa.

  1. Configure as coordenadas para duas colunas.
private val columnOne = rectInset
private val columnTwo = columnOne + rectInset + clipRectRight
  1. Adicione as coordenadas a cada linha, incluindo a linha final do texto transformado.
private val rowOne = rectInset
private val rowTwo = rowOne + rectInset + clipRectBottom
private val rowThree = rowTwo + rectInset + clipRectBottom
private val rowFour = rowThree + rectInset + clipRectBottom
private val textRow = rowFour + (1.5f * clipRectBottom)
  1. Execute o app. Ele será aberto com uma tela em branco em branco abaixo do nome do app.

Em onDraw(), você chama métodos para desenhar sete retângulos cortados diferentes, conforme mostrado na captura de tela do app abaixo. Os retângulos são desenhados da mesma forma. A única diferença são as regiões de recorte definidas e a localização na tela.

O algoritmo usado para desenhar os retângulos funciona conforme mostrado no diagrama e na explicação abaixo. Em resumo, desenhe uma série de retângulos movendo a origem do Canvas. Conceitualmente, isso consiste nas seguintes etapas:

(1) Primeiro, traduza Canvas para onde você quer que o retângulo seja desenhado. Ou seja, em vez de calcular onde o próximo retângulo e todas as outras formas precisam ser desenhados, mova a origem Canvas, ou seja, o sistema de coordenadas.

(2) Em seguida, desenhe o retângulo na nova origem da tela. Ou seja, você desenha as formas no mesmo local no sistema de coordenadas traduzido. É muito mais simples e um pouco mais eficiente.

(3) Por fim, você restaura a Canvas para o Origin original.

Veja o algoritmo que você implementará:

  1. No onDraw(), chame uma função para preencher o Canvas com a cor de fundo cinza e desenhar as formas originais.
  2. Chame uma função para cada retângulo cortado e o texto a ser desenhado.

Para cada retângulo ou texto, faça o seguinte:

  1. Salve o estado atual do Canvas para poder redefini-lo para o estado inicial.
  2. Traduza a Origin da tela para o local onde você quer desenhar.
  3. Aplique formas e caminhos de recorte.
  4. Desenhe o retângulo ou o texto.
  5. Restaure o estado do Canvas.

Etapa: modificar onDraw()

  1. Modifique o onDraw() conforme mostrado no código abaixo. Chame uma função para cada forma que estiver desenhando, que será implementada mais tarde.
 override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        drawBackAndUnclippedRectangle(canvas)
        drawDifferenceClippingExample(canvas)
        drawCircularClippingExample(canvas)
        drawIntersectionClippingExample(canvas)
        drawCombinedClippingExample(canvas)
        drawRoundedRectangleClippingExample(canvas)
        drawOutsideClippingExample(canvas)
        drawSkewedTextExample(canvas)
        drawTranslatedTextExample(canvas)
        // drawQuickRejectExample(canvas)
    }
  1. Crie stubs para cada uma das funções de desenho para que o código continue sendo compilado. Você pode copiar o código abaixo.
private fun drawBackAndUnclippedRectangle(canvas: Canvas){
}
private fun drawDifferenceClippingExample(canvas: Canvas){
}
private fun drawCircularClippingExample(canvas: Canvas){
}
private fun drawIntersectionClippingExample(canvas: Canvas){
}
private fun drawCombinedClippingExample(canvas: Canvas){
}
private fun drawRoundedRectangleClippingExample(canvas: Canvas){
}
private fun drawOutsideClippingExample(canvas: Canvas){
}
private fun drawTranslatedTextExample(canvas: Canvas){
}
private fun drawSkewedTextExample(canvas: Canvas){
}
private fun drawQuickRejectExample(canvas: Canvas){
}

O app desenha o mesmo retângulo e forma sete vezes, primeiro sem recortar e depois seis vezes com vários caminhos de recorte aplicados. O método drawClippedRectangle() considera o código para desenhar um retângulo, conforme mostrado abaixo.

Etapa 1: criar o método drawClippedRectangle()

  1. Crie um método drawClippedRectangle() que recebe um argumento canvas do tipo Canvas.
private fun drawClippedRectangle(canvas: Canvas) {
}
  1. Dentro do método drawClippedRectangle(), defina os limites do retângulo de corte para a forma inteira. Aplique um retângulo de corte que restrinja apenas o desenho ao quadrado.
canvas.clipRect(
       clipRectLeft,clipRectTop,
       clipRectRight,clipRectBottom
)

O método Canvas.clipRect(...) reduz a região da tela em que as operações futuras de desenho podem gravar. Ele define os limites de recorte como a interseção espacial do retângulo de recorte atual e o retângulo transmitido para clipRect(). Há muitas variantes do método clipRect() que aceitam diferentes formas para regiões e permitem operações distintas no retângulo de corte.

  1. Preencha a canvas com cor branca. Sim. A tela inteira, como você não está desenhando retângulos, está sendo recortada. Por causa do retângulo de corte, somente a região definida pelo retângulo de corte é criada, criando um retângulo branco. O restante da superfície permanece cinza.
canvas.drawColor(Color.WHITE)
  1. Mude a cor para vermelho e desenhe uma linha diagonal dentro do retângulo de corte.
paint.color = Color.RED
canvas.drawLine(
   clipRectLeft,clipRectTop,
   clipRectRight,clipRectBottom,paint
)
  1. Defina a cor como verde e desenhe um círculo no retângulo de corte.
paint.color = Color.GREEN
canvas.drawCircle(
   circleRadius,clipRectBottom - circleRadius,
   circleRadius,paint
)
  1. Defina a cor como azul e desenhe um texto alinhado com a borda direita do retângulo de corte. Use o canvas.drawText() para desenhar texto.
paint.color = Color.BLUE
// Align the RIGHT side of the text with the origin.
paint.textSize = textSize
paint.textAlign = Paint.Align.RIGHT
canvas.drawText(
   context.getString(R.string.clipping),
   clipRectRight,textOffset,paint
)

Etapa 2: implementar o método drawBackAndUnclippedRectangle()

  1. Para ver o método drawClippedRectangle() em ação, desenhe o primeiro retângulo não cortado implementando o método drawBackAndUnclippedRectangle(), conforme mostrado abaixo. Salve a canvas, traduza para a primeira posição da linha e da coluna, desenhe chamando drawClippedRectangle() e restaure o canvas para o estado anterior.
private fun drawBackAndUnclippedRectangle(canvas: Canvas){
   canvas.drawColor(Color.GRAY)
   canvas.save()
   canvas.translate(columnOne,rowOne)
   drawClippedRectangle(canvas)
   canvas.restore()
}
  1. Execute o app. Você verá o primeiro retângulo branco com um círculo, uma linha vermelha e um texto em um plano de fundo cinza.

Nos seguintes métodos de exemplo de recorte, você aplica várias combinações de regiões de recorte para alcançar efeitos gráficos e aprender como combinar regiões de recorte para criar qualquer forma necessária.

Cada um desses métodos segue o mesmo padrão.

  1. Salve o estado atual da tela: canvas.save().

O contexto da atividade mantém uma pilha de estados de desenho. Os estados de desenho consistem na matriz de transformação atual e na região de recorte atual. Você pode salvar o estado atual, realizar ações que mudam o estado de desenho (como traduzir ou girar a tela) e restaurar o estado de desenho salvo. Observação: o comando é parecido com o ""stash" no git!".

Quando o desenho inclui transformações, encadear e desfazer transformações se elas forem revertidas serão propensos a erros. Por exemplo, se você traduzir, esticar e girar, isso ficará complexo rapidamente. Em vez disso, salve o estado da tela, aplique as transformações, desenhe e restaure o estado anterior.

Por exemplo, você pode definir uma região de recorte e salvar esse estado. Em seguida, traduza a tela, adicione uma região de recorte e gire. Depois de desenhar, você pode restaurar o estado original do recorte e fazer uma tradução diferente e uma transformação diferente, conforme mostrado no diagrama.

  1. Converta a origem da tela para as coordenadas da linha/coluna: canvas.translate()

É muito mais simples mover a origem da tela e desenhar a mesma coisa em um novo sistema de coordenadas do que mover todos os elementos para desenhar. Dica: você pode usar a mesma técnica para elementos giratórios.

  1. Aplique transformações ao path, se houver.
  2. Aplicar clipe: canvas.clipPath(path)
  3. Desenhe as formas: drawClippedRectangle() or drawText()
  4. Restaura o estado de tela anterior: canvas.restore()

Etapa 1: implementar drawDifferenceClippingExample(Canvas)

Adicione o código para desenhar o segundo retângulo, que usa a diferença entre dois retângulos de recorte para criar um efeito de moldura de imagem.

Use o código abaixo, que faz o seguinte:

  1. Salve a tela.
  2. Converta a origem da tela em espaço aberto para a primeira linha, segunda coluna, à direita do primeiro retângulo.
  3. Aplique dois retângulos de recorte. O operador DIFFERENCE subtrai o segundo retângulo do primeiro.
  1. Chame o método drawClippedRectangle() para desenhar a tela modificada.
  2. Restaura o estado da tela.
private fun drawDifferenceClippingExample(canvas: Canvas) {
   canvas.save()
   // Move the origin to the right for the next rectangle.
   canvas.translate(columnTwo,rowOne)
   // Use the subtraction of two clipping rectangles to create a frame.
   canvas.clipRect(
       2 * rectInset,2 * rectInset,
       clipRectRight - 2 * rectInset,
       clipRectBottom - 2 * rectInset
   )
   // The method clipRect(float, float, float, float, Region.Op
   // .DIFFERENCE) was deprecated in API level 26. The recommended
   // alternative method is clipOutRect(float, float, float, float),
   // which is currently available in API level 26 and higher.
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O){
       canvas.clipRect(
           4 * rectInset,4 * rectInset,
           clipRectRight - 4 * rectInset,
           clipRectBottom - 4 * rectInset,
            Region.Op.DIFFERENCE
       )
   } else {
       canvas.clipOutRect(
           4 * rectInset,4 * rectInset,
           clipRectRight - 4 * rectInset,
           clipRectBottom - 4 * rectInset
       )
   }
   drawClippedRectangle(canvas)
   canvas.restore()
}
  1. Execute o app e ele ficará assim.

Etapa 2: implementar drawCircularClippingExample(tela)

Em seguida, adicione o código para desenhar um retângulo que usa uma região de recorte circular criada a partir de um caminho circular. Esse processo remove o círculo (não desenha) e mostra o plano de fundo cinza.

private fun drawCircularClippingExample(canvas: Canvas) {

   canvas.save()
   canvas.translate(columnOne, rowTwo)
   // Clears any lines and curves from the path but unlike reset(),
   // keeps the internal data structure for faster reuse.
   path.rewind()
   path.addCircle(
       circleRadius,clipRectBottom - circleRadius,
       circleRadius,Path.Direction.CCW
   )
   // The method clipPath(path, Region.Op.DIFFERENCE) was deprecated in
   // API level 26. The recommended alternative method is
   // clipOutPath(Path), which is currently available in
   // API level 26 and higher.
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
       canvas.clipPath(path, Region.Op.DIFFERENCE)
   } else {
       canvas.clipOutPath(path)
   }
   drawClippedRectangle(canvas)
   canvas.restore()
}

Etapa 3: implementar drawIntersectionClippingExample(tela)

Em seguida, adicione o código para desenhar a interseção de dois retângulos de recorte na segunda linha e coluna.

Dependendo da resolução da tela, a aparência da região varia. Teste a dimensão smallRectOffset para mudar o tamanho da região visível. Um smallRectOffset menor resulta em uma região maior na tela.

private fun drawIntersectionClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnTwo,rowTwo)
   canvas.clipRect(
       clipRectLeft,clipRectTop,
       clipRectRight - smallRectOffset,
       clipRectBottom - smallRectOffset
   )
   // The method clipRect(float, float, float, float, Region.Op
   // .INTERSECT) was deprecated in API level 26. The recommended
   // alternative method is clipRect(float, float, float, float), which
   // is currently available in API level 26 and higher.
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
       canvas.clipRect(
           clipRectLeft + smallRectOffset,
           clipRectTop + smallRectOffset,
           clipRectRight,clipRectBottom,
           Region.Op.INTERSECT
       )
   } else {
       canvas.clipRect(
           clipRectLeft + smallRectOffset,
           clipRectTop + smallRectOffset,
           clipRectRight,clipRectBottom
       )
   }
   drawClippedRectangle(canvas)
   canvas.restore()
}

Etapa 4: implementar drawCombinedClippingExample(tela)

Em seguida, combine formas, um círculo e um retângulo e desenhe um caminho para definir uma região de recorte.

private fun drawCombinedClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnOne, rowThree)
   path.rewind()
   path.addCircle(
       clipRectLeft + rectInset + circleRadius,
       clipRectTop + circleRadius + rectInset,
       circleRadius,Path.Direction.CCW
   )
   path.addRect(
       clipRectRight / 2 - circleRadius,
       clipRectTop + circleRadius + rectInset,
       clipRectRight / 2 + circleRadius,
       clipRectBottom - rectInset,Path.Direction.CCW
   )
   canvas.clipPath(path)
   drawClippedRectangle(canvas)
   canvas.restore()
}

Etapa 5: implementar drawRoundedRectangleClippingExemplo(tela)

Em seguida, adicione um retângulo arredondado, que é uma forma de recorte usada com frequência.

  1. No nível superior, crie e inicialize uma variável retangular. RectF é uma classe que contém coordenadas retangulares em ponto flutuante.
private var rectF = RectF(
   rectInset,
   rectInset,
   clipRectRight - rectInset,
   clipRectBottom - rectInset
)
  1. Implemente a função drawRoundedRectangleClippingExample(). A função addRoundRect() usa um retângulo, valores para os valores de x e y do raio do canto e a direção para enrolar o contorno do retângulo circular. O ícone Path.Direction especifica como as formas fechadas (como retângulos e ovais) são orientadas quando são adicionadas a um caminho. CCW significa sentido anti-horário.
private fun drawRoundedRectangleClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnTwo,rowThree)
   path.rewind()
   path.addRoundRect(
       rectF,clipRectRight / 4,
       clipRectRight / 4, Path.Direction.CCW
   )
   canvas.clipPath(path)
   drawClippedRectangle(canvas)
   canvas.restore()
}

Etapa 6: implementar drawOutsideClippingExample(tela)

Corte o exterior do retângulo ao dobrar as inserções do retângulo cortado.

private fun drawOutsideClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnOne,rowFour)
   canvas.clipRect(2 * rectInset,2 * rectInset,
       clipRectRight - 2 * rectInset,
       clipRectBottom - 2 * rectInset)
   drawClippedRectangle(canvas)
   canvas.restore()
}

Etapa 7: implementar drawTranslatedTextExample(Canvas)

Desenhar texto não é muito diferente de outras formas, e é possível aplicar transformações a ele. Por exemplo, você pode traduzir texto traduzindo a tela e desenhando o texto.

  1. Implemente a função abaixo.
private fun drawTranslatedTextExample(canvas: Canvas) {
   canvas.save()
   paint.color = Color.GREEN
   // Align the RIGHT side of the text with the origin.
   paint.textAlign = Paint.Align.LEFT
   // Apply transformation to canvas.
   canvas.translate(columnTwo,textRow)
   // Draw text.
   canvas.drawText(context.getString(R.string.translated),
       clipRectLeft,clipRectTop,paint)
   canvas.restore()
}
  1. Execute o app para ver o texto traduzido.

Etapa 8: implementar drawSkewedTextExample(Canvas)

Também é possível inclinar o texto. Ou seja, distorça-o de várias maneiras.

  1. Crie a função abaixo em ClippedView.
private fun drawSkewedTextExample(canvas: Canvas) {
   canvas.save()
   paint.color = Color.YELLOW
   paint.textAlign = Paint.Align.RIGHT
   // Position text.
   canvas.translate(columnTwo, textRow)
   // Apply skew transformation.
   canvas.skew(0.2f, 0.3f)
   canvas.drawText(context.getString(R.string.skewed),
       clipRectLeft, clipRectTop, paint)
   canvas.restore()
}
  1. Execute o app para ver o texto distorcido desenhado antes do texto traduzido.

O método Canvas quickReject() permite verificar se um retângulo ou caminho especificado estaria completamente fora das regiões visíveis no momento, depois que todas as transformações fossem aplicadas.

O método quickReject() é incrivelmente útil quando você está criando desenhos mais complexos e precisa fazer isso o mais rápido possível. Com o quickReject(), é possível decidir com eficiência quais objetos não é necessário desenhar, e não é necessário escrever a própria lógica de interseção.

  • O método quickReject() retornará true se o retângulo ou caminho não estiver visível na tela. Para sobreposições parciais, você ainda precisará fazer sua própria verificação.
  • O EdgeType é AA (anti-aliasing: trata as bordas arredondando para baixo, porque elas podem ser antialiases) ou BW (branco-branco: trata as bordas apenas arredondando para o limite de pixels mais próximo) para arredondar para o pixel mais próximo.

Há várias versões de quickReject(), e você também pode encontrá-las na documentação.

boolean

quickje(float left, float top, float right, float bottom, Canvas.EdgeType type)

boolean

quickje(RectF rect, Canvas.EdgeType type)

boolean

quickRejected(Caminho path, Canvas.EdgeType type)

Neste exercício, você desenhará em uma nova linha, abaixo do texto, e dentro da clipRect, como antes.

  • Primeiro, chame quickReject() com um retângulo inClipRectangle que se sobrepõe ao clipRect. Portanto, quickReject() retorna falso, clipRect é preenchido com BLACK e o retângulo inClipRectangle é desenhado.

  • Em seguida, mude o código e chame quickReject(), com notInClipRectangle. quickReject() agora retorna "true", clipRect é preenchido com WHITE e notInClipRectangle não é exibido

Quando você tem desenhos complexos, isso pode informar rapidamente quais formas estão completamente fora da região de recorte e para as quais você pode precisar fazer cálculos e desenhos adicionais, porque eles estão parcial ou totalmente dentro da região de recorte.

Etapa: testar com quickRejected()

  1. No nível superior, crie uma variável para as coordenadas y de outra linha.
   private val rejectRow = rowFour + rectInset + 2*clipRectBottom
  1. Adicione a seguinte função drawQuickRejectExample() a ClippedView. Leia o código, já que ele contém tudo o que você precisa saber para usar o quickReject().
private fun drawQuickRejectExample(canvas: Canvas) {
   val inClipRectangle = RectF(clipRectRight / 2,
       clipRectBottom / 2,
       clipRectRight * 2,
       clipRectBottom * 2)

   val notInClipRectangle = RectF(RectF(clipRectRight+1,
       clipRectBottom+1,
       clipRectRight * 2,
       clipRectBottom * 2))

   canvas.save()
   canvas.translate(columnOne, rejectRow)
   canvas.clipRect(
       clipRectLeft,clipRectTop,
       clipRectRight,clipRectBottom
   )
   if (canvas.quickReject(
           inClipRectangle, Canvas.EdgeType.AA)) {
       canvas.drawColor(Color.WHITE)
   }
   else {
       canvas.drawColor(Color.BLACK)
       canvas.drawRect(inClipRectangle, paint
       )
   }
       canvas.restore()
}
  1. Em onDraw(), remova a marca do comentário da invocação de drawQuickRejectExample().
  2. Execute o app e você verá um retângulo preto, que é a região de recorte preenchida, e partes do inClipRectangle, porque os dois retângulos se sobrepõem. quickReject() retorna false e inClipRectangle é desenhado.

  1. Em drawQuickRejectExample(), mude o código para executar quickReject() em notInClipRectangle.agora quickReject() retorna true e a região de corte é preenchida em branco.

Faça o download do código do codelab concluído.

$  git clone https://github.com/googlecodelabs/android-kotlin-drawing-clipping


Como alternativa, é possível fazer o download do repositório como um arquivo ZIP, descompactá-lo e abri-lo no Android Studio.

Fazer o download do ZIP

  • O Context de uma atividade mantém um estado que preserva as transformações e as regiões de recorte da Canvas.
  • Use canvas.save() e canvas.restore() para desenhar e retornar ao estado original da tela.
  • Para desenhar várias formas em uma tela, calcule o local ou mova (traduza) a origem da superfície de desenho. O último pode facilitar a criação de métodos utilitários para sequências de desenho repetidas.
  • As regiões de corte podem ser qualquer forma, combinação de formas ou caminho.
  • É possível adicionar, subtrair e cruzar regiões de recorte para chegar exatamente à região de que você precisa.
  • Transforme o Canvas para aplicar transformações em texto.
  • O método quickReject() Canvas permite verificar se um retângulo ou caminho especificado estaria completamente fora das regiões visíveis no momento.

Curso da Udacity:

Documentação do desenvolvedor Android:

Veja também a série de artigos Arquitetura de gráficos para uma explicação detalhada de como o framework do Android é exibido na tela.

Esta seção lista as possíveis atividades para os alunos que estão trabalhando neste codelab como parte de um curso ministrado por um instrutor. Cabe ao instrutor fazer o seguinte:

  • Se necessário, atribua o dever de casa.
  • Informe aos alunos como enviar o dever de casa.
  • Atribua nota aos trabalhos de casa.

Os professores podem usar essas sugestões o quanto quiserem, e eles devem se sentir à vontade para passar o dever de casa como achar adequado.

Se você estiver fazendo este codelab por conta própria, use essas atividades para testar seu conhecimento.

Responda a estas perguntas

Pergunta 1

Qual método você chama para excluir formas de forma eficiente?

excludeFromDrawing()

quickReject()

onDraw()

clipRect()

Pergunta 2

Canvas.save() e Canvas.restore() salvam e restauram quais informações?

▢ Cor, largura de linha etc.

▢ Apenas transformações atuais

▢ Transformações atuais e região de recorte

▢ Somente a área de corte atual

Pergunta 3

Paint.Align especifica:

▢ Como alinhar as formas de desenho a seguir

▢ De que lado da origem o texto é retirado

▢ Onde na região de corte está alinhada

▢ Qual lado do texto se alinhará à origem

Para ver links de outros codelabs neste curso, consulte a página de destino dos codelabs avançados no Android.