Como usar CustomMultiChildLayout e CustomSingleChildLayout no Flutter

10

Alguém com experiência no uso CustomSingleChildLayoute CustomMultiChildLayoutaulas pode explicar em detalhes (com exemplos) como usá-los.

Eu sou novo no Flutter e estou tentando entender como usá-los. No entanto, a documentação é horrível e não é clara. Tentei vasculhar a internet em busca de exemplos, mas não há outra documentação.

Ficaria eternamente grato se você pudesse ajudar.

Obrigado!

Walter M
fonte
Você poderia adicionar o que você quer usá-los?
João Soares
@ JoãoSoares Não se preocupe, estou escrevendo uma resposta extensa.
Creativecreatorormaybenot
Grandes expectativas então!
João Soares
@ JoãoSoares Concluído, deixe-me saber se você tem alguma correção.
creativecreatorormaybenot
@creativecreatorormaybenot Isso seria muito improvável. Eu já vi suas respostas antes. Ótima resposta, como sempre.
João Soares

Respostas:

20

Antes de tudo, quero dizer que estou feliz em ajudá-lo com isso, pois posso entender suas lutas - há benefícios em descobrir isso sozinho (a documentação é incrível).

O CustomSingleChildLayoutque será óbvio depois que eu expliquei CustomMultiChildLayouta você.

CustomMultiChildLayout

O objetivo deste widget é permitir que você organize os filhos que você transmite a ele em uma única função, ou seja, suas posições e tamanhos podem depender um do outro, o que é algo que você não pode obter usando, por exemplo, o Stackwidget pré-construído .

CustomMultiChildLayout(
  children: [
    // Widgets you want to layout in a customized manner
  ],
)

Agora, você precisa seguir mais duas etapas antes de começar a organizar seus filhos:

  1. Toda criança para quem você passa childrenprecisa ser uma LayoutIde você passa o widget que realmente deseja mostrar quando criança LayoutId. Eles ididentificarão seus widgets de maneira exclusiva, tornando-os acessíveis ao organizá-los:
CustomMultiChildLayout(
  children: [
    LayoutId(
      id: 1, // The id can be anything, i.e. any Object, also an enum value.
      child: Text('Widget one'), // This is the widget you actually want to show.
    ),
    LayoutId(
      id: 2, // You will need to refer to that id when laying out your children.
      child: Text('Widget two'),
    ),
  ],
)
  1. Você precisa criar uma MultiChildLayoutDelegatesubclasse que lide com a parte do layout. A documentação aqui parece ser muito elaborada.
class YourLayoutDelegate extends MultiChildLayoutDelegate {
  // You can pass any parameters to this class because you will instantiate your delegate
  // in the build function where you place your CustomMultiChildLayout.
  // I will use an Offset for this simple example.

  YourLayoutDelegate({this.position});

  final Offset position;
}

Agora, toda a configuração está concluída e você pode começar a implementar o layout real. Existem três métodos que você pode usar para isso:

  • hasChild, que permite verificar se um ID específico (lembra-se LayoutId?) foi passado para o children, ou seja, se um filho desse ID estiver presente.

  • layoutChild, que você precisa chamar para todos os IDs , todas as crianças, fornecidas exatamente uma vez e fornecerá o resultado Sizedessa criança.

  • positionChild, que permite alterar a posição de Offset(0, 0)para qualquer deslocamento especificado.

Sinto que o conceito deve ficar bem claro agora, e é por isso que ilustrarei como implementar um delegado para o exemplo CustomMultiChildLayout:

class YourLayoutDelegate extends MultiChildLayoutDelegate {
  YourLayoutDelegate({this.position});

  final Offset position;

  @override
  void performLayout(Size size) {
    // `size` is the size of the `CustomMultiChildLayout` itself.

    Size leadingSize = Size.zero; // If there is no widget with id `1`, the size will remain at zero.
    // Remember that `1` here can be any **id** - you specify them using LayoutId.
    if (hasChild(1)) {
      leadingSize = layoutChild(
        1, // The id once again.
        BoxConstraints.loose(size), // This just says that the child cannot be bigger than the whole layout.
      );
      // No need to position this child if we want to have it at Offset(0, 0).
    }

    if (hasChild(2)) {
      final secondSize = layoutChild(
        2,
        BoxConstraints(
          // This is exactly the same as above, but this can be anything you specify.
          // BoxConstraints.loose is a shortcut to this.
          maxWidth: size.width,
          maxHeight: size.height,
        ),
      );

      positionChild(
        2,
        Offset(
          leadingSize.width, // This will place child 2 to the right of child 1.
          size.height / 2 - secondSize.height / 2, // Centers the second child vertically.
        ),
      );
    }
  }
}

