Como criar visualizações personalizadas

Este codelab faz parte do curso Android avançado no Kotlin. Você vai aproveitar mais este 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 do Android avançado em Kotlin.

Introdução

O Android oferece um grande conjunto de subclasses View, como Button, TextView, EditText, ImageView, CheckBox ou RadioButton. Você pode usar essas subclasses para criar uma interface que permita a interação do usuário e mostre informações no seu app. Se nenhuma das subclasses View atender às suas necessidades, crie uma subclasse View conhecida como uma visualização personalizada .

Para criar uma visualização personalizada, estenda uma subclasse View (como Button ou EditText) ou crie sua própria subclasse de View. Ao estender View diretamente, você pode criar um elemento de interface interativo de qualquer tamanho e forma substituindo o método onDraw() para que o View o desenhe.

Depois de criar uma visualização personalizada, você pode adicioná-la aos layouts de atividade da mesma forma que adicionaria um TextView ou Button.

Esta lição mostra como criar uma visualização personalizada do zero estendendo View.

O que você já precisa saber

  • Como criar um app com uma atividade e executá-lo usando o Android Studio.

O que você vai aprender

  • Como estender View para criar uma visualização personalizada.
  • Como desenhar uma visualização personalizada de formato circular.
  • Como usar listeners para processar a interação do usuário com a visualização personalizada.
  • Como usar uma visualização personalizada em um layout.

Atividades deste laboratório

  • Estenda View para criar uma visualização personalizada.
  • Inicialize a visualização personalizada com valores de desenho e pintura.
  • Substitua onDraw() para desenhar a visualização.
  • Use listeners para fornecer o comportamento da visualização personalizada.
  • Adicione a visualização personalizada a um layout.

O app CustomFanController (link em inglês) demonstra como criar uma subclasse de visualização personalizada estendendo a classe View. A nova subclasse é chamada de DialView.

O app mostra um elemento circular da interface que se parece com um controle físico de ventilador, com configurações para desligado (0), baixo (1), médio (2) e alto (3). Quando o usuário toca na visualização, o indicador de seleção se move para a próxima posição: 0-1-2-3 e volta para 0. Além disso, se a seleção for 1 ou mais, a cor de fundo da parte circular da visualização mudará de cinza para verde, indicando que a potência do ventilador está ativada.

As visualizações são os elementos básicos da interface de um app. A classe View oferece muitas subclasses, chamadas de widgets de UI, que atendem a muitas das necessidades da interface do usuário de um app Android típico.

Elementos básicos da interface, como Button e TextView, são subclasses que estendem a classe View. Para economizar tempo e esforço de desenvolvimento, é possível estender uma dessas subclasses View. A visualização personalizada herda a aparência e o comportamento da visualização mãe, e você pode substituir o comportamento ou o aspecto da aparência que quer mudar. Por exemplo, se você estender EditText para criar uma visualização personalizada, ela vai agir como uma visualização EditText, mas também poderá ser personalizada para mostrar, por exemplo, um botão X que limpa o texto do campo de entrada de texto.

Você pode estender qualquer subclasse View, como EditText, para ter uma visualização personalizada. Escolha a mais próxima do que você quer fazer. Em seguida, use a visualização personalizada como qualquer outra subclasse View em um ou mais layouts como um elemento XML com atributos.

Para criar sua própria visualização personalizada do zero, estenda a classe View. Seu código substitui os métodos View para definir a aparência e a funcionalidade da visualização. A chave para criar sua própria visualização personalizada é que você é responsável por desenhar todo o elemento da interface de qualquer tamanho e forma na tela. Se você criar uma subclasse de uma visualização existente, como Button, essa classe vai processar o desenho para você. Você vai aprender mais sobre desenho mais adiante neste codelab.

Para criar uma visualização personalizada, siga estas etapas gerais:

  • Crie uma classe de visualização personalizada que estenda View ou uma subclasse View (como Button ou EditText).
  • Se você estender uma subclasse View, substitua apenas o comportamento ou os aspectos da aparência que quer mudar.
  • Se você estender a classe View, desenhe a forma da visualização personalizada e controle a aparência dela substituindo métodos View, como onDraw() e onMeasure(), na nova classe.
  • Adicione código para responder à interação do usuário e, se necessário, renderize novamente a visualização personalizada.
  • Use a classe de visualização personalizada como um widget de interface no layout XML da atividade. Também é possível definir atributos personalizados para a visualização, oferecendo personalização em diferentes layouts.

