O Android oferece um modelo de componentes sofisticado e eficiente para criar sua IU,
com base nas classes fundamentais de layout: View
e
ViewGroup
. Para começar, a plataforma inclui diversas subclasses pré-criadas
View e ViewGroup, chamadas widgets e layouts, respectivamente,
que você pode usar para construir sua IU.
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 ver mais exemplos, consulte Objetos de layout comuns.
Se nenhum dos widgets ou layouts predefinidos atender às suas necessidades, você poderá criar sua própria subclasse View. Se você precisar fazer apenas pequenos ajustes em um widget ou layout já existente, poderá simplesmente criar uma subclasse do widget ou layout e substituir os métodos correspondentes.
A criação de subclasses View próprias proporciona controle preciso sobre a aparência e a função de um elemento de tela. Para ter 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 visualização de renderização personalizada, por exemplo, um botão de "controle de volume" renderizado usando gráficos 2D e que se assemelha a um controle eletrônico analógico.
- Você pode combinar um grupo de componentes de visualização em um único novo componente, por exemplo, para fazer algo como uma caixa de combinação, que combina a lista de pop-ups e o campo de entrada de texto livre; um controle seletor de painel duplo, que é um painel esquerdo e direito com uma lista em cada para atribuição de itens; e assim por diante.
- Você pode substituir a forma como um componente EditText é renderizado na tela. O Tutorial do bloco de notas 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 alguma 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
Veja a seguir uma visão geral de alto nível do que você precisa saber para começar a criar componentes de visualização:
-
Estenda uma classe
View
ou subclasse com sua própria classe. -
Modifique 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 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ída, sua nova classe de extensão pode ser usada no lugar da visualização em que ela foi baseada.
Dica: as classes de extensão podem ser definidas como classes internas dentro das atividades que as utilizam. Isso é útil porque controla o acesso a elas, mas não é necessário. Talvez você queira criar uma nova visualização pública para uso mais amplo no seu aplicativo.
Componentes totalmente personalizados
Componentes totalmente personalizados podem ser usados para criar componentes gráficos que são exibidos como você quiser. Talvez 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ê. De qualquer maneira, você quer algo que os componentes integrados simplesmente não farão, independentemente de como sejam combinados.
Felizmente, é fácil criar componentes com a aparência e o comportamento que você quer. As limitações talvez sejam apenas sua imaginação, o tamanho da tela e a capacidade de processamento disponível. Lembre-se de que seu aplicativo precisa funcionar em algo com muito menos capacidade do que seu computador.
Para criar um componente totalmente personalizado:
-
A visualização mais genérica que você pode estender é, obviamente,
View
. Assim, 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. Você pode também consumir seus atributos e parâmetros, talvez a cor e o intervalo do medidor VU, a largura e o amortecimento da agulha etc.
- É interessante que você crie seus próprios listeners de eventos, acessadores e modificadores de propriedade e, possivelmente, 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 o comportamento padrão, oonDraw()
padrão não fará nada, e oonMeasure()
padrão sempre definirá um tamanho de 100 x 100, o que provavelmente não é o que você quer. -
Outros métodos
on...
também podem ser modificados conforme necessário.
Estender onDraw()
e onMeasure()
O método onDraw()
oferece um Canvas
sobre o qual 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.
Observação:
isso não se aplica a gráficos 3D. Se quiser
usar gráficos 3D, você precisará estender SurfaceView
em vez da visualização e desenhar a partir de uma linha de execução separada. Consulte a
amostra GLSurfaceViewActivity para
ver detalhes.
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 transmitidos 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 conseguir
chamar esse método a partir de um método onMeasure()
substituído, o
resultado será 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 medida de largura e altura (parâmetroswidthMeasureSpec
eheightMeasureSpec
, ambos são códigos inteiros representando dimensões) que precisam ser tratadas como requisitos para as restrições das medidas de largura e altura a serem produzidas. 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 de referência explica muito bem toda a operação de medição. -
O método
onMeasure()
do componente precisa calcular uma largura e altura de medida que serão necessárias para renderizar o componente. Ele tem que tentar permanecer dentro das especificações transmitidas, embora possa optar por excedê-las. Nesse caso, o 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. -
Depois que a largura e a altura são calculadas, o método
setMeasuredDimension(int width, int height)
precisa ser chamado com as medidas calculadas. Se isso não for feito, uma exceção será gerada.
Veja um resumo de alguns dos 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 precisa analisar e aplicar todos os atributos definidos no arquivo de layout. |
|
Chamado depois que uma visualização e todos os filhos dela foram 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 mudou. | |
Desenho |
|
Chamado quando a visualização precisa renderizar o conteúdo. |
Processamento de eventos |
|
Chamado quando ocorre um novo evento 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 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 foi alterada. |
Controles compostos
Se você não quer criar um componente completamente personalizado, mas quer montar um componente reutilizável que consista em um grupo de controles já existentes, a criação de um componente composto (ou controle composto) pode ser adequada. Para resumir, isso reúne uma série de controles mais atômicos (ou visualizações) em um grupo lógico de itens que podem ser tratados como uma única coisa. Por exemplo, uma caixa de combinação pode ser considerada uma combinação de um campo EditText de uma linha e um botão adjacente com uma PopupList anexada. Se você pressionar o botão e selecionar algo da lista, ele preencherá o campo "EditText", mas o usuário também poderá digitar algo diretamente no campo, se preferir.
No Android, há duas outras visualizações prontamente disponíveis para fazer
isso: Spinner
e
AutoCompleteTextView
. Mas
independentemente disso, o conceito de uma caixa de combinação é um exemplo fácil
de entender.
Para criar um componente composto:
- O ponto de partida usual é algum tipo de layout; portanto, crie uma classe que estenda um layout. Talvez, no caso de uma caixa de combinação, possamos usar um LinearLayout com orientação horizontal. Lembre-se de que outros layouts podem ser aninhados internamente, de modo que o componente composto pode ser arbitrariamente complexo e estruturado. Observe que, assim como ocorre com uma atividade, você pode usar a abordagem declarativa (baseada em XML) para criar os componentes contidos ou pode aninhá-los de maneira programática no código.
- 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. Esse é o momento em que você cria o campo EditText e a PopupList. Você também pode introduzir seus próprios atributos e parâmetros no XML que podem ser extraídos e usados pelo construtor.
- Você também pode criar listeners para eventos que as visualizações contidas possam gerar, por exemplo, um método listener para o List Item Click Listener para atualizar o conteúdo do EditText se uma seleção de lista for feita.
- Você também pode criar suas próprias propriedades com acessadores e modificadores, por exemplo, permitir que o valor de EditText seja definido inicialmente no componente e consultar o conteúdo quando necessário.
-
Ao estender um layout, não é necessário substituir os métodos
onDraw()
eonMeasure()
, já que o layout terá um comportamento padrão que provavelmente funcionará bem. No entanto, você ainda poderá substituí-lo se for necessário. -
Você pode 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 é pressionada.
Para resumir, o uso de um layout como base para um controle personalizado tem uma série de vantagens, incluindo:
- Você pode especificar o layout usando os arquivos XML declarativos como faria com uma tela de atividades ou criar visualizações programaticamente e aninhá-las no layout do código.
-
Os métodos
onDraw()
eonMeasure()
(e a maioria dos outros métodoson...
) terão um comportamento adequado para que você não precise substituí-los. - No final, é possível construir rapidamente visualizações compostas arbitrariamente complexas e reutilizá-las como se fossem um único componente.
Como modificar um tipo de visualização existente
Há ainda uma opção mais fácil para criar uma visualização personalizada que é útil em determinadas circunstâncias. Se houver um componente que já é muito parecido com o que você quer, simplesmente estenda esse componente e substitua o comportamento que quer alterar. Você pode fazer tudo o que faria com um componente totalmente personalizado. No entanto, ao começar com uma classe mais especializada na hierarquia de visualização, também é possível usar muitos comportamentos livres que provavelmente fazem exatamente o que você quer.
Por exemplo, o app Bloco de notas demonstra muitos aspectos do uso da Plataforma Android. Entre eles, está a extensão da visualização EditText para criar um bloco de notas com linhas. O exemplo não é 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, observe 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
é definida como uma classe interna dentro da atividadeNoteEditor
, mas é pública para que possa ser acessada comoNoteEditor.LinedEditText
de fora da classeNoteEditor
, se você quiser. -
É
static
, o que significa que ela não gera os chamados “métodos sintéticos” que permitem acessar dados da classe pai, o que, por sua vez, significa que ela realmente se comporta como uma classe separada em vez de algo altamente relacionado aNoteEditor
. Essa é uma maneira mais limpa de criar classes internas se elas não precisarem acessar o estado da classe externa, manter a classe gerada pequena e permitir que ela seja usada facilmente em outras classes. -
Ela estende
EditText
, que é a visualização que escolhemos para personalizar nesse caso. Quando terminarmos, a nova classe poderá substituir uma visualização normal deEditText
.
-
-
Inicialização da classe
Como sempre, a superclasse é chamada primeiro. Além disso, este não é um construtor padrão, mas um construtor parametrizado. O EditText é criado com esses parâmetros quando é inflado de um arquivo de layout XML. Portanto, nosso construtor precisa pegar e transmitír eles para o construtor da superclasse também.
-
Métodos modificados
Este exemplo substitui apenas um método,
onDraw()
, mas talvez seja necessário substituir outros à medida que você cria componentes personalizados.Para esse exemplo, substituir o método
onDraw()
nos permite pintar as linhas azuis na tela de visualizaçãoEditText
. A tela é transmitida para o métodoonDraw()
substituído. O método super.onDraw() é chamado antes do término do método. O método da superclasse precisa ser invocado e, nesse caso, fazemos isso no final depois de pintarmos as linhas que queremos incluir. -
Uso do componente personalizado
Temos o componente personalizado, mas como podemos usá-lo? No exemplo do bloco de notas, o componente personalizado é usado diretamente do layout declarativo. Veja
note_editor.xml
na pasta res/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. Observe também que a
classe interna que definimos é 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á, como alternativa, declarar o componente de visualização com o nome do elemento XML e excluir o atributo
class
. 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 de componente personalizado e transmitidos para o construtor de EditText, de modo que sejam os mesmos parâmetros que você usaria para uma visualização de EditText. Observe que também é possível adicionar seus próprios parâmetros. Falaremos sobre isso novamente abaixo.
-
O componente personalizado é criado como uma visualização genérica no XML, e
a classe é especificada usando o pacote completo. Observe também que a
classe interna que definimos é referenciada usando a notação
E isso é tudo. Evidentemente, esse é um caso simples, mas este é o objetivo: 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 alguns dos 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.