Restrições e ordem dos modificadores

No Compose, é possível encadear vários modificadores para mudar a aparência de um elemento combinável. Essas cadeias de modificadores podem afetar as restrições transmitidas aos elementos que podem ser compostos, que definem os limites de largura e altura.

Esta página descreve como os modificadores encadeados afetam as restrições e, consequentemente, a medição e o posicionamento de elementos combináveis.

Modificadores na árvore da IU

Para entender como os modificadores influenciam uns aos outros, é útil visualizar como eles aparecem na árvore da interface, que é gerada durante a fase de composição. Para mais informações, consulte a seção Composição.

Na árvore da interface, é possível visualizar os modificadores como nós de wrapper para os nós de layout:

Código para elementos combináveis e modificadores, além da representação visual deles como uma árvore de interface.
Figura 1. Modificadores que envolvem nós de layout na árvore da IU.

Adicionar mais de um modificador a um elemento de composição cria uma cadeia de modificadores. Quando você encadeia vários modificadores, cada nó de modificador envolve o restante da cadeia e o nó de layout dentro dele. Por exemplo, quando você encadeia um modificador clip e um size, o nó do modificador clip envolve o nó do modificador size, que, por sua vez, envolve o nó de layout Image.

Na fase de layout, o algoritmo que percorre a árvore permanece o mesmo, mas cada nó modificador também é visitado. Dessa forma, um modificador pode mudar os requisitos de tamanho e o posicionamento do modificador ou do nó de layout que ele envolve.

Como mostrado na Figura 2, a implementação dos elementos combináveis Image e Text consiste em uma cadeia de modificadores que envolvem um único nó de layout.

As implementações de Row e Column são nós de layout que descrevem como organizar os filhos.

A estrutura de árvore de antes, mas agora cada nó é apenas um layout simples, com muitos nós de modificador ao redor.
Figura 2. A mesma estrutura de árvore da Figura 1, mas com os elementos que podem ser compostos na árvore de IU visualizados como cadeias de modificadores.

Para resumir:

  • Os modificadores envolvem um único modificador ou nó de layout.
  • Os nós de layout podem organizar vários nós filhos.

As seções a seguir descrevem como usar esse modelo mental para refletir sobre o encadeamento de modificadores e como isso influencia o tamanho dos elementos combináveis.

Restrições na fase de layout

A fase de layout segue um algoritmo de três etapas para encontrar a largura, a altura e as coordenadas x e y de cada nó de layout:

  1. Medir filhos: um nó mede os filhos, se houver.
  2. Decidir o próprio tamanho: com base nessas medições, um nó decide o próprio tamanho.
  3. Posicionar filhos: cada nó filho é posicionado em relação à própria posição de um nó.

Constraints ajudam a encontrar os tamanhos certos para os nós durante as duas primeiras etapas do algoritmo. As restrições definem os limites mínimo e máximo para a largura e a altura de um nó. Quando o nó decide o tamanho, o tamanho medido precisa estar dentro desse intervalo.

Tipos de restrições

Uma restrição pode ser uma das seguintes:

  • Bounded: o nó tem uma largura e altura máximas e mínimas.
Restrições limitadas de tamanhos diferentes em um contêiner.
Figura 3. Restrições limitadas.
  • Sem limite: o nó não tem restrição de tamanho. Os limites máximos de largura e altura são definidos como infinito.
Restrições ilimitadas que têm a largura e a altura definidas como infinito. As restrições vão além do contêiner.
Figura 4. Restrições ilimitadas.
  • Exato: o nó precisa seguir um requisito de tamanho exato. Os limites mínimo e máximo são definidos com o mesmo valor.
Restrições exatas que estão em conformidade com um requisito de tamanho exato no contêiner.
Figura 5. Restrições exatas.
  • Combinação: o nó segue uma combinação dos tipos de restrição anteriores. Por exemplo, uma restrição pode limitar a largura e permitir uma altura máxima ilimitada ou definir uma largura exata, mas fornecer uma altura limitada.
Dois contêineres que mostram combinações de restrições limitadas e ilimitadas e larguras e alturas exatas.
Figura 6. Combinações de restrições limitadas e ilimitadas e larguras e alturas exatas.

A próxima seção descreve como essas restrições são transmitidas de um pai para um filho.

Como as restrições são transmitidas do pai para o filho