Nesta tarefa, você vai:

  • Crie um app com um ImageView como um marcador temporário para a visualização personalizada.
  • Estenda View para criar a visualização personalizada.
  • Inicialize a visualização personalizada com valores de desenho e pintura.

Etapa 1: criar um app com um marcador de posição ImageView

  1. Crie um app Kotlin com o título CustomFanController usando o modelo Empty Activity. Verifique se o nome do pacote é com.example.android.customfancontroller.
  2. Abra activity_main.xml na guia Texto para editar o código XML.
  3. Substitua o TextView atual por este código. Esse texto funciona como um rótulo na atividade para a visualização personalizada.
<TextView
       android:id="@+id/customViewLabel"
       android:textAppearance="@style/Base.TextAppearance.AppCompat.Display3"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:padding="16dp"
       android:textColor="@android:color/black"
       android:layout_marginStart="8dp"
       android:layout_marginEnd="8dp"
       android:layout_marginTop="24dp"
       android:text="Fan Control"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toTopOf="parent"/>
  1. Adicione esse elemento ImageView ao layout. Este é um marcador de posição para a visualização personalizada que você vai criar neste codelab.
<ImageView
       android:id="@+id/dialView"
       android:layout_width="200dp"
       android:layout_height="200dp"
       android:background="@android:color/darker_gray"
       app:layout_constraintTop_toBottomOf="@+id/customViewLabel"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       android:layout_marginLeft="8dp"
       android:layout_marginRight="8dp"
       android:layout_marginTop="8dp"/>
  1. Extraia recursos de string e dimensão nos dois elementos da interface.
  2. Clique na guia Design. O layout vai ficar assim:

Etapa 2: Criar sua classe de visualização personalizada

  1. Crie uma classe Kotlin chamada DialView.
  2. Modifique a definição da classe para estender View. Importe android.view.View quando solicitado.
  3. Clique em View e depois na lâmpada vermelha. Escolha Adicionar construtores de visualização do Android usando '@JvmOverloads'. O Android Studio adiciona o construtor da classe View. A anotação @JvmOverloads instrui o compilador Kotlin a gerar sobrecargas para essa função que substituem os valores de parâmetro padrão.
class DialView @JvmOverloads constructor(
   context: Context,
   attrs: AttributeSet? = null,
   defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
  1. Acima da definição da classe DialView, logo abaixo das importações, adicione uma enum de nível superior para representar as velocidades disponíveis do ventilador. Observe que esse enum é do tipo Int porque os valores são recursos de string, e não strings reais. O Android Studio vai mostrar erros para os recursos de string ausentes em cada um desses valores. Você vai corrigir isso em uma etapa posterior.
private enum class FanSpeed(val label: Int) {
   OFF(R.string.fan_off),
   LOW(R.string.fan_low),
   MEDIUM(R.string.fan_medium),
   HIGH(R.string.fan_high);
}
  1. Abaixo do enum, adicione estas constantes. Você vai usar esses elementos para desenhar os indicadores e rótulos do dial.
private const val RADIUS_OFFSET_LABEL = 30      
private const val RADIUS_OFFSET_INDICATOR = -35
  1. Dentro da classe DialView, defina várias variáveis necessárias para desenhar a visualização personalizada. Importe android.graphics.PointF, se solicitado.
private var radius = 0.0f                   // Radius of the circle.
private var fanSpeed = FanSpeed.OFF         // The active selection.
// position variable which will be used to draw label and indicator circle position
private val pointPosition: PointF = PointF(0.0f, 0.0f)
  • O radius é o raio atual do círculo. Esse valor é definido quando a visualização é mostrada na tela.
  • O fanSpeed é a velocidade atual do ventilador, que é um dos valores na enumeração FanSpeed. Por padrão, esse valor é OFF.
  • Por fim, postPosition é um ponto X,Y que será usado para desenhar vários elementos da visualização na tela.

Esses valores são criados e inicializados aqui em vez de quando a visualização é realmente desenhada, para garantir que a etapa de desenho real seja executada o mais rápido possível.

  1. Também na definição da classe DialView, inicialize um objeto Paint com alguns estilos básicos. Importe android.graphics.Paint e android.graphics.Typeface quando solicitado. Assim como as variáveis, esses estilos são inicializados aqui para ajudar a acelerar a etapa de desenho.
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
   style = Paint.Style.FILL
   textAlign = Paint.Align.CENTER
   textSize = 55.0f
   typeface = Typeface.create( "", Typeface.BOLD)
}
  1. Abra res/values/strings.xml e adicione os recursos de string para as velocidades do ventilador:
