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.

Introdução

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.

O que você já deveria saber

Você deve estar familiarizado com:

O que você aprenderá

O que você vai fazer

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.

Etapa: Crie o projeto FindMe

  1. Abra o Android Studio.
  2. Crie um novo projeto Kotlin chamado FindMe que usa o modelo Empty Activity.
  3. Abra 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>
  1. Pegue a imagem de um Android em um skate no GitHub e adicione-a à sua pasta drawable. Alternativamente, você pode usar uma pequena imagem de sua escolha com dimensões próximas a 120 X 120 pixels, chamada android, e adicioná-la à sua pasta drawable.

  1. Baixe a imagem da máscara (https://github.com/googletutoriais/android-drawing-shaders/blob/master/app/src/main/res/drawable/mask.png) e adicione-a a sua pasta drawable. Você usará esta imagem para mascarar os holofotes. Você aprenderá sobre mascaramento posteriormente neste tutorial.

Nesta tarefa, você cria uma ImageView, SpotLightImageView customizada e declara algumas variáveis ​​auxiliares. Esta aula é onde o jogo acontece. SpotLightImageView

Etapa: Crie a classe SpotLightImageView

  1. Crie uma nova classe Kotlin chamada SpotLightImageView.
  2. Estenda a classe SpotLightImageView de AppCompatImageView. Importe androidx.appcompat.widget.AppCompatImageView quando solicitado.
class SpotLightImageView : AppCompatImageView {
}
  1. Clique em 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) {
}
  1. Em 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
  1. Em 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

  1. Abra 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.)
  2. Substitua a vista de texto Hello world! pela vista personalizada, 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" />
  1. Execute seu aplicativo. Observe uma tela branca em branco, porque você não está desenhando nada na 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:

Conceito: PorterDuff.Mode

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:

PorterDuff.Mode

Resultado da compostagem???

DST

Os pixels de origem são descartados, deixando o destino intacto.

DST_ATOP

Os pixels de destino que não são cobertos pelos pixels de origem são descartados.

DST_IN

Os pixels de destino que cobrem os pixels de origem são mantidos e os pixels de origem e destino restantes são descartados.

DST_OUT

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.

Etapa 1: Crie o bitmap de destino

  1. Em SpotLightImageView, adicione um bloco init.
  2. Dentro do bloco 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)
}
  1. No final do bloco init, crie e inicialize um objeto Canvas com o novo bitmap.
  2. Abaixo, crie e inicialize um objeto Paint.
val canvas = Canvas(bitmap)
val shaderPaint = Paint(Paint.ANTI_ALIAS_FLAG)
  1. Crie a textura do bitmap e pinte o bitmap de preto. Em uma etapa posterior, você criará o efeito de destaque ao compor a imagem de máscara que você baixou anteriormente. Desenhe um retângulo preto do mesmo tamanho que o bitmap de destaque.
// Draw a black rectangle.
shaderPaint.color = Color.BLACK
canvas.drawRect(0.0f, 0.0f, spotlight.width.toFloat(), spotlight.height.toFloat(), shaderPaint)

Destino

Etapa 2: mascarar o refletor do retângulo preto

  1. No final do bloco 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

Etapa 3: Crie o BitmapShader

TileMode

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

  1. Em SpotLightImageView, declare uma variável de classe private lateinit do tipo Shader.
private var shader: Shader
  1. No final do bloco 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.

  1. Adicione o 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
  1. O bloco 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.

Etapa 1: desenhe a textura

  1. Em SpotLightImageView, substitua o método onDraw().
  2. Em onDraw(), pinte o fundo de amarelo usando a canvas.
  3. Desenhe um retângulo do mesmo tamanho que a textura usando o objeto 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)
}
  1. Execute o aplicativo. Observe como a textura de bitmap que você criou é desenhada no canto superior esquerdo da tela com um fundo amarelo.

Etapa 2: experimente os modos de mosaico

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:

  1. Em 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)
}
  1. Execute o aplicativo. Observe que a textura do holofote é desenhada apenas uma vez e o resto do retângulo é preenchido com a cor da borda, preto.

  1. Experimente diferentes modos de tiling nas direções X e Y.

X = TileMode.REPEAT

Y = TileMode.CLAMP

X = TileMode.CLAMP

Y = TileMode.REPEAT

X = TileMode.REPEAT

Y = TileMode.REPEAT

Etapa 3: Traduzir matriz de Shader