Durante a primeira etapa do algoritmo descrito em Restrições na fase de layout, as restrições são transmitidas do pai para o filho na árvore da interface.

Quando um nó pai mede os filhos, ele fornece essas restrições a cada filho para que eles saibam o tamanho que podem ter. Em seguida, ao decidir o próprio tamanho, ele também segue as restrições transmitidas pelos pais.

De modo geral, o algoritmo funciona da seguinte maneira:

  1. Para decidir o tamanho que realmente quer ocupar, o nó raiz na árvore de IU mede os filhos e encaminha as mesmas restrições para o primeiro filho.
  2. Se o filho for um modificador que não afeta a medição, ele encaminhará as restrições para o próximo modificador. As restrições são transmitidas pela cadeia de modificadores como estão, a menos que um modificador que afete a medição seja alcançado. As restrições são redimensionadas de acordo.
  3. Quando um nó que não tem filhos (chamado de "nó folha") é alcançado, ele decide o tamanho com base nas restrições transmitidas e retorna esse tamanho resolvido para o pai.
  4. O pai adapta as restrições com base nas medições desse filho e chama o próximo filho com essas restrições ajustadas.
  5. Depois que todos os filhos de um pai são medidos, o nó pai decide o próprio tamanho e comunica isso ao pai dele.
  6. Dessa forma, toda a árvore é percorrida em profundidade. Por fim, todos os nós decidem os tamanhos, e a etapa de medição é concluída.

Para ver um exemplo detalhado, assista ao vídeo Restrições e ordem de modificadores (link em inglês).

Modificadores que afetam as restrições

Na seção anterior, você aprendeu que alguns modificadores podem afetar o tamanho da restrição. As seções a seguir descrevem modificadores específicos que afetam as restrições.

O modificador size

O modificador size declara o tamanho preferencial do conteúdo.

Por exemplo, a árvore de interface abaixo precisa ser renderizada em um contêiner de 300dp por 200dp. As restrições são limitadas, permitindo larguras entre 100dp e 300dp e alturas entre 100dp e 200dp:

Uma parte de uma árvore de IU com o modificador de tamanho envolvendo um nó de layout e a
  representação das restrições limitadas definidas pelo modificador de tamanho em um contêiner.
Figura 7. Restrições limitadas na árvore da interface e a representação delas em um contêiner.

O modificador size adapta as restrições recebidas para corresponder ao valor transmitido a ele. Neste exemplo, o valor é 150dp:

O mesmo que a Figura 7, mas com o modificador de tamanho adaptando as restrições recebidas para corresponder ao valor transmitido.
Figura 8. O modificador size ajusta as restrições para 150dp.

Se a largura e a altura forem menores que o menor limite de restrição ou maiores que o maior limite de restrição, o modificador vai corresponder às restrições transmitidas o mais próximo possível, ainda aderindo às restrições transmitidas:

Duas árvores de interface e as representações correspondentes em contêineres. No primeiro, o
  modificador de tamanho aceita as restrições recebidas. No segundo, o modificador de tamanho se adapta às
  restrições muito grandes o mais próximo possível, resultando em restrições que preenchem o contêiner.
Figura 9. O modificador size que segue a restrição transmitida o mais próximo possível.

Não é possível encadear vários modificadores size. O primeiro modificador size define as restrições mínima e máxima como um valor fixo. Mesmo que o segundo modificador de tamanho solicite um tamanho menor ou maior, ele ainda precisa aderir aos limites exatos transmitidos, para que não substitua esses valores:

Uma cadeia de dois modificadores de tamanho na árvore da interface e a representação deles em um contêiner,
  que é o resultado do primeiro valor transmitido, não do segundo.
Figura 10. Uma cadeia de dois modificadores size, em que o segundo valor transmitido (50dp) não substitui o primeiro (100dp).

O modificador requiredSize

Use o modificador requiredSize em vez de size se você precisar que o nó substitua as restrições de entrada. O modificador requiredSize substitui as restrições recebidas e transmite o tamanho especificado como limites exatos.

Quando o tamanho é transmitido de volta para a árvore, o nó filho é centralizado no espaço disponível:

O modificador size e requiredSize encadeados em uma árvore de IU e a representação
  correspondente em um contêiner. As restrições do modificador requiredSize substituem as restrições do modificador
  size.
Figura 11. O modificador requiredSize substituindo as restrições de entrada do modificador size.

Os modificadores width e height

