Este tutorial faz parte do curso Android avançado em Kotlin. Você obterá o máximo valor deste curso se trabalhar com os tutoriais em sequência, mas isso não é obrigatório. Todos os tutoriais do curso estão listados na página de destino Android avançado em Kotlin.
Em computação gráfica, um sombreador é um tipo de programa de computador originalmente usado para sombreamento (a produção de níveis apropriados de luz, escuridão e cor em uma imagem), mas que agora executa uma variedade de funções especializadas em vários campos da computação gráfica efeitos especiais.
No Android, Shader
define a (s) cor (es) ou a textura com a qual o objeto Paint
deve desenhar (outro do que um bitmap). O Android define várias subclasses de Shader
para Paint
usar, como BitmapShader
, ComposeShader
, LinearGradient
, RadialGradient
, e SweepGradient
.
Por exemplo, você pode usar um BitmapShader
para definir um bitmap como uma textura para o objeto Paint
. Isso permite que você implemente temas personalizados com efeitos translúcidos e vistas personalizadas com um bitmap como textura. Você também pode usar máscaras com animações de transição para criar efeitos visuais impressionantes. Para desenhar imagens em formas diferentes (retângulo arredondado neste exemplo), você pode definir um BitmapShader
para seu objeto Paint
e usar o drawRoundRect()
método para desenhar um retângulo com cantos arredondados, conforme mostrado abaixo.
Neste tutorial, você usará um BitmapShader
para definir um bitmap como uma textura para o objeto Paint
, em vez de uma cor simples.
Você deve estar familiarizado com:
View
personalizada.Canvas
. Shader
para um Paint
e usá-lo para modificar o que está sendo desenhado.O aplicativo FindMe permite pesquisar uma imagem do Android na tela escura do telefone usando um "holofote".
As capturas de tela a seguir mostram o aplicativo FindMe na inicialização, quando o usuário está arrastando o dedo e depois que o usuário encontrou a imagem do Android movendo-se em torno do refletor.
Características adicionais:
Nesta tarefa, você cria o aplicativo FindMe do zero, portanto, irá configurar um projeto e definir algumas strings.
strings.xml
e adicione as seguintes strings para o título do aplicativo e as instruções do jogo.
<resources>
<string name="app_name">Find the Android</string>
<string name="instructions_title">
<b>How to play:</b>
</string>
<string name="instructions">
\t \u2022 Find the Android hidden behind the dark surface. \n
\t \u2022 Touch and hold the screen for the spotlight. \n
\t \u2022 Once you find the Android, lift your finger to end the game. \n \n
\t \u2022 To restart the game touch the screen again.
</string>
</resources>
android
, e adicioná-la à sua pasta drawable.Nesta tarefa, você cria uma ImageView
, SpotLightImageView
customizada e declara algumas variáveis auxiliares. Esta aula é onde o jogo acontece. SpotLightImageView
SpotLightImageView
. SpotLightImageView
de AppCompatImageView
. Importe androidx.appcompat.widget.AppCompatImageView
quando solicitado.class SpotLightImageView : AppCompatImageView {
}
AppCompatImageView
e, a seguir, clique na lâmpada vermelha. Escolha Add Android View constructors using '@JvmOverloads'. A anotação @JvmOverloads
instrui o compilador Kotlin a gerar uma sobrecarga adicional para cada parâmetro com um valor padrão, que tem este parâmetro e todos os parâmetros à direita dele no parâmetro lista removida. Depois que o Android Studio adiciona o construtor da classe AppCompatImageView
, o código gerado deve ser semelhante ao código abaixo.
class SpotLightImageView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {
}
SpotLightImageView
, declare e defina as seguintes variáveis de classe. private var paint = Paint()
private var shouldDrawSpotLight = false
private var gameOver = false
private lateinit var winnerRect: RectF
private var androidBitmapX = 0f
private var androidBitmapY = 0f
SpotLightImageView
, crie e inicialize variáveis para a imagem Android e a máscara.
private val bitmapAndroid = BitmapFactory.decodeResource(
resources,
R.drawable.android
)
private val spotlight = BitmapFactory.decodeResource(resources, R.drawable.mask)
Imagem Android |
mascarar |
activity_main.xml
. (No Android Studio 4.0 e posterior, clique no ícone Splitno canto superior direito para mostrar o código XML e o painel de prévia.)SpotLightImageView
, conforme mostrado no código a seguir. Certifique-se de que o nome do seu pacote e o nome da vista personalizada correspondam.<com.example.android.findme.SpotLightImageView
android:id="@+id/spotLightImageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
SpotLightImageView
ainda. Um Shader
define a textura para um objeto Paint
. Uma subclasse de Shader
é instalada em um Paint
chamando paint.setShader(shader)
. Depois disso, qualquer objeto (que não seja um bitmap) desenhado com esse Paint
obterá sua (s) cor (es) do shader.
O Android fornece as seguintes subclasses de Shader
para Paint
usar:
LinearGradient
desenha um gradiente linear usando duas ou mais cores fornecidas.RadialGradient
desenha um gradiente radial usando as (duas ou mais) cores fornecidas, o centro e o raio. As cores são distribuídas entre o centro e a borda do círculo.
SweepGradient
, desenha um gradiente de varredura em torno de um ponto central com as cores especificadas.ComposeShader
é uma composição de dois shaders. ComposeShader
e modos de composição estão além do escopo deste tutorial, leia a documentação do ComposeShader para aprender mais. BitmapShader
desenha um drawable bitmap como uma textura. O bitmap pode ser repetido ou espelhado configurando o modo TileMode. Você aprenderá mais sobre BitmapShader
e TileMode posteriormente neste tutorial.A classe PorterDuff.Mode
fornece vários modos de composição e combinação Alpha. A composição alfa é o processo de composição (ou combinação) de uma imagem de origem com uma imagem de destino para criar a aparência de transparência parcial ou total. A transparência é definida pelo canal alfa. O canal alfa representa o grau de transparência de uma cor, ou seja, para seus canais vermelho, verde e azul.
Para aprender sobre os modos de mesclagem, consulte a documentação dos modos de mesclagem.
Por exemplo, considere as seguintes imagens de origem e destino.
Imagem de origem |
Imagem de destino |
Aqui está uma tabela que define alguns dos modos de composição Alpha:
Resultado da compostagem??? |
|
|
Os pixels de origem são descartados, deixando o destino intacto. |
|
Os pixels de destino que não são cobertos pelos pixels de origem são descartados. |
|
Os pixels de destino que cobrem os pixels de origem são mantidos e os pixels de origem e destino restantes são descartados. |
|
Os pixels de destino que não são cobertos pelos pixels de origem são mantidos. |
Você usará o DST_OUT
Modo PorterDuff para inverter o recurso de máscara em um retângulo preto com um holofote. Para aprender mais sobre os outros modos, consulte a documentação dos Modos de composição Alpha.
Nesta tarefa, você cria uma textura usando um bitmap de máscara para o sombreador usar.
BitmapShader
. PorterDuff.Mode
.SpotLightImageView
, adicione um bloco init
.init
, crie um bitmap do mesmo tamanho que o bitmap de destaque que você criou a partir da imagem da máscara, usando createBitmap()
. init {
val bitmap = Bitmap.createBitmap(spotlight.width, spotlight.height, Bitmap.Config.ARGB_8888)
}
init
, crie e inicialize um objeto Canvas
com o novo bitmap. Paint
. val canvas = Canvas(bitmap)
val shaderPaint = Paint(Paint.ANTI_ALIAS_FLAG)
// Draw a black rectangle.
shaderPaint.color = Color.BLACK
canvas.drawRect(0.0f, 0.0f, spotlight.width.toFloat(), spotlight.height.toFloat(), shaderPaint)
Destino
init
, use o modo de composição DST_OUT
para mascarar o destaque do retângulo preto.shaderPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT)
canvas.drawBitmap(spotlight, 0.0f, 0.0f, shaderPaint)
Textura do resultado
O modo tiling, TileMode
, definido no Shader
, especifica como o drawable de bitmap é repetido ou espelhado no Direções X e Y se o drawable de bitmap sendo usado para textura for menor que a tela. O Android oferece três maneiras diferentes de repetir (lado a lado) o drawable de bitmap (textura):
Amostra de imagem de shader:
Saídas de exemplo para os diferentes tilemodes:
Repetir Tilemode |
Clamp Tilemode |
Mirror Tilemode |
SpotLightImageView
, declare uma variável de classe private lateinit
do tipo Shader
. private var shader: Shader
init
, crie um shader de bitmap usando o construtor, BitmapShader(). Passe o bitmap de textura, bitmap
, e o modo de mosaico como CLAMP
. O modo de til de grampo desenhará tudo fora do círculo com a cor da borda da textura, que neste caso é preta.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
O shader
criado com o tilemodo CLAMP
é semelhante à imagem abaixo, e a cor da borda, preta, é desenhada para preencher o espaço restante.
shader
que você criou acima ao objeto paint
. O shader
contém a textura e instruções (como ladrilho) sobre como aplicar a textura.paint.shader = shader
init
concluído agora deve ser semelhante ao código abaixo.init {
val bitmap = Bitmap.createBitmap(spotlight.width, spotlight.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
val shaderPaint = Paint(Paint.ANTI_ALIAS_FLAG)
// Draw a black rectangle.
shaderPaint.color = Color.BLACK
canvas.drawRect(0.0f, 0.0f, spotlight.width.toFloat(), spotlight.height.toFloat(), shaderPaint)
// Use the DST_OUT compositing mode to mask out the spotlight from the black rectangle.
shaderPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT)
canvas.drawBitmap(spotlight, 0.0f, 0.0f, shaderPaint)
shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
paint.shader = shader
}
Seu sombreador de bitmap está pronto para uso e você aprenderá como usá-lo em uma tarefa posterior.
Nesta tarefa, você atualizará o aplicativo para ver a textura criada na tarefa anterior. Você também experimentará os modos lado a lado. Esta tarefa é opcional e o código adicionado nesta tarefa deve ser revertido ou comentado quando terminar.
SpotLightImageView
, substitua o método onDraw()
.onDraw()
, pinte o fundo de amarelo usando a canvas
. Paint
com o Shader
. Isso deve desenhar a textura uma vez.override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Color the background yellow.
canvas.drawColor(Color.YELLOW)
canvas.drawRect(0.0f, 0.0f,spotlight.width.toFloat(), spotlight.height.toFloat(), paint)
}
Se o tamanho do objeto que está sendo desenhado (como o retângulo na etapa anterior) for maior do que a textura, o que geralmente é o caso. Você pode dividir a textura de bitmap de diferentes maneiras - CLAMP, REPEAT e MIRROR. O modo lado a lado para o sombreador que você criou na tarefa anterior é CLAMP, já que você deseja desenhar o holofote apenas uma vez e preencher o restante com preto.
Para ver o sombreador que você criou em ação:
SpotLightImageView
, dentro do método onDraw()
, atualize a chamada do método drawRect()
. Desenhe o retângulo do tamanho da tela.override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Color the background Yellow.
canvas.drawColor(Color.YELLOW)
// canvas.drawRect(0.0f, 0.0f,spotlight.width.toFloat(), spotlight.height.toFloat(), paint)
canvas.drawRect(0.0f, 0.0f, width.toFloat(), height.toFloat(), paint)
}
X = Y = |
X = Y = |
X = Y = |
Nesta etapa, você aprenderá a traduzir a textura (shader) para qualquer local na tela e desenhá-la.
Quando o usuário toca e segura a tela do holofote, em vez de calcular onde o holofote precisa ser desenhado, você move a matriz de sombreamento; ou seja, o sistema de coordenadas de textura / sombreador e, em seguida, desenhe a textura (o holofote) no mesmo local no sistema de coordenadas traduzido. O efeito resultante parecerá como se você estivesse desenhando a textura do holofote em um local diferente, que é o mesmo que o local traduzido da matriz de sombreamento. Isso é mais simples e um pouco mais eficiente.
SpotLightImageView
, crie uma variável de classe do tipo Matrix
e inicialize-a. private val shaderMatrix = Matrix()
BitmapShader
no bloco init
. Mude o modo de bloco para CLAMP
nas direções X e Y. Isso desenhará a textura apenas uma vez, o que torna a observação da tradução da matriz Shader
direta. shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
onDraw()
, antes da chamada para drawRect(),
traduza a matriz de sombreador para valores X e Y aleatórios.shaderMatrix.
shaderMatrix.setTranslate(
100f,
550f
)
shader.setLocalMatrix(shaderMatrix)
paint
que você definiu anteriormente. Este código desenhará um retângulo do tamanho da metade da tela. override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawColor(Color.YELLOW)
shaderMatrix.setTranslate(
100f,
550f
)
shader.setLocalMatrix(shaderMatrix)
canvas.drawRect(0.0f, 0.0f, width.toFloat(), height.toFloat()/2, paint)
}
paint
. Abaixo estão alguns efeitos que você pode criar. Nesta tarefa, você calculará um local aleatório na tela para o bitmap de imagem Android que o jogador deve encontrar. Você também precisa calcular se o usuário encontrou o bitmap.
SpotLightImageView
, adicione um novo método private
chamado setupWinnerRect()
.androidBitmapX
e androidBitmapY
para posições x e y aleatórias que caiam dentro da tela. Importe kotlin.math.floor
e kotlin.random.Random
, quando solicitado. setupWinnerRect()
, inicialize winnerRect
com uma caixa delimitadora retangular que contém a imagem Android. private fun setupWinnerRect() {
androidBitmapX = floor(Random.nextFloat() * (width - bitmapAndroid.width))
androidBitmapY = floor(Random.nextFloat() * (height - bitmapAndroid.height))
winnerRect = RectF(
(androidBitmapX),
(androidBitmapY),
(androidBitmapX + bitmapAndroid.width),
(androidBitmapY + bitmapAndroid.height)
)
}
Nesta tarefa, você substituirá e implementará onSizeChanged()
e onDraw()
em SpotLightImageView
.
onSizeChanged()
SpotLightImageView
, substitua onSizeChanged().
Chame setupWinnerRect()
a partir dele. override fun onSizeChanged(
newWidth: Int,
newHeight: Int,
oldWidth: Int,
oldHeight: Int
) {
super.onSizeChanged(newWidth, newHeight, oldWidth, oldHeight)
setupWinnerRect()
}
onDraw()
Nesta etapa, você desenhará a imagem Android em um fundo branco, usando o objeto Paint
com o sombreador de bitmap.
SpotLightImageView
, substitua onDraw()
.onDraw()
, remova o código para mostrar a textura que você adicionou em uma tarefa anterior.
onDraw()
, pinte a tela de branco e desenhe a imagem Android nas posições aleatórias androidBitmapX
, androidBitmapY
que você calculou em onSizeChanged()
. override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawColor(Color.WHITE)
canvas.drawBitmap(bitmapAndroid, androidBitmapX, androidBitmapY, paint)
}
Para que o jogo funcione, seu aplicativo precisa detectar e responder aos movimentos do usuário na tela. Você traduzirá a matriz de sombreador de holofote em resposta aos eventos de toque do usuário, de modo que o holofote siga o toque do usuário.
SpotLightImageView
, substitua o método onTouchEvent()
. Crie motionEventX
e motionEventY
para armazenar as coordenadas de toque do usuário. true
.override fun onTouchEvent(motionEvent: MotionEvent): Boolean {
val motionEventX = motionEvent.x
val motionEventY = motionEvent.y
return true
}
return
, adicione um bloco when
em motionEvent.
action
. Adicione blocos de caso para MotionEvent.
ACTION_DOWN
e MotionEvent.
ACTION_UP
.when (motionEvent.action) {
MotionEvent.ACTION_DOWN -> {
}
MotionEvent.ACTION_UP -> {
}
}
MotionEvent.
ACTION_DOWN
-> {}
caso, defina shouldDrawSpotLight
para true
. Verifique se gameOver
é verdadeiro, e se for, reset
para falso e chame setupWinnerRect()
para reiniciar o jogo.MotionEvent.
ACTION_UP
-> {}
caso, defina shouldDrawSpotLight
para false
. Verifique se o centro do holofote está dentro do retângulo vencedor.when (motionEvent.action) {
MotionEvent.ACTION_DOWN -> {
shouldDrawSpotLight = true
if (gameOver) {
gameOver = false
setupWinnerRect()
}
}
MotionEvent.ACTION_UP -> {
shouldDrawSpotLight = false
gameOver = winnerRect.contains(motionEventX, motionEventY)
}
}
Quando o usuário toca e segura a tela do holofote, em vez de calcular onde o holofote precisa ser desenhado, você move a matriz de sombreamento; ou seja, o sistema de coordenadas de textura / sombreador e, em seguida, desenhe a textura de destaque no mesmo local no sistema de coordenadas traduzido. O efeito resultante aparece como se você estivesse desenhando a textura do holofote em um local diferente, que é o mesmo que o local convertido da matriz de sombreamento.
SpotLightImageView
, adicione uma nova variável para salvar a matriz de sombreador. Importe android.graphics.Matrix
, quando solicitado.private val shaderMatrix = Matrix()
onTouchEvent()
, antes da instrução return
, traduza a shaderMatrix
para a nova posição com base no evento de toque do usuário.shaderMatrix.setTranslate(
motionEventX - spotlight.width / 2.0f,
motionEventY - spotlight.height / 2.0f
)
shaderMatrix.
shader.setLocalMatrix(shaderMatrix)
invalidate()
para acionar uma chamada para onDraw()
, que redesenha o shader na nova posição. override fun onTouchEvent(motionEvent: MotionEvent): Boolean {
val motionEventX = motionEvent.x
val motionEventY = motionEvent.y
when (motionEvent.action) {
MotionEvent.ACTION_DOWN -> {
shouldDrawSpotLight = true
if (gameOver) {
// New Game
gameOver = false
setupWinnerRect()
}
}
MotionEvent.ACTION_UP -> {
shouldDrawSpotLight = false
gameOver = winnerRect.contains(motionEventX, motionEventY)
}
}
shaderMatrix.setTranslate(
motionEventX - spotlight.width / 2.0f,
motionEventY - spotlight.height / 2.0f
)
shader.setLocalMatrix(shaderMatrix)
invalidate()
return true
}
Nesta tarefa, você desenhará um retângulo escuro de tela inteira com o holofote usando o BitmapShader
com a textura que você criou.
onDraw()
, verifique se gameOver
é falso. Nesse caso, verifique se shouldDrawSpotLight
é true
e, em caso afirmativo, desenhe o retângulo de tela inteira usando o objeto de pintura com o sombreador de bitmap atualizado. shouldDrawSpotLight
for false
, pinte a tela de preto.override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawColor(Color.WHITE)
canvas.drawBitmap(bitmapAndroid, androidBitmapX, androidBitmapY, paint)
if (!gameOver) {
if (shouldDrawSpotLight) {
canvas.drawRect(0.0f, 0.0f, width.toFloat(), height.toFloat(), paint)
} else {
canvas.drawColor(Color.BLACK)
}
}
}
Nesta tarefa, você adicionará uma caixa de diálogo de alerta com instruções sobre como jogar.
MainActivity
, adicione um método createInstructionsDialog()
para criar um AlertDialog
. Importe androidx.appcompat.app.AlertDialog
, quando solicitado. private fun createInstructionsDialog(): Dialog {
val builder = AlertDialog.Builder(this)
builder.setIcon(R.drawable.android)
.setTitle(R.string.instructions_title)
.setMessage(R.string.instructions)
.setPositiveButtonIcon(ContextCompat.getDrawable(this, android.R.drawable.ic_media_play))
return builder.create()
}
MainActivity
, no final do método onCreate()
, exiba a caixa de diálogo de alerta.val dialog = createInstructionsDialog()
dialog.show()
Baixe o código para o tutorial concluído.
$ git clone https://github.com/googletutoriais/android-drawing-shaders
Alternativamente, você pode baixar o repositório como um arquivo Zip, descompactá-lo e abri-lo no Android Studio.
Shader
define a (s) cor (es) ou a textura com a qual o objeto Paint
deve desenhar (outro do que um bitmap). Shader
para Paint
usar, como BitmapShader
, ComposeShader
, LinearGradient
, RadialGradient
, SweepGradient
.Shader
define o conteúdo para um objeto Paint
que deve ser desenhado. Uma subclasse de Shader
é instalada em um Paint
chamando paint.setShader(shader)
.BitmapShader
desenha um drawable bitmap como uma textura. O bitmap pode ser repetido ou espelhado configurando o modo TileMode. TileMode
, definido em Shader
, especifica como o drawable bitmap é repetido no X e Y directiona. O Android oferece três maneiras de repetir o drawable de bitmap: REPEAT
, CLAMP
, MIRROR
.
Documentação do desenvolvedor Android:
De outros:
Para obter links para outros tutoriais neste curso, consulte a página inicial de tutoriais do Android avançado em Kotlin.