<string name="fan_off">off</string>
<string name="fan_low">1</string>
<string name="fan_medium">2</string>
<string name="fan_high">3</string>

Depois de criar uma visualização personalizada, é necessário desenhá-la. Quando você estende uma subclasse View, como EditText, essa subclasse define a aparência e os atributos da visualização e se desenha na tela. Por isso, não é preciso escrever código para desenhar a visualização. Em vez disso, você pode substituir métodos do elemento pai para personalizar sua visualização.

Se você estiver criando sua própria visualização do zero (estendendo View), será responsável por desenhar toda a visualização sempre que a tela for atualizada e por substituir os métodos View que processam o desenho. Para desenhar corretamente uma visualização personalizada que estende View, você precisa:

  • Calcule o tamanho da visualização quando ela aparecer pela primeira vez e sempre que o tamanho dela mudar, substituindo o método onSizeChanged().
  • Substitua o método onDraw() para desenhar a visualização personalizada usando um objeto Canvas estilizado por um objeto Paint.
  • Chame o método invalidate() ao responder a um clique do usuário que muda a forma como a visualização é desenhada para invalidar toda a visualização, forçando assim uma chamada para onDraw() para redesenhar a visualização.

O método onDraw() é chamado sempre que a tela é atualizada, o que pode acontecer várias vezes por segundo. Por motivos de desempenho e para evitar falhas visuais, faça o mínimo de alterações possível no onDraw(). Em particular, não coloque alocações em onDraw(), porque elas podem levar a uma coleta de lixo que pode causar um travamento visual.

As classes Canvas e Paint oferecem vários atalhos de desenho úteis:

Você vai aprender mais sobre Canvas e Paint em um próximo codelab. Para saber mais sobre como o Android desenha visualizações, consulte Como o Android desenha visualizações.

Nesta tarefa, você vai desenhar a visualização personalizada do controlador de ventoinha na tela (o próprio botão, o indicador de posição atual e os rótulos do indicador) com os métodos onSizeChanged() e onDraw(). Você também vai criar um método auxiliar, computeXYForSpeed(),,para calcular a posição X e Y atual do rótulo do indicador no mostrador.

Etapa 1. Calcular posições e desenhar a visualização

  1. Na classe DialView, abaixo das inicializações, substitua o método onSizeChanged() da classe View para calcular o tamanho do mostrador da visualização personalizada. Importe kotlin.math.min quando solicitado.

    O método onSizeChanged() é chamado sempre que o tamanho da visualização muda, incluindo a primeira vez que ela é desenhada quando o layout é inflado. Substitua onSizeChanged() para calcular posições, dimensões e quaisquer outros valores relacionados ao tamanho da sua visualização personalizada, em vez de recalcular esses valores toda vez que você desenhar. Nesse caso, use onSizeChanged() para calcular o raio atual do elemento circular do mostrador.
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
   radius = (min(width, height) / 2.0 * 0.8).toFloat()
}
  1. Abaixo de onSizeChanged(), adicione este código para definir uma função de extensão computeXYForSpeed() para a classe PointF . Importe kotlin.math.cos e kotlin.math.sin quando solicitado. Essa função de extensão na classe PointF calcula as coordenadas X e Y na tela para o rótulo de texto e o indicador atual (0, 1, 2 ou 3), considerando a posição FanSpeed atual e o raio do mostrador. Você vai usar isso em onDraw().
private fun PointF.computeXYForSpeed(pos: FanSpeed, radius: Float) {
   // Angles are in radians.
   val startAngle = Math.PI * (9 / 8.0)   
   val angle = startAngle + pos.ordinal * (Math.PI / 4)
   x = (radius * cos(angle)).toFloat() + width / 2
   y = (radius * sin(angle)).toFloat() + height / 2
}
  1. Substitua o método onDraw() para renderizar a visualização na tela com as classes Canvas e Paint. Importe android.graphics.Canvas quando solicitado. Esta é a substituição do esqueleto:
override fun onDraw(canvas: Canvas) {
   super.onDraw(canvas)
   
}
  1. Em onDraw(), adicione esta linha para definir a cor da tinta como cinza (Color.GRAY) ou verde (Color.GREEN), dependendo se a velocidade do ventilador é OFF ou qualquer outro valor. Importe android.graphics.Color quando solicitado.