Dois outros exemplos são o da documentação (verifique a etapa 2 da preparação ) e um exemplo do mundo real que escrevi há algum tempo para o feature_discoverypacote: MultiChildLayoutDelegateimplementação e CustomMultiChildLayoutno buildmétodo .

A última etapa é substituir o shouldRelayoutmétodo , que controla simples se performLayoutdeve ser chamado novamente em um determinado momento comparando com um delegado antigo (opcionalmente, você também pode substituir getSize) e adicionando o delegado ao seu CustomMultiChildLayout:

class YourLayoutDelegate extends MultiChildLayoutDelegate {
  YourLayoutDelegate({this.position});

  final Offset position;

  @override
  void performLayout(Size size) {
    // ... (layout code from above)
  }

  @override
  bool shouldRelayout(YourLayoutDelegate oldDelegate) {
    return oldDelegate.position != position;
  }
}
CustomMultiChildLayout(
  delegate: YourLayoutDelegate(position: Offset.zero),
  children: [
    // ... (your children wrapped in LayoutId's)
  ],
)

Considerações

  • Eu usei 1e 2como os IDs neste exemplo, mas usar um enumé provavelmente a melhor maneira de lidar com os IDs se você tiver IDs específicos.

  • Você pode passar um Listenablepara super(por exemplo super(relayout: animation)) se desejar animar o processo de layout ou acioná-lo com base em uma audição em geral.

CustomSingleChildLayout

A documentação explica muito bem o que eu descrevi acima e aqui você também verá por que eu disse que CustomSingleChildLayoutserá muito óbvio depois de entender como CustomMultiChildLayoutfunciona:

CustomMultiChildLayout é apropriado quando há relacionamentos complexos entre o tamanho e o posicionamento de vários widgets. Para controlar o layout de um único filho, CustomSingleChildLayout é mais apropriado.

Isso também significa que o uso CustomSingleChildLayoutsegue os mesmos princípios que descrevi acima, mas sem IDs, porque há apenas um filho.
Você precisa usar um em SingleChildLayoutDelegatevez disso, que possui métodos diferentes para implementar o layout (todos eles têm comportamento padrão, portanto, tecnicamente, todos são opcionais para substituir ):

Tudo o resto é exatamente o mesmo (lembre-se de que você não precisa LayoutIde só tem um filho único em vez de children).


MultiChildRenderObjectWidget

É nisso que CustomMultiChildLayoutse baseia.
Usar isso requer um conhecimento ainda mais profundo sobre o Flutter e é novamente um pouco mais complicado, mas é a melhor opção se você quiser mais personalização, pois é um nível ainda mais baixo. Isso tem uma grande vantagem sobre CustomMultiChildLayout(geralmente, há mais controle):

CustomMultiChildLayout não pode dimensionar -se com base em seus filhos (ver questão sobre uma melhor documentação para o raciocínio ).

Não explicarei como usar MultiChildRenderObjectWidgetaqui por razões óbvias, mas se você estiver interessado, pode conferir minha submissão ao desafio Flutter Clock após 20 de janeiro de 2020, no qual uso MultiChildRenderObjectWidgetextensivamente - você também pode ler um artigo sobre isso , o que deve explicar um pouco de como tudo funciona.

Por enquanto, você pode se lembrar que MultiChildRenderObjectWidgeté isso que torna CustomMultiChildLayoutpossível e usá-lo diretamente fornecerá alguns bons benefícios, como não precisar usar LayoutIde, em vez disso, ser capaz de acessar os RenderObjectdados pai diretamente.

Fato engraçado

Eu escrevi todo o código em texto sem formatação (no campo de texto StackOverflow), portanto, se houver erros, aponte-os para mim e eu os corrigirei.

creativecreatorormaybenot
fonte
11
Uau. Resposta incrível. Deve LayoutIdser exclusivo global ou localmente? globalmente, ou seja, os widgets do tipo irmão CustomMultiChildLayoutexigem ter distintos LayoutIdem seus filhos.
Om-ha
11
@ om-ha localmente - o id será armazenado nos dados de pai LayoutId, o que significa que estes dados, a ID, só vai ser acedida pelo progenitor directa :)
creativecreatorormaybenot
2
UAU! Você é meu salvador! É ISSO QUE EU ESTOU FALANDO! é assim que a documentação do flutter deve ser !! Não havia como eu descobrir como essas classes funcionavam apenas com a documentação normal. MUITO OBRIGADO!
Walter M
11
Resposta incrível realmente. CustomMultiChildLayouté tão útil em cenários em que você precisa agrupar o redimensionamento automático de vários widgets de texto dentro de um Columnde Row, por exemplo.
om-ha