O modificador size adapta a largura e a altura das restrições. Com o modificador width, é possível definir uma largura fixa, mas deixar a altura indefinida. Da mesma forma, com o modificador height, é possível definir uma altura fixa, mas deixar a largura indefinida:

Duas árvores de IU, uma com o modificador de largura e a representação do contêiner e a outra
  com o modificador de altura e a representação dele.
Figura 12. O modificador width e o modificador height definem uma largura e uma altura fixas, respectivamente.

O modificador sizeIn

O modificador sizeIn permite definir restrições exatas de mínimo e máximo para largura e altura. Use o modificador sizeIn se precisar de um controle detalhado sobre as restrições.

Uma árvore de interface com o modificador sizeIn com larguras e alturas mínimas e máximas definidas
  e a representação dela em um contêiner.
Figura 13. O modificador sizeIn com minWidth, maxWidth, minHeight e maxHeight definidos.

Exemplos

Nesta seção, mostramos e explicamos a saída de vários snippets de código com modificadores encadeados.

Image(
    painterResource(R.drawable.hero),
    contentDescription = null,
    Modifier
        .fillMaxSize()
        .size(50.dp)
)

Esse snippet produz a seguinte saída:

Um quadrado azul que preenche o contêiner pai.
Figura 14. O Image preenche o tamanho máximo como resultado da cadeia de modificadores.
  • O modificador fillMaxSize muda as restrições para definir a largura e a altura mínimas como o valor máximo: 300dp de largura e 200dp de altura.
  • Mesmo que o modificador size queira usar um tamanho de 50dp, ele ainda precisa aderir às restrições mínimas de entrada. Portanto, o modificador size também vai gerar os limites exatos de restrição de 300 por 200, ignorando o valor fornecido no modificador size.
  • O Image segue esses limites e informa um tamanho de 300 por 200, que é transmitido por toda a árvore.

Image(
    painterResource(R.drawable.hero),
    contentDescription = null,
    Modifier
        .fillMaxSize()
        .wrapContentSize()
        .size(50.dp)
)

Esse snippet produz a seguinte saída:

Um pequeno quadrado azul centralizado no contêiner pai.
Figura 15. O Image é centralizado e dimensionado para 50dp.
  • O modificador fillMaxSize adapta as restrições para definir a largura e a altura mínimas como o valor máximo: 300dp na largura e 200dp na altura.
  • O modificador wrapContentSize redefine as restrições mínimas. Portanto, enquanto fillMaxSize resultou em restrições fixas, wrapContentSize as redefine para restrições limitadas. O nó seguinte pode ocupar todo o espaço novamente ou ser menor que o espaço inteiro.
  • O modificador size define as restrições para os limites mínimo e máximo de 50.
  • O Image é resolvido para um tamanho de 50 por 50, e o modificador size encaminha isso.
  • O modificador wrapContentSize tem uma propriedade especial. Ele usa o filho e o coloca no centro dos limites mínimos disponíveis que foram transmitidos a ele. O tamanho que ele comunica aos pais é igual aos limites mínimos que foram transmitidos a ele.

Ao combinar apenas três modificadores, é possível definir um tamanho para o elemento combinável e centralizá-lo no pai.

Image(
    painterResource(R.drawable.hero),
    contentDescription = null,
    Modifier
        .clip(CircleShape)
        .padding(10.dp)
        .size(100.dp)
)

Esse snippet produz a seguinte saída:

Uma forma circular que é cortada incorretamente devido à
       ordem do modificador.
Figura 16. Uma forma cortada incorretamente devido à ordem do modificador.
  • O modificador clip não muda as restrições.
  • O modificador padding reduz as restrições máximas.
  • O modificador size define todas as restrições como 100dp.
  • O Image segue essas restrições e informa um tamanho de 100dp por 100dp.
  • O modificador padding adiciona 10dp em todos os lados ao tamanho informado pelo Image. Assim, o layout com padding informa uma largura e altura de 120dp.
  • Agora, na fase de desenho, o modificador clip atua em uma tela de 120dp por 120dp. Ele cria uma máscara circular desse tamanho.
  • O modificador padding insere o conteúdo por 10dp em todos os lados, o que reduz o tamanho da tela para o Image para 100dp por 100dp.
  • O Image é desenhado nessa tela menor. A imagem é cortada com base no círculo original de 120dp, então a saída é um resultado não redondo.