O Android oferece um modelo de componentes sofisticado e eficiente para criar sua UI, com base nas classes fundamentais de layout
View e
ViewGroup. A plataforma inclui várias subclasses View e ViewGroup pré-criadas, chamadas widgets e layouts, respectivamente, que você pode usar para construir sua interface.
Uma lista parcial de widgets disponíveis inclui Button,
TextView,
EditText,
ListView,
CheckBox,
RadioButton,
Gallery,
Spinner e, para fins especiais,
AutoCompleteTextView,
ImageSwitcher e
TextSwitcher.
Entre os layouts disponíveis estão
LinearLayout,
FrameLayout,
RelativeLayout
e outros. Para mais exemplos, consulte
Layouts comuns.
Se nenhum dos widgets ou layouts predefinidos atender às suas necessidades, crie sua própria subclasse View. Se você precisar fazer apenas pequenos ajustes em um widget ou
layout, poderá criar uma subclasse do widget ou layout e substituir os métodos correspondentes.
Criar suas próprias subclasses View proporciona 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 de
Viewde renderização totalmente 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. -
É possível combinar um grupo de componentes
Viewem um novo componente único, 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 dois painéis (um painel esquerdo e 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 NotePad usa esse recurso para criar uma página de bloco de notas com linhas. - Você pode capturar outros eventos, como pressionamento de teclas, e manipulá-los de forma personalizada, por exemplo, para um jogo.
As seções abaixo explicam como criar visualizações personalizadas e usá-las no seu aplicativo. Para informações de referências detalhadas, consulte a classe View.
A abordagem básica
Confira a seguir uma visão geral do que você precisa saber para criar seus próprios componentes View:
-
Estenda uma classe ou subclasse
Viewcom sua própria classe. -
Substitua alguns dos métodos da superclasse. Os métodos da superclasse que precisam ser substituídos começam com
on. Por exemplo,onDraw(),onMeasure(), eonKeyDown(). Isso é semelhante aos eventosonemActivityouListActivityque você substitui para o ciclo de vida e outros hooks de funcionalidade. - Use sua nova classe de extensão. Depois de concluída, 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 da maneira que você quiser. Talvez você queira um medidor VU gráfico parecido com um antigo medidor analógico ou uma visualização de texto simples em que uma bola saltitante se move ao longo das palavras como em uma máquina de karaokê. Você pode querer algo que os componentes integrados não conseguem fazer, não importa como sejam combinados.
Felizmente, você pode criar componentes que parecem e se comportam da maneira que quiser, limitados apenas pela sua imaginação, pelo tamanho da tela e pela capacidade de processamento disponível. Lembre-se de que seu aplicativo pode precisar ser executado em algo com significativamente menos energia do que sua estação de trabalho de computador.
Para criar um componente totalmente personalizado, considere o seguinte:
-
A visualização mais genérica que você pode estender é
View. Por isso, geralmente você começa estendendo essa opção para criar seu novo supercomponente. - Você pode fornecer um construtor que pode receber atributos e parâmetros do XML. Também é possível 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.
- É interessante que você crie seus próprios listeners de eventos, acessadores e modificadores de propriedade e um comportamento mais sofisticado na sua classe de componentes.
-
É muito recomendável substituir
onMeasure()e provavelmente necessário substituironDraw()se quiser que o componente mostre algo. Embora ambos tenham comportamento padrão, oonDraw()padrão não faz nada, e oonMeasure()padrão sempre define um tamanho de 100 x 100, o que provavelmente não é o que você quer. -
Também é possível substituir outros métodos
on, conforme necessário.
Estender onDraw() e onMeasure()
O método onDraw() oferece 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. Isso é um pouco mais complexo devido aos requisitos de limites do elemento pai, que são passados ao método onMeasure(), e pelo requisito de chamar o método setMeasuredDimension() com a largura e a altura medidas depois de serem calculadas. Se você não chamar esse método de um método onMeasure() substituído, isso
resultará em uma exceção no momento da medição.
De forma geral, a implementação de onMeasure() tem esta aparência:
-
O método
onMeasure()substituído é chamado com especificações de largura e altura, que são tratadas como requisitos para as restrições das medidas de largura e altura produzidas. Os parâmetroswidthMeasureSpeceheightMeasureSpecsão códigos inteiros que representam dimensões. Uma referência completa ao tipo de restrições que essas especificações podem exigir está disponível na documentação de referência emView.onMeasure(int, int). Essa documentação também explica toda a operação de medição. -
O método
onMeasure()do componente calcula uma largura e 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 elemento pai pode escolher o que fazer, incluindo recortar, rolar, gerar uma exceção ou pedir 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. Caso contrário, uma exceção será gerada.
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 no código e uma forma que é chamada quando a visualização é inflada de um arquivo de layout. A segunda forma analisa e aplica atributos definidos no arquivo de layout. |
|
Chamado depois que uma visualização e todos os filhos dela são inflados 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 muda. | |
| 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 touchscreen. | |
| 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 é separada da janela. | |
|
Chamado quando a visibilidade da janela que contém a visualização é alterada. |
Controles compostos
Se você não quiser criar um componente completamente personalizado, mas quiser 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 uma série de 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 uma linha
e um botão adjacente com uma lista pop-up anexada. Se o usuário tocar no botão e selecionar algo da lista, o campo EditText será preenchido, 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. De qualquer forma, esse conceito de caixa de combinação é um bom exemplo.
Para criar um componente composto, faça o seguinte:
-
Assim como com uma
Activity, use a abordagem declarativa (baseada em XML) para criar os componentes contidos ou aninhe-os de maneira programática no código. O ponto de partida usual é umLayoutde algum tipo. Portanto, crie uma classe que estenda umLayout. No caso de uma caixa de combinação, use umLinearLayoutcom orientação horizontal. É possível aninhar outros layouts dentro dele, de modo que o componente composto pode ser arbitrariamente complexo e estruturado. -
No construtor da nova classe, pegue os parâmetros esperados pela superclasse e transmita-os 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
EditTexte a lista pop-up. Você pode introduzir seus próprios atributos e parâmetros no XML que seu construtor pode extrair e usar. -
Se quiser, crie listeners para eventos que as visualizações contidas possam gerar. Um exemplo é um método listener para o listener de clique do item da lista que atualiza o conteúdo do
EditTextse uma seleção de lista for feita. -
Se quiser, crie suas próprias propriedades com acessadores e modificadores. Por exemplo, permita que o valor de
EditTextseja definido inicialmente no componente e consulte o conteúdo quando necessário. -
Se quiser, 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. -
Como opção, substitua outros métodos
on, comoonKeyDown(), por exemplo, para escolher determinados valores padrão na lista pop-up de uma caixa de combinação quando uma determinada tecla é pressionada.
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 com uma tela de atividade, ou criar visualizações programaticamente 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. - Você pode construir rapidamente visualizações compostas arbitrariamente complexas e reutilizá-las como se fossem um único componente.
Modificar um tipo de visualização
Se houver um componente semelhante ao que você quer, estenda esse componente e substitua
o comportamento que quer mudar. Você pode fazer tudo o que faria com um componente totalmente personalizado, mas ao começar com uma classe mais especializada na hierarquia View, é possível ter um comportamento que faz o que você quer sem custo financeiro.
Por exemplo, o
app de exemplo NotePad
demonstra muitos aspectos do uso da plataforma Android. Entre eles, 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.
Se você ainda não tiver feito isso, importe a amostra do bloco de notas para o Android Studio ou veja o código 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 EditTextLinedEditTexté definida como uma classe interna dentro da atividadeNoteEditor, mas é pública para que possa ser acessada comoNoteEditor.LinedEditTextde fora da classeNoteEditor.Além disso,
LinedEditTextéstatic, o que significa que ela não gera os chamados "métodos sintéticos" que permitem acessar dados da classe pai. Isso significa que ela se comporta como uma classe separada em vez de algo fortemente relacionado aNoteEditor. Essa é uma maneira mais limpa de criar classes internas se elas não precisarem acessar o estado da classe externa. Ela mantém a classe gerada pequena e permite que ela seja usada facilmente em outras classes.LinedEditTextestendeEditText, que é a visualização a ser personalizada nesse caso. Quando terminar, a nova classe poderá substituir uma visualização normal deEditText. -
Inicialização da classe
Como sempre, a superclasse é chamada primeiro. Este não é um construtor padrão, mas um parametrizado. O
EditTexté criado com esses parâmetros quando é inflado de um arquivo de layout XML. Portanto, o construtor precisa pegá-los e transmiti-los ao construtor da superclasse também. -
Métodos substituídos
Este exemplo substitui apenas o método
onDraw(), mas talvez seja necessário substituir outros à medida que você cria 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-o 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. Consulte
note_editor.xmlna 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 personalizada não estiver definido como uma classe interna, você poderá declarar o componente com o nome do elemento XML e excluir o atributo
class. Por exemplo:<com.example.android.notepad.LinedEditText id="@+id/note" ... />
A classe
LinedEditTextagora é 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 de componente personalizado e, em seguida, para o construtor de
EditText. Portanto, são os mesmos parâmetros que você usa para uma visualização deEditText. Também é possível adicionar seus próprios parâmetros.
A criação de componentes personalizados só é complicada se você precisar que seja.
Um componente mais sofisticado pode substituir ainda mais métodos on e introduzir seus
próprios métodos auxiliares, personalizando substancialmente suas propriedades e comportamento. O único limite é sua imaginação e o que você precisa que o componente faça.