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

O Android oferece um grande conjunto de subclasses de View, como Button, TextView, EditText, ImageView, CheckBox ou RadioButton. Você pode usar essas subclasses para construir uma IU que permite a interação do usuário e exibe informações em seu aplicativo. Se nenhuma das subclasses de View atender às suas necessidades, você pode criar uma subclasse View conhecida como vista personalizada.

Para criar uma vista personalizada, você pode estender uma subclasse View existente (como um Button ou EditText) ou criar sua própria subclasse de View. Ao estender View diretamente, você pode criar um elemento de IU interativo de qualquer tamanho e forma substituindo o método onDraw() para que View seja desenhado isto.

Depois de criar uma vista personalizada, você pode adicioná-la aos seus layouts de atividade da mesma forma que adicionaria uma TextView ou Button.

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

O que você já deveria saber

O que você aprenderá

O que você vai fazer

O aplicativo CustomFanController demonstra como criar uma subclasse de vista personalizada estendendo a classe View. A nova subclasse é chamada de DialView.

O aplicativo exibe um elemento de IU circular que se assemelha a um controle de ventilador físico, com configurações para desligado (0), baixo (1), médio (2) e alto (3). Quando o usuário toca na vista, 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 superior, a cor de fundo da parte circular da vista muda de cinza para verde (indicando que o ventilador está ligado).

As vistas são os blocos básicos de construção da IU de um aplicativo. A classe View fornece muitas subclasses, conhecidas como widgets de IU, que cobrem muitas das necessidades de uma interface de usuário típica de aplicativo Android.

Os blocos de construção da IU, como Button e TextView são subclasses que estendem a classe View. Para economizar tempo e esforço de desenvolvimento, você pode estender uma dessas subclasses de View. A vista personalizada herda a aparência e o comportamento de seu pai, e você pode substituir o comportamento ou aspecto da aparência que deseja alterar. Por exemplo, se você estender EditText para criar uma vista personalizada, a vista atua como uma vista EditText, mas também pode ser personalizada para mostrar, por exemplo, um X que limpa o texto do campo de entrada de texto.

Você pode estender qualquer subclasse View, como EditText, para obter uma vista personalizada - escolha aquela mais próxima do que você deseja realizar. Você pode então usar a vista personalizada como qualquer outra subclasse View em um ou mais layouts como um elemento XML com atributos.

Para criar sua própria vista personalizada do zero, estenda a própria classe View. Seu código substitui os métodos View para definir a aparência e funcionalidade da vista. A chave para criar sua própria vista customizada é que você é responsável por desenhar todo o elemento da IU de qualquer tamanho e forma na tela. Se você criar uma subclasse de uma vista existente, como Button, essa classe cuidará do desenho para você. (Você aprenderá mais sobre desenho posteriormente neste tutorial.)

Para criar uma vista personalizada, siga estas etapas gerais:

Nesta tarefa você irá:

Etapa 1: Criar um aplicativo com um espaço reservado ImageView

  1. Crie um aplicativo Kotlin com o título CustomFanController usando o modelo Empty Activity. Certifique-se de que o nome do pacote seja com.example.android.customfancontroller.
  2. Abra activity_main.xml na guia Text para editar o código XML.
  3. Substitua o TextView existente por este código. Este texto atua como um rótulo na atividade para a vista 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 este elemento ImageView ao layout. Este é um espaço reservado para a vista personalizada que você criará neste tutorial.
<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 em ambos os elementos da IU.
  2. Clique na guia Design. O layout deve ser assim:

Etapa 2. Crie sua classe de vista personalizada

  1. Crie uma nova classe Kotlin chamada DialView.
  2. Modifique a definição da classe para estender a View. Importe android.view.View quando solicitado.
  3. Clique em View e depois clique na lâmpada vermelha. Escolha Add Android View constructors using '@JvmOverloads'. O Android Studio adiciona o construtor da classe View. A anotação @JvmOverloads instrui o compilador Kotlin a gerar sobrecargas para esta 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 um enum de nível superior para representar as velocidades do ventilador disponíveis. Observe que este enum é do tipo Int porque os valores são recursos de string em vez de strings reais. O Android Studio mostrará erros para os recursos de string ausentes em cada um desses valores; você 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 essas constantes. Você os usará como parte do desenho dos indicadores de mostrador e rótulos.