// Set dial background color to green if selection not off.
paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN
  1. Adicione este código para desenhar um círculo para o mostrador com o método drawCircle(). Esse método usa a largura e a altura da visualização atual para encontrar o centro e o raio do círculo, além da cor da pintura atual. As propriedades width e height são membros da superclasse View e indicam as dimensões atuais da visualização.
// Draw the dial.
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
  1. Adicione o código a seguir para desenhar um círculo menor para a marca do indicador de velocidade do ventilador, também com o método drawCircle(). Esta parte usa o PointF.Método de extensão computeXYforSpeed() para calcular as coordenadas X e Y do centro do indicador com base na velocidade atual do ventilador.
// Draw the indicator circle.
val markerRadius = radius + RADIUS_OFFSET_INDICATOR
pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
paint.color = Color.BLACK
canvas.drawCircle(pointPosition.x, pointPosition.y, radius/12, paint)
  1. Por fim, desenhe os rótulos de velocidade do ventilador (0, 1, 2, 3) nas posições adequadas ao redor do botão. Essa parte do método chama PointF.computeXYForSpeed() novamente para receber a posição de cada rótulo e reutiliza o objeto pointPosition sempre para evitar alocações. Use drawText() para desenhar os rótulos.
// Draw the text labels.
val labelRadius = radius + RADIUS_OFFSET_LABEL
for (i in FanSpeed.values()) {
   pointPosition.computeXYForSpeed(i, labelRadius)
   val label = resources.getString(i.label)
   canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
}

O método onDraw() concluído fica assim:

override fun onDraw(canvas: Canvas) {
   super.onDraw(canvas)
   // Set dial background color to green if selection not off.
   paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN
   // Draw the dial.
   canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
   // Draw the indicator circle.
   val markerRadius = radius + RADIUS_OFFSET_INDICATOR
   pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
   paint.color = Color.BLACK
   canvas.drawCircle(pointPosition.x, pointPosition.y, radius/12, paint)
   // Draw the text labels.
   val labelRadius = radius + RADIUS_OFFSET_LABEL
   for (i in FanSpeed.values()) {
       pointPosition.computeXYForSpeed(i, labelRadius)
       val label = resources.getString(i.label)
       canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
   }
}

Etapa 2. Adicionar a visualização ao layout

Para adicionar uma visualização personalizada à interface de um app, especifique-a como um elemento no layout XML da atividade. Controle a aparência e o comportamento dela com atributos de elementos XML, como faria com qualquer outro elemento da interface.

  1. Em activity_main.xml, mude a tag ImageView do dialView para com.example.android.customfancontroller.DialView e exclua o atributo android:background. Tanto DialView quanto o ImageView original herdam os atributos padrão da classe View. Portanto, não é necessário mudar nenhum dos outros atributos. O novo elemento DialView tem esta aparência:
<com.example.android.customfancontroller.DialView
       android:id="@+id/dialView"
       android:layout_width="@dimen/fan_dimen"
       android:layout_height="@dimen/fan_dimen"
       app:layout_constraintTop_toBottomOf="@+id/customViewLabel"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       android:layout_marginLeft="@dimen/default_margin"
       android:layout_marginRight="@dimen/default_margin"
       android:layout_marginTop="@dimen/default_margin" />
  1. Execute o app. A visualização de controle do ventilador vai aparecer na atividade.

A última tarefa é permitir que sua visualização personalizada execute uma ação quando o usuário tocar nela. Cada toque deve mover o indicador de seleção para a próxima posição: desligado-1-2-3 e de volta para desligado. Além disso, se a seleção for 1 ou mais, mude o plano de fundo de cinza para verde, indicando que a potência do ventilador está ativada.

Para ativar a capacidade de clicar na visualização personalizada, faça o seguinte:

  • Defina a propriedade isClickable da visualização como true. Isso permite que sua visualização personalizada responda a cliques.
  • Implemente o performClick() da classe View para realizar operações quando a visualização for clicada.
  • Chame o método invalidate(). Isso informa ao sistema Android para chamar o método onDraw() e redesenhar a visualização.

Normalmente, com uma visualização padrão do Android, você implementa OnClickListener() para realizar uma ação quando o usuário clica nessa visualização. Para uma visualização personalizada, implemente o método performClick() da classe View e chame super.performClick(). O método performClick() padrão também chama onClickListener(). Assim, você pode adicionar suas ações a performClick() e deixar onClickListener() disponível para mais personalizações por você ou outros desenvolvedores que possam usar sua visualização personalizada.

  1. Em DialView.kt, dentro da enumeração FanSpeed, adicione uma função de extensão next() que muda a velocidade atual do ventilador para a próxima velocidade na lista (de OFF para LOW, MEDIUM e HIGH, e depois de volta para OFF). A enumeração completa agora tem esta aparência:
