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
Viewpara 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
Viewpara 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
Viewou uma subclasseView(comoButtonouEditText). - 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étodosView, comoonDraw()eonMeasure(), 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
ImageViewcomo um marcador temporário para a visualização personalizada. - Estenda
Viewpara 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
- Crie um app Kotlin com o título
CustomFanControllerusando o modelo Empty Activity. Verifique se o nome do pacote écom.example.android.customfancontroller. - Abra
activity_main.xmlna guia Texto para editar o código XML. - Substitua o
TextViewatual 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"/>- Adicione esse elemento
ImageViewao 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"/>- Extraia recursos de string e dimensão nos dois elementos da interface.
- Clique na guia Design. O layout vai ficar assim:

Etapa 2: Criar sua classe de visualização personalizada
- Crie uma classe Kotlin chamada
DialView. - Modifique a definição da classe para estender
View. Importeandroid.view.Viewquando solicitado. - Clique em
Viewe depois na lâmpada vermelha. Escolha Adicionar construtores de visualização do Android usando '@JvmOverloads'. O Android Studio adiciona o construtor da classeView. A anotação@JvmOverloadsinstrui 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) {- Acima da definição da classe
DialView, logo abaixo das importações, adicione umaenumde nível superior para representar as velocidades disponíveis do ventilador. Observe que esseenumé do tipoIntporque 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);
}- 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- Dentro da classe
DialView, defina várias variáveis necessárias para desenhar a visualização personalizada. Importeandroid.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çãoFanSpeed. 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.
- Também na definição da classe
DialView, inicialize um objetoPaintcom alguns estilos básicos. Importeandroid.graphics.Painteandroid.graphics.Typefacequando 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)
}- Abra
res/values/strings.xmle 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 objetoCanvasestilizado por um objetoPaint. - 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 paraonDraw()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:
- Desenhe texto usando
drawText(). Especifique a fonte chamandosetTypeface()e a cor do texto chamandosetColor(). - Desenhe formas primitivas usando
drawRect(),drawOval()edrawArc(). Defina se as formas são sólidas, circunscritas ou ambos chamandosetStyle(). - Desenhe bitmaps usando
drawBitmap().
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
- Na classe
DialView, abaixo das inicializações, substitua o métodoonSizeChanged()da classeViewpara calcular o tamanho do mostrador da visualização personalizada. Importekotlin.math.minquando solicitado.
O métodoonSizeChanged()é chamado sempre que o tamanho da visualização muda, incluindo a primeira vez que ela é desenhada quando o layout é inflado. SubstituaonSizeChanged()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, useonSizeChanged()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()
}- Abaixo de
onSizeChanged(), adicione este código para definir uma função de extensãocomputeXYForSpeed()para a classePointF. Importekotlin.math.cosekotlin.math.sinquando solicitado. Essa função de extensão na classePointFcalcula 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çãoFanSpeedatual e o raio do mostrador. Você vai usar isso emonDraw().
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
}- Substitua o método
onDraw()para renderizar a visualização na tela com as classesCanvasePaint. Importeandroid.graphics.Canvasquando solicitado. Esta é a substituição do esqueleto:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
}- 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 éOFFou qualquer outro valor. Importeandroid.graphics.Colorquando solicitado.
// Set dial background color to green if selection not off.
paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN- 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 propriedadeswidtheheightsão membros da superclasseViewe indicam as dimensões atuais da visualização.
// Draw the dial.
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)- 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 oPointF.Método de extensãocomputeXYforSpeed()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)- 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 objetopointPositionsempre para evitar alocações. UsedrawText()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.
- Em
activity_main.xml, mude a tagImageViewdodialViewparacom.example.android.customfancontroller.DialViewe exclua o atributoandroid:background. TantoDialViewquanto oImageVieworiginal herdam os atributos padrão da classeView. Portanto, não é necessário mudar nenhum dos outros atributos. O novo elementoDialViewtem 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" />- 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
isClickableda visualização comotrue. Isso permite que sua visualização personalizada responda a cliques. - Implemente o
performClick()da classeViewpara realizar operações quando a visualização for clicada. - Chame o método
invalidate(). Isso informa ao sistema Android para chamar o métodoonDraw()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.
- Em
DialView.kt, dentro da enumeraçãoFanSpeed, adicione uma função de extensãonext()que muda a velocidade atual do ventilador para a próxima velocidade na lista (deOFFparaLOW,MEDIUMeHIGH, e depois de volta paraOFF). 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
}
}- Dentro da classe
DialView, logo antes do métodoonSizeChanged(), adicione um blocoinit(). Definir a propriedadeisClickableda visualização como "true" permite que ela aceite a entrada do usuário.
init {
isClickable = true
}- Abaixo de
init(),, substitua o métodoperformClick()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()..
- Execute o app. Toque no elemento
DialViewpara 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.
- Crie e abra
res/values/attrs.xml. - Em
<resources>, adicione um elemento de recurso<declare-styleable>. - Dentro do elemento de recurso
<declare-styleable>, adicione três elementosattr, um para cada atributo, com umnamee umformat. Oformaté 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>- Abra o arquivo de layout
activity_main.xml. - No
DialView, adicione atributos parafanColor1,fanColor2efanColor3e defina os valores como as cores mostradas abaixo. Useapp:como o prefixo do atributo personalizado (como emapp:fanColor1) em vez deandroid:, porque seus atributos personalizados pertencem ao namespaceschemas.android.com/apk/res/your_app_package_nameem vez do namespaceandroid.
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.
- Abra o arquivo de classe
DialView.kt. - 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- No bloco
init, adicione o seguinte código usando a função de extensãowithStyledAttributes. Você fornece os atributos e a visualização e define as variáveis locais. A importação dewithStyledAttributestambém importa a funçãogetColor()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)
}- 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.GRAYelseColor.GREEN) pelo código abaixo.
paint.color = when (fanSpeed) {
FanSpeed.OFF -> Color.GRAY
FanSpeed.LOW -> fanSpeedLowColor
FanSpeed.MEDIUM -> fanSpeedMediumColor
FanSpeed.HIGH -> fanSeedMaxColor
} as Int- 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.
- Em um dispositivo ou emulador Android, acesse Configurações > Acessibilidade > TalkBack.
- Toque no botão Ativar/Desativar para ativar o TalkBack.
- Toque em OK para confirmar as permissões.
- 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.
- 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.
- Compile e execute o app
CustomFanControllerou 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ótuloTextView("Controle de ventoinha"). No entanto, se você tocar na própria visualizaçãoDialView, 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.
- Na parte de baixo da classe
DialView, declare uma funçãoupdateContentDescription()sem argumentos ou tipo de retorno.
fun updateContentDescription() {
}- Em
updateContentDescription(), mude a propriedadecontentDescriptionda 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 emonDraw()quando o controle é desenhado na tela.
fun updateContentDescription() {
contentDescription = resources.getString(fanSpeed.label)
}- Role para cima até o bloco
init()e, no final dele, adicione uma chamada paraupdateContentDescription(). Isso inicializa a descrição do conteúdo quando a visualização é inicializada.
init {
isClickable = true
// ...
updateContentDescription()
}- Adicione outra chamada para
updateContentDescription()no métodoperformClick(), logo antes deinvalidate().
override fun performClick(): Boolean {
if (super.performClick()) return true
fanSpeed = fanSpeed.next()
updateContentDescription()
invalidate()
return true
}- 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.
- Em
DialView.kt, no blocoinit, defina um delegado de acessibilidade na visualização como um novo objetoAccessibilityDelegateCompat. Importeandroidx.core.view.ViewCompateandroidx.core.view.AccessibilityDelegateCompatquando solicitado. Essa estratégia permite a maior quantidade de compatibilidade com versões anteriores no seu app.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
})- No objeto
AccessibilityDelegateCompat, substitua a funçãoonInitializeAccessibilityNodeInfo()por um objetoAccessibilityNodeInfoCompate chame o método da superclasse. Importeandroidx.core.view.accessibility.AccessibilityNodeInfoCompatquando 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.
- Dentro de
onInitializeAccessibilityNodeInfo(), crie um objetoAccessibilityNodeInfoCompat.AccessibilityActionCompate atribua-o à variávelcustomClick. Transmita para o construtor a constanteAccessibilityNodeInfo.ACTION_CLICKe uma string de marcador de posição. ImporteAccessibilityNodeInfoquando 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.
- Substitua a string
"placeholder"por uma chamada paracontext.getString()e recupere um recurso de string. Para o recurso específico, teste a velocidade atual do ventilador. Se a velocidade atual forFanSpeed.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)
)
}
})- Depois dos parênteses de fechamento da definição
customClick, use o métodoaddAction()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)
}
})- Em
res/values/strings.xml, adicione os recursos de string para "Mudar" e "Redefinir".
<string name="change">Change</string>
<string name="reset">Reset</string>- 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.
- Para criar uma visualização personalizada que herda a aparência e o comportamento de uma subclasse
View, comoEditText, 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, comoonDraw(), 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 deOnClickListener() 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 usemonClickListener()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.xmlna pastavaluespara 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:
- Como criar visualizações personalizadas
@JvmOverloads- Componentes personalizados
- Como o Android desenha visualizações
onMeasure()onSizeChanged()onDraw()CanvasPaintdrawText()setTypeface()setColor()drawRect()drawOval()drawArc()drawBitmap()setStyle()invalidate()- Ver
- Eventos de entrada
- Paint
- Biblioteca de extensão Kotlin android-ktx
withStyledAttributes- Documentação do Android KTX
- Blog do anúncio original do Android KTX
- Tornar visualizações personalizadas mais acessíveis
AccessibilityDelegateCompatAccessibilityNodeInfoCompatAccessibilityNodeInfoCompat.AccessibilityActionCompat
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.