private const val RADIUS_OFFSET_LABEL = 30      
private const val RADIUS_OFFSET_INDICATOR = -35
  1. Dentro da classe DialView, defina várias variáveis ​​que você precisa para desenhar a vista 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)

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

  1. Também dentro da definição da classe DialView, inicialize um objeto Paint com um punhado de estilos básicos. Importe android.graphics.Paint e android.graphics.Typeface quando solicitado. Como anteriormente com 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 vista personalizada, você precisa ser capaz de desenhá-la. Quando você estende uma subclasse View como EditText, essa subclasse define a aparência e os atributos da vista e se desenha na tela. Consequentemente, você não precisa escrever código para desenhar a vista. Você pode substituir os métodos do pai para personalizar sua vista.

Se você estiver criando sua própria vista do zero (estendendo View), você é responsável por desenhar a vista inteira cada vez que a tela for atualizada e por substituir os métodos de View que tratar com o desenho. Para desenhar adequadamente uma vista personalizada que estenda View, você precisa:

O método onDraw() é chamado toda vez que a tela é atualizada, o que pode ser muitas vezes por segundo. Por motivos de desempenho e para evitar falhas visuais, você deve trabalhar o mínimo possível em onDraw(). Em particular, não coloque alocações em onDraw(), porque as alocações podem levar a uma coleta de lixo que pode causar uma falha visual.

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

Você aprenderá mais sobre Canvas e Paint em um tutorial posterior. Para saber mais sobre como o Android desenha vistas, consulte Como o Android desenha vistas.

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

Etapa 1. Calcule as posições e desenhe a vista

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

    O método onSizeChanged() é chamado sempre que o tamanho da vista 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 de sua vista personalizada, em vez de recalculá-los toda vez que você desenhar. Neste caso, você usa 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. Esta função de extensão na classe PointF calcula as coordenadas X, Y na tela para o rótulo de texto e o indicador atual (0, 1, 2 ou 3), dado o FanSpeed posição e raio do mostrador. Você 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 vista 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. Dentro de onDraw(), adicione esta linha para definir a cor da pintura para cinza (Color.GRAY) ou verde (Color.GREEN) dependendo de 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(). Este método usa a largura e altura da vista atual para encontrar o centro do círculo, o raio do círculo e a cor de pintura atual. As propriedades width e height são membros da superclasse View e indicam as dimensões atuais da vista.
// 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 indicadora de velocidade do ventilador, também com o método drawCircle(). Esta parte usa o PointFcomputeXYforSpeed() método de extensão para calcular as coordenadas X, Y para o centro do indicador com base na velocidade do ventilador atual.
// 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. Finalmente, desenhe as etiquetas de velocidade do ventilador (0, 1, 2, 3) nas posições apropriadas ao redor do mostrador. Esta parte do método chama PointF.computeXYForSpeed() novamente para obter a posição de cada rótulo e reutiliza o objeto pointPosition a cada vez 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 tem a seguinte aparência:

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 vista ao layout

Para adicionar uma vista personalizada à IU de um aplicativo, você a especifica como um elemento no layout XML da atividade. Controle sua aparência e comportamento com atributos de elemento XML, como faria com qualquer outro elemento de IU.

  1. Em activity_main.xml, altere a etiquetaImageView para dialView para com.example.android.customfancontroller.DialView e exclua o atributo android:background. Tanto o DialView quanto o ImageView original herdam os atributos padrão da classe View, portanto, não há necessidade de alterar nenhum dos outros atributos. O novo elemento DialView tem a seguinte 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 aplicativo. Sua vista de controle do ventilador aparece na atividade.

A tarefa final é permitir que sua vista personalizada execute uma ação quando o usuário tocar na vista. 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 superior, altere o fundo de cinza para verde, indicando que o ventilador está ligado.

Para permitir que sua vista personalizada seja clicável, você:

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

  1. Em DialView.kt, dentro da enumeração FanSpeed, adicione uma função de extensão next() que altera 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 se parece com isto:
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 vista como true permite que a vista aceite a entrada do usuário.
init {
   isClickable = true
}
  1. Abaixo de init(), substitua o método performClick() com o 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 superperformClick() deve acontecer primeiro, o que ativa eventos de acessibilidade, bem como chamadas onClickListener().

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

Finalmente, o método invalidate() invalida a vista inteira, forçando uma chamada para onDraw() para redesenhar a vista. Se algo em sua vista personalizada mudar por qualquer motivo, incluindo interação do usuário, e a mudança precisar ser exibida, chame invalidate().

  1. Execute o aplicativo. Toque no elemento DialView para mover o indicador de desligado para 1. O dial deve ficar verde. A cada toque, o indicador deve passar para a próxima posição. Quando o indicador voltar a desligar, o dial deve ficar cinza novamente.