private enum class FanSpeed(val label: Int) {
   OFF(R.string.fan_off),
   LOW(R.string.fan_low),
   MEDIUM(R.string.fan_medium),
   HIGH(R.string.fan_high);

   fun next() = when (this) {
       OFF -> LOW
       LOW -> MEDIUM
       MEDIUM -> HIGH
       HIGH -> OFF
   }
}
  1. Dentro da classe DialView, logo antes do método onSizeChanged(), adicione um bloco init(). Definir a propriedade isClickable da visualização como "true" permite que ela aceite a entrada do usuário.
init {
   isClickable = true
}
  1. Abaixo de init(),, substitua o método performClick() pelo código abaixo.
override fun performClick(): Boolean {
   if (super.performClick()) return true

   fanSpeed = fanSpeed.next()
   contentDescription = resources.getString(fanSpeed.label)
  
   invalidate()
   return true
}

A chamada para super.O performClick() precisa acontecer primeiro, o que ativa eventos de acessibilidade e chama onClickListener().

As duas linhas seguintes incrementam a velocidade do ventilador com o método next() e definem a descrição do conteúdo da visualização como o recurso de string que representa a velocidade atual (desligado, 1, 2 ou 3).

Por fim, o método invalidate() invalida toda a visualização, forçando uma chamada para onDraw() e redesenhando a visualização. Se algo na sua visualização personalizada mudar por qualquer motivo, incluindo interação do usuário, e a mudança precisar ser exibida, chame invalidate()..

  1. Execute o app. Toque no elemento DialView para mover o indicador de "desativado" para "1". O botão vai ficar verde. A cada toque, o indicador precisa se mover para a próxima posição. Quando o indicador voltar a ficar desligado, o botão vai ficar cinza de novo.

Este exemplo mostra a mecânica básica de uso de atributos personalizados com sua visualização personalizada. Você define atributos personalizados para a classe DialView com uma cor diferente para cada posição do botão do ventilador.

  1. Crie e abra res/values/attrs.xml.
  2. Em <resources>, adicione um elemento de recurso <declare-styleable>.
  3. Dentro do elemento de recurso <declare-styleable>, adicione três elementos attr, um para cada atributo, com um name e um format. O format é como um tipo e, neste caso, é color.
<?xml version="1.0" encoding="utf-8"?>
<resources>
       <declare-styleable name="DialView">
           <attr name="fanColor1" format="color" />
           <attr name="fanColor2" format="color" />
           <attr name="fanColor3" format="color" />
       </declare-styleable>
</resources>
  1. Abra o arquivo de layout activity_main.xml.
  2. No DialView, adicione atributos para fanColor1, fanColor2 e fanColor3 e defina os valores como as cores mostradas abaixo. Use app: como o prefixo do atributo personalizado (como em app:fanColor1) em vez de android:, porque seus atributos personalizados pertencem ao namespace schemas.android.com/apk/res/your_app_package_name em vez do namespace android.
app:fanColor1="#FFEB3B"
app:fanColor2="#CDDC39"
app:fanColor3="#009688"

Para usar os atributos na classe DialView, é necessário recuperá-los. Eles são armazenados em um AttributeSet, que é entregue à turma após a criação, se existir. Você recupera os atributos em init e atribui os valores do atributo a variáveis locais para armazenamento em cache.

  1. Abra o arquivo de classe DialView.kt.
  2. Dentro do DialView, declare variáveis para armazenar em cache os valores de atributo.
private var fanSpeedLowColor = 0
private var fanSpeedMediumColor = 0
private var fanSeedMaxColor = 0
  1. No bloco init, adicione o seguinte código usando a função de extensão withStyledAttributes. Você fornece os atributos e a visualização e define as variáveis locais. A importação de withStyledAttributes também importa a função getColor() certa.
context.withStyledAttributes(attrs, R.styleable.DialView) {
   fanSpeedLowColor = getColor(R.styleable.DialView_fanColor1, 0)
   fanSpeedMediumColor = getColor(R.styleable.DialView_fanColor2, 0)
   fanSeedMaxColor = getColor(R.styleable.DialView_fanColor3, 0)
}
  1. Use as variáveis locais em onDraw() para definir a cor do mostrador com base na velocidade atual do ventilador. Substitua a linha em que a cor da tinta é definida (paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN) pelo código abaixo.