Nesta etapa, você aprenderá a traduzir a textura (shader) para qualquer local na tela e desenhá-la.

Tradução de matriz

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.

  1. Em SpotLightImageView, crie uma variável de classe do tipo Matrix e inicialize-a.
private val shaderMatrix = Matrix()
  1. Atualize o 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)
  1. Dentro do método onDraw(), antes da chamada para drawRect(), traduza a matriz de sombreador para valores X e Y aleatórios.
  2. Defina a matriz local do shader como traduzida shaderMatrix.
shaderMatrix.setTranslate(
   100f,
   550f
)
shader.setLocalMatrix(shaderMatrix)
  1. Desenhe alguma forma arbitrária usando a 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)
   }
  1. Execute seu aplicativo. Experimente traduzir a matriz de sombreamento para locais diferentes, alterar os modos dos blocos e desenhar formas e tamanhos diferentes com a paint. Abaixo estão alguns efeitos que você pode criar.

  1. Reverta ou comente as alterações de código feitas nesta tarefa.

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.

  1. Em SpotLightImageView, adicione um novo método private chamado setupWinnerRect().
  2. Defina 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.
  3. No final de 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.

Etapa 1: substituir onSizeChanged()

  1. Em 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()
}

Etapa 2: substituir onDraw()

Nesta etapa, você desenhará a imagem Android em um fundo branco, usando o objeto Paint com o sombreador de bitmap.

  1. Em SpotLightImageView, substitua onDraw().
  2. Em onDraw(), remova o código para mostrar a textura que você adicionou em uma tarefa anterior.
  3. Em 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)
}
  1. Execute o aplicativo. Altere a orientação da tela do dispositivo / emulador para reiniciar a atividade. Cada vez que a atividade é reiniciada, a imagem do Android é exibida em uma posição aleatória diferente.

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.

Etapa 1: substituir e implementar o método onTouchEvent()

  1. Em SpotLightImageView, substitua o método onTouchEvent(). Crie motionEventX e motionEventY para armazenar as coordenadas de toque do usuário.
  2. Você precisa retornar um booleano. Já que você está tratando com o evento de movimento, faça com que o método retorne true.
override fun onTouchEvent(motionEvent: MotionEvent): Boolean {
   val motionEventX = motionEvent.x
   val motionEventY = motionEvent.y
   return true
 }
  1. Logo antes da instrução 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 -> {
      
   }
}
  1. Dentro do 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.
  2. Dentro do 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)
   }
}

Etapa 2: traduzir a matriz de sombreador

Tradução de matriz (atualização)

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.

  1. Em SpotLightImageView, adicione uma nova variável para salvar a matriz de sombreador. Importe android.graphics.Matrix, quando solicitado.
private val shaderMatrix = Matrix()
  1. No final do método 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
)
  1. Defina a matriz local do shader para a nova shaderMatrix.
shader.setLocalMatrix(shaderMatrix)
  1. Chame invalidate() para acionar uma chamada para onDraw(), que redesenha o shader na nova posição.
  2. O método completo deve ser semelhante a este:
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
}
  1. Execute seu aplicativo. Observe a imagem do Android no fundo branco. Seu app de jogo está quase pronto. A única implementação que falta é a tela preta com holofote.
  2. Clique na imagem do Android, para simular que você encontrou o Android e ganhou e o jogo.
  3. Clique em outro lugar na tela para reiniciar o jogo, e agora a imagem do Android será exibida em um local aleatório diferente.

Nesta tarefa, você desenhará um retângulo escuro de tela inteira com o holofote usando o BitmapShader com a textura que você criou.

  1. No final do método 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.
  2. Se shouldDrawSpotLight for false, pinte a tela de preto.
  3. O método completo deve ser semelhante a este:
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)
       }
   }
}
  1. Execute seu aplicativo e JOGUE!
  2. Depois de ganhar, toque na tela para jogar novamente.

Nesta tarefa, você adicionará uma caixa de diálogo de alerta com instruções sobre como jogar.

  1. Em 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()
}
  1. Em MainActivity, no final do método onCreate(), exiba a caixa de diálogo de alerta.
val dialog = createInstructionsDialog()
dialog.show()
  1. Execute seu aplicativo. Você deverá ver uma caixa de diálogo com instruções e um botão de reprodução. Toque no botão play e jogue o jogo.

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.

Baixe o Zip

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.