Este exemplo mostra a mecânica básica do uso de atributos personalizados com sua vista 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. Dentro de <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 format. O format é como um tipo e, neste caso, é color.

<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 seus valores para as cores mostradas abaixo. Use app: como o prefácio do atributo personalizado (como em app:fanColor1) em vez de android: porque seus atributos personalizados pertencem ao 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 em sua classe DialView, você precisa recuperá-los. Eles são armazenados em um AttributeSet, que é entregue à sua classe na criação, se existir. Você recupera os atributos em init e atribui os valores dos atributos às variáveis ​​locais para armazenamento em cache.

  1. Abra o arquivo de classe DialView.kt.
  2. Dentro do DialView, declare as variáveis ​​para armazenar em cache os valores dos atributos.
private var fanSpeedLowColor = 0
private var fanSpeedMediumColor = 0
private var fanSpeedMaxColor = 0
  1. No bloco init, adicione o seguinte código usando a função de extensão withStyledAttributes. Você fornece os atributos e a vista e define suas variáveis ​​locais. Importar withStyledAttributes também importará a função getColor() correta.
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 dial com base na velocidade atual do ventilador. Substitua a linha onde a cor da tinta está definida (paint.color=if(fanSpeed== FanSpeed.OFF) Color.GRAYelseColor.GREEN) com o código abaixo.
paint.color = when (fanSpeed) {
   FanSpeed.OFF -> Color.GRAY
   FanSpeed.LOW -> fanSpeedLowColor
   FanSpeed.MEDIUM -> fanSpeedMediumColor
   FanSpeed.HIGH -> fanSeedMaxColor
} as Int
  1. Execute seu aplicativo, clique no dial e a configuração de cor deve ser diferente para cada posição, conforme mostrado abaixo.

Para aprender mais sobre os atributos de vista customizada, consulte Criando uma classe de vista.

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

As deficiências comuns que podem afetar o uso de um dispositivo Android por uma pessoa incluem cegueira, baixa vista, daltonismo, surdez ou perda auditiva e habilidades motoras restritas. Ao desenvolver seus aplicativos com acessibilidade em mente, você torna a experiência do usuário melhor não apenas para usuários com essas deficiências, mas também para todos os seus outros usuários.

O Android fornece vários recursos de acessibilidade por padrão nas vistas da IU padrão, como TextView e Button. Ao criar uma vista personalizada, no entanto, você precisa considerar como essa vista personalizada fornecerá recursos acessíveis, como descrições faladas do conteúdo na tela.

Nesta tarefa, você aprenderá sobre TalkBack, o leitor de tela do Android, e modificará seu aplicativo para incluir dicas faláveis ​​e descrições para a vista personalizada DialView.

Etapa 1. Explorar TalkBack

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

Nesta tarefa, você habilita o TalkBack para entender como os leitores de tela funcionam e como navegar pelos aplicativos.

  1. Em um dispositivo ou emulador Android, navegue até Settings > Accessibility > TalkBack.
  2. Toque no botão de alternância On/Off para ligar o TalkBack.
  3. Toque em OK para confirmar as permissões.
  4. Confirme a senha do seu dispositivo, se solicitado. Se esta é a primeira vez que você executa o TalkBack, um tutorial é 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 para Settings > Accessibility > TalkBack > Settings > Launch TalkBack tutorial.
  6. Compile e execute o aplicativo CustomFanController ou abra-o com o botão Overview ou Recents em seu dispositivo. Com o TalkBack ativado, observe que o nome do aplicativo é anunciado, bem como o texto do rótulo TextView ("Fan Control"). No entanto, se você tocar na própria vista DialView, nenhuma informação será falada sobre o estado da vista (a configuração atual do dial) ou a ação que ocorrerá quando você tocar na vista para ative-o.

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 vistas em seu aplicativo. 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 vistas estáticas, como ImageView, você pode adicionar a descrição do conteúdo à vista no arquivo de layout com o atributo contentDescription. As vistas de texto (TextView e EditText) usam automaticamente o texto na vista como a descrição do conteúdo.

Para a vista de controle de ventilador customizada, você precisa atualizar dinamicamente a descrição do conteúdo cada vez que a vista é clicada, para indicar a configuração de ventilador atual.

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

   updateContentDescription()
}
  1. Adicione outra chamada a 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 aplicativo e verifique se o TalkBack está ativado. Toque para alterar a configuração da vista de discagem e observe que agora o TalkBack anuncia o rótulo atual (desligado, 1, 2, 3), bem como a frase "Toque duas vezes para ativar".

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

