Como o Android desenha visualizações

Quando uma Activity receber foco, ela será solicitada a desenhar o próprio layout. O framework do Android gerenciará o procedimento do desenho, mas a Activity precisará fornecer o nó raiz da hierarquia de layout.

O desenho começa com o nó raiz do layout. É solicitado que a árvore de layout seja medida e desenhada. O desenho é processado percorrendo a árvore e renderizando cada View que cruza a região inválida. Por sua vez, cada ViewGroup é responsável por solicitar que cada filho seja desenhado (com o método draw()) e cada View seja responsável por se desenhar. Como a travessia da árvore é feita em pré-ordem, isso significa que os pais serão desenhados antes (ou seja, atrás) dos filhos, com os irmãos desenhados na ordem em que aparecem na árvore.

Observação: o framework não desenha objetos View que não estejam em uma região válida. Ele também cuida do desenho do plano de fundo da View para você.

Chame invalidate() para forçar o desenho de uma View.

O desenho do layout é um processo de duas etapas: uma passagem de medição e uma de layout.

Passagem de medição

A passagem de medição é implementada em measure(int, int) e é uma travessia de cima para baixo da árvore de View. Cada View envia as especificações de dimensão pela árvore durante a recursão. No final da passagem de medição, cada View armazenou as próprias medidas. A segunda passagem acontece em layout(int, int, int, int) e também é de cima para baixo. Durante essa passagem, cada pai é responsável por posicionar todos os filhos usando os tamanhos calculados na passagem de medição.

Quando um método measure() do objeto View retorna, os valores getMeasuredWidth() e getMeasuredHeight() dele precisam ser definidos, junto com aqueles para todos os descendentes desse objeto View. Os valores de largura e altura medidos de um objeto View precisam respeitar as restrições impostas pelos pais do objeto View. Isso garante que, ao final da passagem de medição, todos os pais aceitem todas as medidas dos filhos. Um View pai pode chamar measure() mais de uma vez nos filhos. Por exemplo, um pai pode medir cada filho uma vez com dimensões não especificadas para descobrir o tamanho desejado e, depois, chamar measure() novamente com números reais se a soma de todos os tamanhos irrestritos dos filhos for muito grande ou muito pequena. Isto é, se os filhos não concordarem entre si quanto ao espaço que cada um recebe, o pai intervirá e definirá as regras na segunda passagem.

A passagem de medição usa duas classes para comunicar dimensões. A classe ViewGroup.LayoutParams é usada por objetos View para dizer aos pais como eles serão medidos e posicionados. A classe base ViewGroup.LayoutParams apenas descreve o tamanho do View para a largura e a altura. Para cada dimensão, é possível especificar uma das seguintes opções:

  • Um número exato
  • MATCH_PARENT, que significa que View quer ser do tamanho do pai (menos o padding)
  • WRAP_CONTENT, que significa que View quer ser grande o suficiente para o próprio conteúdo (mais o padding).

Existem subclasses de ViewGroup.LayoutParams para diferentes subclasses de ViewGroup. Por exemplo, RelativeLayout tem a própria subclasse de ViewGroup.LayoutParams, que inclui a capacidade de centralizar objetos View filhos horizontal e verticalmente.

Os objetos MeasureSpec são usados para enviar os requisitos da árvore do pai para o filho. Um MeasureSpec pode estar em um dos três modos:

  • UNSPECIFIED: é usado por um pai para determinar a dimensão desejada de um View filho. Por exemplo, um LinearLayout pode chamar measure() no filho com a altura definida como UNSPECIFIED e uma largura de EXACTLY 240 para descobrir a altura que o filho View quer receber com uma largura de 240 pixels.
  • EXACTLY: é usado pelo pai para impor um tamanho exato ao filho. O filho precisa usar esse tamanho e garantir que todos os descendentes se encaixem nesse tamanho.
  • AT MOST: é usado pelo pai para impor um tamanho máximo ao filho. O filho precisa garantir que ele e todos os descendentes se encaixem nesse tamanho.

Passagem de layout

Para iniciar um layout, chame requestLayout(). Esse método costuma ser chamado por um View quando ele acredita que não caberá mais nos limites atuais.