paint.color = when (fanSpeed) {
   FanSpeed.OFF -> Color.GRAY
   FanSpeed.LOW -> fanSpeedLowColor
   FanSpeed.MEDIUM -> fanSpeedMediumColor
   FanSpeed.HIGH -> fanSeedMaxColor
} as Int
  1. Execute o app, clique no botão giratório. A configuração de cor será diferente para cada posição, como mostrado abaixo.

Para saber mais sobre atributos de visualização personalizados, consulte Como criar uma classe de visualização.

A acessibilidade é um conjunto de técnicas de design, implementação e teste que permitem que seu app seja usado por todos, incluindo pessoas com deficiência.

As deficiências comuns que podem afetar o uso de um dispositivo Android incluem cegueira, baixa visão, daltonismo, surdez ou perda auditiva e limitação de capacidade motora. Ao desenvolver apps com acessibilidade em mente, você melhora a experiência do usuário não apenas para pessoas com essas deficiências, mas também para todos os outros usuários.

O Android oferece vários recursos de acessibilidade por padrão nas visualizações de UI padrão, como TextView e Button. No entanto, ao criar uma visualização personalizada, é necessário considerar como ela vai oferecer recursos acessíveis, como descrições faladas do conteúdo na tela.

Nesta tarefa, você vai aprender sobre o TalkBack, o leitor de tela do Android, e modificar seu app para incluir dicas e descrições faladas para a visualização personalizada DialView.

Etapa 1. Conheça o TalkBack

O TalkBack é o leitor de tela integrado do Android. Com o TalkBack ativado, o usuário pode interagir com o dispositivo Android sem ver a tela, porque o Android descreve os elementos da tela em voz alta. Os usuários com deficiência visual podem contar com o TalkBack para usar seu app.

Nesta tarefa, você vai ativar o TalkBack para entender como os leitores de tela funcionam e como navegar nos apps.

  1. Em um dispositivo ou emulador Android, acesse Configurações > Acessibilidade > TalkBack.
  2. Toque no botão Ativar/Desativar para ativar o TalkBack.
  3. Toque em OK para confirmar as permissões.
  4. Confirme a senha do dispositivo, se necessário. Se esta for a primeira vez que você usa o TalkBack, um tutorial será iniciado. O tutorial pode não estar disponível em dispositivos mais antigos.
  5. Pode ser útil navegar pelo tutorial com os olhos fechados. Para abrir o tutorial novamente no futuro, navegue até Configurações > Acessibilidade > TalkBack > Configurações > Iniciar tutorial do TalkBack.
  6. Compile e execute o app CustomFanController ou abra-o com o botão Visão geral ou Recentes no seu dispositivo. Com o TalkBack ativado, observe que o nome do app é anunciado, assim como o texto do rótulo TextView ("Controle de ventoinha"). No entanto, se você tocar na própria visualização DialView, nenhuma informação será falada sobre o estado da visualização (a configuração atual do controle) ou a ação que será realizada quando você tocar na visualização para ativá-la.

Etapa 2. Adicionar descrições de conteúdo para rótulos de discagem

As descrições de conteúdo descrevem o significado e a finalidade das visualizações no app. Esses rótulos permitem que leitores de tela, como o recurso TalkBack do Android, expliquem a função de cada elemento com precisão. Para visualizações estáticas, como ImageView, adicione a descrição de conteúdo à visualização no arquivo de layout com o atributo contentDescription. As visualizações de texto (TextView e EditText) usam automaticamente o texto na visualização como a descrição do conteúdo.

Para a visualização de controle personalizado do ventilador, é necessário atualizar dinamicamente a descrição do conteúdo sempre que a visualização for clicada para indicar a configuração atual do ventilador.

  1. Na parte de baixo da classe DialView, declare uma função updateContentDescription() sem argumentos ou tipo de retorno.
fun updateContentDescription() {
}
  1. Em updateContentDescription(), mude a propriedade contentDescription da visualização personalizada para o recurso de string associado à velocidade atual do ventilador (desligado, 1, 2 ou 3). São os mesmos rótulos usados em onDraw() quando o controle é desenhado na tela.
fun updateContentDescription() {
   contentDescription = resources.getString(fanSpeed.label)
}
  1. Role para cima até o bloco init() e, no final dele, adicione uma chamada para updateContentDescription(). Isso inicializa a descrição do conteúdo quando a visualização é inicializada.