Você poderia parar por aí e sua visualização seria utilizável no TalkBack. Mas seria útil se sua visualização pudesse indicar não só que pode ser ativada ("Toque duas vezes para ativar"), mas também explicar o que irá acontecer quando a visualização está ativado ("Toque duas vezes para alterar." ou "Toque duas vezes para redefinir.")

Para fazer isso, você adiciona informações sobre a ação da vista (aqui, uma ação de clique ou toque) a um objeto de informações do nó de acessibilidade, por meio de um delegado de acessibilidade. Um delegado de acessibilidade permite que você personalize os recursos relacionados à acessibilidade de seu aplicativo por meio de composição (em vez de herança).

Para esta tarefa, você 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 vista como um novo objeto AccessibilityDelegateCompat. Importe androidx.core.view.ViewCompat e androidx.core.view.AccessibilityDelegateCompat quando solicitado. Essa estratégia permite o máximo de compatibilidade com versões anteriores em seu aplicativo.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   
})
  1. Dentro do objeto AccessibilityDelegateCompat, substitua a função onInitializeAccessibilityNodeInfo() por um objeto AccessibilityNodeInfoCompat e chame o método super. 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 vista possui uma árvore de nós de acessibilidade, que podem ou não corresponder aos componentes de layout reais da vista. Os serviços de acessibilidade do Android navegam nesses nós para descobrir informações sobre a vista (como descrições de conteúdo faláveis ​​ou possíveis ações que podem ser executadas nessa vista). Quando você cria uma vista personalizada, também pode precisar substituir as informações do nó em a fim de fornecer informações personalizadas para acessibilidade. Nesse caso, você substituirá as informações do nó para indicar que há informações personalizadas para a ação da vista.

  1. Dentro de onInitializeAccessibilityNodeInfo(), crie um novo objeto AccessibilityNodeInfoCompat.AccessibilityActionCompat e atribua-o à variável customClick. Passe para o construtor a constante AccessibilityNodeInfo.ACTION_CLICK e uma string de espaço reservado. 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 vista para fins de acessibilidade. Um típico a ação é 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 dentro da vista. O construtor para esta classe requer uma constante de ação (aqui, AccessibilityNodeInfo.ACTION_CLICK) e uma string que é usada pelo TalkBack para indicar qual é a ação.

  1. Substitua a string "placeholder" por uma chamada para context.getString() para recuperar um recurso de string. Para o recurso específico, teste a velocidade atual do ventilador. Se a velocidade for atualmente FanSpeed.HIGH, a string será "Reset". Se a velocidade do ventilador for qualquer outra, a string será "Change." Você 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. Após fechar os parênteses para a 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 "Alterar" e "Redefinir".
<string name="change">Change</string>
<string name="reset">Reset</string>
  1. Compile e execute o aplicativo e verifique se o TalkBack está ativado. Observe agora que a frase "Toque duas vezes para ativar" agora é "Toque duas vezes para alterar" (se a velocidade do ventilador for menor que alta ou 3) ou "Toque duas vezes para redefinir" (se a velocidade do ventilador já estiver em alto ou 3). Observe que o prompt "Toque duas vezes para..." é fornecido pelo próprio serviço TalkBack.

Baixe o código para o tutorial concluído.

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


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:

Vídeos:

Se você estiver trabalhando neste tutorial por conta própria, sinta-se à vontade para usar essas tarefas de lição de casa para testar seus conhecimentos

Questão 1

Para calcular as posições, dimensões e quaisquer outros valores quando o tamanho da vista personalizada é atribuído pela primeira vez, qual método você substitui?

onMeasure()

onSizeChanged()

invalidate()

onDraw()

Questão 2

Para indicar que você gostaria que sua vista fosse redesenhada com onDraw(), qual método você chama a partir da thread de interface do usuário, após a alteração de um valor de atributo?

▢ onMeasure()

▢ onSizeChanged()

▢ invalidate()

▢ getVisibility()

Questão 3

Qual método de View você deve substituir para adicionar interatividade à vista personalizada?

▢ setOnClickListener()

▢ onSizeChanged()

▢ isClickable()

▢ performClick()

Para obter links para outros tutoriais neste curso, consulte a página inicial de tutoriais do Android avançado em Kotlin.