O Android oferece um modelo de componentes sofisticado e eficiente para criar sua interface com base nas
classes fundamentais de layout
View
e
ViewGroup
. A plataforma inclui uma
variedade de subclasses View
e ViewGroup
pré-criadas, chamadas de widgets e
layouts, respectivamente, que podem ser usadas para construir sua interface.
Uma lista parcial de widgets disponíveis inclui Button
,
TextView
,
EditText
,
ListView
,
CheckBox
,
RadioButton
,
Gallery
,
Spinner
e a finalidade mais especial
AutoCompleteTextView
,
ImageSwitcher
e
TextSwitcher
.
Entre os layouts disponíveis estão
LinearLayout
,
FrameLayout
,
RelativeLayout
e outros. Para ver mais exemplos, consulte
Layouts comuns.
Se nenhum dos widgets ou layouts pré-criados atender às suas necessidades, você pode criar sua própria
subclasse View
. Se você só precisa fazer pequenos ajustes em um widget ou
layout existente, pode criar uma subclasse do widget ou layout e substituir os métodos dele.
A criação das suas próprias subclasses View
oferece controle preciso sobre a aparência e
a função de um elemento de tela. Para dar uma ideia do controle que você tem com as visualizações personalizadas, veja
alguns exemplos do que é possível fazer com elas:
-
Você pode criar um tipo
View
de renderização personalizada, por exemplo, um botão de "controle de volume", renderizado usando gráficos 2D, que se assemelha a um controle eletrônico analógico. -
Você pode combinar um grupo de componentes
View
em um novo componente, talvez para criar algo como uma caixa de combinação (uma combinação de lista pop-up e campo de texto de entrada livre), um controle seletor de painel duplo (um painel esquerdo e um painel direito com uma lista em cada um em que é possível reatribuir qual item está em qual lista) e assim por diante. -
É possível substituir a forma como um componente
EditText
é renderizado na tela. O app de exemplo Bloco de notas usa isso para criar uma página de bloco de notas com linhas. - É possível capturar outros eventos, como pressionamentos de teclas, e processá-los de maneira personalizada, como para um jogo.
As seções a seguir explicam como criar visualizações personalizadas e usá-las no aplicativo. Para
informações de referência detalhadas, consulte a
classe View
.
A abordagem básica
Esta é uma visão geral de alto nível do que você precisa saber para criar seus próprios componentes
View
:
-
Estenda uma classe
View
ou subclasse com sua própria classe. -
Modifique alguns dos métodos da superclasse. Os métodos da superclasse que serão modificados começam com
on
, por exemplo,onDraw()
,onMeasure()
eonKeyDown()
. Isso é semelhante aos eventoson
emActivity
ouListActivity
que você substitui para o ciclo de vida e outros hooks de funcionalidade. - Use sua nova classe de extensão. Depois de concluído, você pode usar sua nova classe de extensão no lugar da visualização em que ela foi baseada.
Componentes totalmente personalizados
É possível criar componentes gráficos totalmente personalizados que aparecem como você quiser. Talvez você queira um medidor VU gráfico que se pareça com um antigo medidor analógico ou uma visualização de texto para cantar junto, em que uma bola saltitante se move ao longo das palavras enquanto você canta com uma máquina de karaokê. Você pode querer algo que os componentes integrados não podem fazer, independente de como sejam combinados.
Felizmente, é possível criar componentes com aparência e comportamento da maneira que você quiser, limitados apenas pela sua imaginação, pelo tamanho da tela e pela capacidade de processamento disponível, tendo em mente que seu aplicativo pode precisar ser executado em algo com muito menos energia do que a estação de trabalho do computador.
Para criar um componente totalmente personalizado, considere o seguinte:
-
A visualização mais genérica que você pode estender é
View
. Portanto, você geralmente começa estendendo-a para criar seu novo supercomponente. - Você pode fornecer um construtor, que pode receber atributos e parâmetros do XML, e pode consumir seus próprios atributos e parâmetros, como a cor e o intervalo do medidor VU ou a largura e o amortecimento da agulha.
- É provável que você queira criar seus próprios listeners de eventos, acessadores de propriedades e modificadores, além de um comportamento mais sofisticado na sua classe de componentes.
-
É muito provável que você queira substituir
onMeasure()
e também provavelmente precisará substituironDraw()
se quiser que o componente mostre algo. Embora ambos tenham o comportamento padrão, oonDraw()
padrão não faz nada, e oonMeasure()
padrão sempre define um tamanho de 100 x 100, o que você provavelmente não quer. -
Você também pode substituir outros métodos
on
, conforme necessário.
Estender onDraw() e onMeasure()
O método onDraw()
fornece um
Canvas
em que você pode
implementar o que quiser: gráficos 2D, outros componentes padrão ou personalizados, texto estilizado ou
qualquer outra coisa que você possa imaginar.
onMeasure()
está um pouco mais envolvido. onMeasure()
é uma parte essencial
do contrato de renderização entre seu componente e o contêiner dele. onMeasure()
precisa ser
substituído para informar com eficiência e precisão as medidas das partes contidas nele. Isso é
um pouco mais complexo pelos requisitos de limite do elemento pai, que são transmitidos ao método
onMeasure()
, e pelo requisito de chamar o
método setMeasuredDimension()
com a largura e a altura medidas depois que são
calculados. Se você não chamar esse método usando um método onMeasure()
substituído, isso
vai resultar em uma exceção no momento da medição.
De modo geral, a implementação de onMeasure()
é semelhante a esta:
-
O método
onMeasure()
substituído é chamado com especificações de largura e altura, que são tratadas como requisitos para as restrições de medidas de largura e altura que você produz. Os parâmetroswidthMeasureSpec
eheightMeasureSpec
são códigos inteiros que representam dimensões. Uma referência completa aos tipos de restrições que essas especificações podem exigir pode ser encontrada na documentação de referência emView.onMeasure(int, int)
. Esta documentação de referência também explica toda a operação de medição. -
O método
onMeasure()
do componente calcula a largura e a altura de medida, que são necessárias para renderizar o componente. Ele precisa tentar permanecer dentro das especificações transmitidas, embora possa excedê-las. Nesse caso, o pai pode escolher o que fazer, incluindo recorte, rolagem, geração de uma exceção ou solicitação aoonMeasure()
para tentar novamente, talvez com especificações de medição diferentes. -
Quando a largura e a altura forem calculadas, chame o método
setMeasuredDimension(int width, int height)
com as medidas calculadas. Se isso não for feito, haverá uma exceção.
Confira um resumo de outros métodos padrão que o framework chama em visualizações:
Categoria | Métodos | Descrição |
---|---|---|
Criação | Construtores | Há uma forma do construtor que é chamada quando a visualização é criada a partir do código, e uma forma que é chamada quando a visualização é inflada de um arquivo de layout. O segundo analisa e aplica atributos definidos no arquivo de layout. |
|
Chamado depois que uma visualização e todos os filhos são inflados a partir do XML. | |
Layout |
|
Chamado para determinar os requisitos de tamanho da visualização e de todos os filhos. |
|
Chamado quando a visualização precisa atribuir um tamanho e uma posição a todos os filhos. | |
|
Chamado quando o tamanho da visualização é alterado. | |
Desenho |
|
Chamado quando a visualização precisa renderizar o conteúdo. |
Processamento de eventos |
|
Chamado quando ocorre um evento de pressionamento de tecla. |
|
Chamado quando ocorre um evento de liberação de tecla. | |
|
Chamado quando ocorre um evento de movimento do trackball. | |
|
Chamado quando ocorre um evento de movimento da tela touch. | |
Foco |
|
Chamado quando a visualização ganha ou perde o foco. |
|
Chamado quando a janela que contém a visualização ganha ou perde o foco. | |
Anexo |
|
Chamado quando a visualização é anexada a uma janela. |
|
Chamado quando a visualização é removida da janela. | |
|
Chamado quando a visibilidade da janela que contém a visualização é alterada. |
Controles compostos
Se você não quer criar um componente completamente personalizado, mas pretende montar
um componente reutilizável que consista em um grupo de controles existentes, a criação de um componente
composto (ou controle composto) pode ser a melhor opção. Em resumo, isso reúne vários controles ou visualizações mais
atômicos em um grupo lógico de itens que podem ser tratados como uma única coisa.
Por exemplo, uma caixa de combinação pode ser uma combinação de um campo EditText
de linha única
e um botão adjacente com uma lista pop-up anexada. Se o usuário tocar no botão e selecionar algo da
lista, ele preencherá o campo EditText
, mas ele também poderá digitar algo
diretamente no EditText
, se preferir.
No Android, há duas outras visualizações prontamente disponíveis para fazer isso: Spinner
e
AutoCompleteTextView
. Independentemente disso, o conceito de caixa de combinação é um bom exemplo.
Para criar um componente composto, faça o seguinte:
-
Assim como com um
Activity
, use a abordagem declarativa (baseada em XML) para criar os componentes contidos ou aninhá-los de maneira programática no código. O ponto de partida comum é algum tipo deLayout
. Portanto, crie uma classe que estenda umLayout
. No caso de uma caixa de combinação, você pode usar umLinearLayout
com orientação horizontal. Você pode aninhar outros layouts dentro dele, de modo que o componente composto possa ser arbitrariamente complexo e estruturado. -
No construtor da nova classe, use os parâmetros esperados pela superclasse e os transmita para o construtor da superclasse primeiro. Em seguida, você pode configurar as outras visualizações para usar no novo componente. É aqui que você cria o campo
EditText
e a lista pop-up. Você pode introduzir seus próprios atributos e parâmetros no XML que o construtor pode extrair e usar. -
Opcionalmente, crie listeners para eventos que as visualizações contidas podem gerar. Um exemplo é um
método para o listener de clique do item de lista atualizar o conteúdo de
EditText
caso uma seleção de lista seja feita. -
Como opção, crie suas próprias propriedades com acessadores e modificadores. Por exemplo, permita que o valor
EditText
seja definido inicialmente no componente e consulte o conteúdo dele quando necessário. -
Opcionalmente, substitua
onDraw()
eonMeasure()
. Isso geralmente não é necessário ao estender umLayout
, já que o layout tem um comportamento padrão que provavelmente funciona bem. -
Também é possível substituir outros métodos
on
, comoonKeyDown()
, para escolher determinados valores padrão na lista pop-up de uma caixa de combinação quando uma determinada tecla é tocada.
Há vantagens em usar um Layout
como base para um controle personalizado,
incluindo:
- Você pode especificar o layout usando os arquivos XML declarativos, assim como em uma tela de atividade, ou pode criar visualizações de forma programática e aninhá-las no layout do código.
-
Os métodos
onDraw()
eonMeasure()
, além da maioria dos outros métodoson
, têm um comportamento adequado, então você não precisa substituí-los. - É possível criar rapidamente visualizações compostas arbitrariamente complexas e reutilizá-las como se fossem um único componente.
Modificar um tipo de visualização existente
Se houver um componente semelhante ao que você quer, estenda esse componente e substitua
o comportamento que quer mudar. É possível fazer tudo o que você faz com um componente totalmente personalizado,
mas, começando com uma classe mais especializada na hierarquia View
, você pode
ter algum comportamento que faça o que você quiser sem custos.
Por exemplo, o app de exemplo
NotePad
demonstra muitos aspectos do uso da Plataforma Android. Entre elas, está a extensão de uma
visualização EditText
para criar um bloco de notas com linhas. Este não é um exemplo perfeito, e as APIs para
fazer isso podem mudar, mas os princípios são demonstrados.
Importe a amostra do bloco de notas para o Android Studio, caso ainda não tenha feito isso, ou analise a
fonte usando o link fornecido. Em particular, consulte a definição de LinedEditText
no
arquivo
NoteEditor.java
.
Veja alguns itens a serem observados neste arquivo:
-
A definição
A classe é definida com a seguinte linha:
public static class LinedEditText extends EditText
LinedEditText
é definido como uma classe interna na atividadeNoteEditor
, mas é pública para que possa ser acessada comoNoteEditor.LinedEditText
de fora da classeNoteEditor
.Além disso,
LinedEditText
éstatic
, o que significa que ele não gera os chamados "métodos sintéticos" que permitem acessar dados da classe pai. Isso significa que ele se comporta como uma classe separada, e não como algo fortemente relacionado aNoteEditor
. Essa é uma maneira mais limpa de criar classes internas se elas não precisarem acessar o estado da classe externa. Ele mantém a classe gerada pequena e permite que ela seja usada facilmente por outras classes.LinedEditText
estendeEditText
, que é a visualização a ser personalizada nesse caso. Quando você terminar, a nova classe poderá substituir uma visualizaçãoEditText
normal. -
Inicialização de classes
Como sempre, a superclasse é chamada primeiro. Esse não é um construtor padrão, mas é parametrizado. O
EditText
é criado com esses parâmetros quando é inflado de um arquivo de layout XML. Assim, o construtor precisa pegá-las e passá-las para o construtor da superclasse também. -
Métodos modificados
Este exemplo substitui apenas o método
onDraw()
, mas pode ser necessário modificar outros à medida que você cria seus próprios componentes personalizados.Para esse exemplo, substituir o método
onDraw()
permite pintar as linhas azuis na tela de visualizaçãoEditText
. A tela é transmitida para o métodoonDraw()
substituído. O métodosuper.onDraw()
é chamado antes do término do método. O método da superclasse precisa ser invocado. Nesse caso, invoque-a no final depois de pintar as linhas que você quer incluir. -
Componente personalizado
Agora você tem seu componente personalizado, mas como pode usá-lo? No exemplo do Bloco de notas, o componente personalizado é usado diretamente do layout declarativo. Por isso, observe
note_editor.xml
na pastares/layout
:<view xmlns:android="http://schemas.android.com/apk/res/android" class="com.example.android.notepad.NoteEditor$LinedEditText" android:id="@+id/note" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/transparent" android:padding="5dp" android:scrollbars="vertical" android:fadingEdge="vertical" android:gravity="top" android:textSize="22sp" android:capitalize="sentences" />
O componente personalizado é criado como uma visualização genérica no XML, e a classe é especificada usando o pacote completo. A classe interna definida é referenciada usando a notação
NoteEditor$LinedEditText
, que é uma maneira padrão de se referir a classes internas na linguagem de programação Java.Se o componente de visualização personalizado não estiver definido como uma classe interna, você poderá declarar o componente de visualização com o nome do elemento XML e excluir o atributo
class
. Por exemplo:<com.example.android.notepad.LinedEditText id="@+id/note" ... />
A classe
LinedEditText
agora é um arquivo de classe separado. Quando a classe está aninhada na classeNoteEditor
, essa técnica não funciona.Os outros atributos e parâmetros na definição são aqueles transmitidos para o construtor do componente personalizado e, em seguida, transmitidos para o construtor
EditText
. Portanto, são os mesmos parâmetros que você usa para uma visualizaçãoEditText
. Também é possível adicionar seus próprios parâmetros.
Criar componentes personalizados é uma tarefa complicada se você precisar que ela seja.
Um componente mais sofisticado pode substituir ainda mais métodos on
e introduzir os
próprios métodos auxiliares, personalizando substancialmente suas propriedades e o comportamento. O único limite é sua
imaginação e o que você precisa que o componente faça.