init {
   isClickable = true
   // ...

   updateContentDescription()
}
  1. Adicione outra chamada para updateContentDescription() no método performClick(), logo antes de invalidate().
override fun performClick(): Boolean {
   if (super.performClick()) return true
   fanSpeed = fanSpeed.next()
   updateContentDescription()
   invalidate()
   return true
}
  1. Compile e execute o app. Verifique se o TalkBack está ativado. Toque para mudar a configuração da visualização de discagem e observe que agora o TalkBack anuncia o rótulo atual (desativado, 1, 2, 3) e a frase "Toque duas vezes para ativar".

Etapa 3. Adicionar mais informações para a ação de clique

Você pode parar por aí, e sua visualização vai estar disponível no TalkBack. Mas seria útil se a visualização indicasse não apenas que ela pode ser ativada ("Toque duas vezes para ativar"), mas também o que vai acontecer quando ela for ativada ("Toque duas vezes para mudar" ou "Toque duas vezes para redefinir").

Para fazer isso, adicione informações sobre a ação da visualização (neste caso, um clique ou toque) a um objeto de informações do nó de acessibilidade usando um delegado de acessibilidade. Um delegado de acessibilidade permite personalizar os recursos relacionados à acessibilidade do seu app usando composição (em vez de herança).

Para esta tarefa, você vai usar as classes de acessibilidade nas bibliotecas do Android Jetpack (androidx.*) para garantir a compatibilidade com versões anteriores.

  1. Em DialView.kt, no bloco init, defina um delegado de acessibilidade na visualização como um novo objeto AccessibilityDelegateCompat. Importe androidx.core.view.ViewCompat e androidx.core.view.AccessibilityDelegateCompat quando solicitado. Essa estratégia permite a maior quantidade de compatibilidade com versões anteriores no seu app.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   
})
  1. No objeto AccessibilityDelegateCompat, substitua a função onInitializeAccessibilityNodeInfo() por um objeto AccessibilityNodeInfoCompat e chame o método da superclasse. Importe androidx.core.view.accessibility.AccessibilityNodeInfoCompat quando solicitado.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   override fun onInitializeAccessibilityNodeInfo(host: View, 
                            info: AccessibilityNodeInfoCompat) {
      super.onInitializeAccessibilityNodeInfo(host, info)

   }  
})

Cada visualização tem uma árvore de nós de acessibilidade, que podem ou não corresponder aos componentes de layout reais da visualização. Os serviços de acessibilidade do Android navegam por esses nós para encontrar informações sobre a visualização, como descrições de conteúdo faláveis ou possíveis ações que podem ser realizadas nessa visualização. Ao criar uma visualização personalizada, talvez seja necessário substituir as informações do nó para fornecer informações personalizadas de acessibilidade. Nesse caso, você vai substituir as informações do nó para indicar que há informações personalizadas para a ação da visualização.

  1. Dentro de onInitializeAccessibilityNodeInfo(), crie um objeto AccessibilityNodeInfoCompat.AccessibilityActionCompat e atribua-o à variável customClick. Transmita para o construtor a constante AccessibilityNodeInfo.ACTION_CLICK e uma string de marcador de posição. Importe AccessibilityNodeInfo quando solicitado.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   override fun onInitializeAccessibilityNodeInfo(host: View, 
                            info: AccessibilityNodeInfoCompat) {
      super.onInitializeAccessibilityNodeInfo(host, info)
      val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
         AccessibilityNodeInfo.ACTION_CLICK,
        "placeholder"
      )
   }  
})

A classe AccessibilityActionCompat representa uma ação em uma visualização para fins de acessibilidade. Uma ação típica é um clique ou toque, como você usa aqui, mas outras ações podem incluir ganhar ou perder o foco, uma operação da área de transferência (cortar/copiar/colar) ou rolar na visualização. O construtor dessa classe exige uma constante de ação (aqui, AccessibilityNodeInfo.ACTION_CLICK) e uma string usada pelo TalkBack para indicar qual é a ação.

  1. Substitua a string "placeholder" por uma chamada para context.getString() e recupere um recurso de string. Para o recurso específico, teste a velocidade atual do ventilador. Se a velocidade atual for FanSpeed.HIGH, a string será "Reset". Se a velocidade do ventilador for outra, a string será "Change.". Você vai criar esses recursos de string em uma etapa posterior.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   override fun onInitializeAccessibilityNodeInfo(host: View, 
                            info: AccessibilityNodeInfoCompat) {
      super.onInitializeAccessibilityNodeInfo(host, info)
      val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
         AccessibilityNodeInfo.ACTION_CLICK,
        context.getString(if (fanSpeed !=  FanSpeed.HIGH) R.string.change else R.string.reset)
      )
   }  
})
  1. Depois dos parênteses de fechamento da definição customClick, use o método addAction() para adicionar a nova ação de acessibilidade ao objeto de informações do nó.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   override fun onInitializeAccessibilityNodeInfo(host: View, 
                            info: AccessibilityNodeInfoCompat) {
       super.onInitializeAccessibilityNodeInfo(host, info)
       val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
           AccessibilityNodeInfo.ACTION_CLICK,
           context.getString(if (fanSpeed !=  FanSpeed.HIGH) 
                                 R.string.change else R.string.reset)
       )
       info.addAction(customClick)
   }
})
  1. Em res/values/strings.xml, adicione os recursos de string para "Mudar" e "Redefinir".
<string name="change">Change</string>
<string name="reset">Reset</string>
  1. Compile e execute o app. Verifique se o TalkBack está ativado. Agora, a frase "Toque duas vezes para ativar" mudou para "Toque duas vezes para mudar" (se a velocidade do ventilador for menor que alta ou 3) ou "Toque duas vezes para redefinir" (se a velocidade do ventilador já estiver alta ou 3). A mensagem "Toque duas vezes para..." é fornecida pelo próprio serviço TalkBack.

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

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


Se preferir, você pode fazer o download do repositório como um arquivo ZIP, descompactar e abrir no Android Studio.

Fazer o download do ZIP

  • Para criar uma visualização personalizada que herda a aparência e o comportamento de uma subclasse View, como EditText, adicione uma nova classe que estenda essa subclasse e faça ajustes substituindo alguns dos métodos dela.
  • Para criar uma visualização personalizada de qualquer tamanho e formato, adicione uma nova classe que estenda View.
  • Substitua os métodos View, como onDraw(), para definir a forma e a aparência básica da visualização.
  • Use invalidate() para forçar um desenho ou redesenho da visualização.
  • Para otimizar o desempenho, aloque variáveis e atribua os valores necessários para desenhar e pintar antes de usá-los em onDraw(), como na inicialização de variáveis de membro.
  • Substitua performClick() em vez de OnClickListener() na visualização personalizada para fornecer o comportamento interativo dela. Isso permite que você ou outros desenvolvedores Android que usam sua classe de visualização personalizada usem onClickListener() para fornecer mais comportamento.
  • Adicione a visualização personalizada a um arquivo de layout XML com atributos para definir a aparência dela, assim como faria com outros elementos da interface.
  • Crie o arquivo attrs.xml na pasta values para definir atributos personalizados. Em seguida, use os atributos personalizados para a visualização personalizada no arquivo de layout XML.

Curso da Udacity:

Documentação do desenvolvedor Android:

Vídeos:

Esta seção lista as possíveis atividades de dever de casa para os alunos que estão fazendo este codelab como parte de um curso ministrado por um professor. Cabe ao professor fazer o seguinte:

  • Atribuir o dever de casa, se necessário.
  • Informar aos alunos como enviar deveres de casa.
  • Atribuir nota aos deveres de casa.

Os professores podem usar essas sugestões o quanto quiserem, podendo passar os exercícios que acharem mais apropriados como dever de casa.

Se você estiver seguindo este codelab por conta própria, sinta-se à vontade para usar esses deveres de casa para testar seu conhecimento.

Pergunta 1

Para calcular as posições, dimensões e quaisquer outros valores quando a visualização personalizada recebe um tamanho pela primeira vez, qual método você substitui?

onMeasure()

onSizeChanged()

invalidate()

onDraw()

Pergunta 2

Para indicar que você quer que sua visualização seja redesenhada com onDraw(), qual método você chama da linha de execução da UI depois que um valor de atributo muda?

▢ onMeasure()

▢ onSizeChanged()

▢ invalidate()

▢ getVisibility()

Pergunta 3

Qual método View você precisa substituir para adicionar interatividade à sua visualização personalizada?

▢ setOnClickListener()

▢ onSizeChanged()

▢ isClickable()

▢ performClick()

Para acessar links de outros codelabs deste curso, consulte a página inicial dos codelabs do curso Android avançado no Kotlin.