Flutter Layout Cheat Sheet

Tempo de leitura: 11 minutes

Você precisa de amostras de layout simples para o Flutter?
Apresento a você meu conjunto de trechos de código de layout do Flutter. Vou mantê-lo curto, doce e simples, com muitos exemplos visuais.
Ainda assim, é um trabalho em andamento – o catálogo de amostras vai crescer. Vou me concentrar mais no uso de widgets do Flutter em vez de mostrar os componentes (a Galeria do Flutter é ótima para isso!).
Se você tiver um problema com o “layout” do seu Flutter ou quiser compartilhar seus trechos com outras pessoas, envie uma mensagem!

 

Row e Column

MainAxisAlignment

Row /*ou Column*/( 
  mainAxisAlignment: MainAxisAlignment.start,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),

Row /*ou Column*/( 
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),

Row /*ou Column*/( 
  mainAxisAlignment: MainAxisAlignment.end,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),

Row /*ou Column*/( 
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),

Row /*ou Column*/( 
  mainAxisAlignment: MainAxisAlignment.spaceAround,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),

Você deve usar CrossAxisAlignment.baseline se precisar que a linha de base de um texto diferente seja alinhada.

Row(
  crossAxisAlignment: CrossAxisAlignment.baseline,
  textBaseline: TextBaseline.alphabetic,
  children: <Widget>[
    Text(
      'Baseline',
      style: Theme.of(context).textTheme.display3,
    ),
    Text(
      'Baseline',
      style: Theme.of(context).textTheme.body1,
    ),
  ],
),

CrossAxisAlignment

Row /*ou Column*/( 
  crossAxisAlignment: CrossAxisAlignment.center,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 200),
    Icon(Icons.star, size: 50),
  ],
),

Row /*ou Column*/( 
  crossAxisAlignment: CrossAxisAlignment.end,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 200),
    Icon(Icons.star, size: 50),
  ],
),

Row /*ou Column*/( 
  crossAxisAlignment: CrossAxisAlignment.stretch,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 200),
    Icon(Icons.star, size: 50),
  ],
),

MainAxisSize

Row /*ou Column*/( 
  mainAxisSize: MainAxisSize.max,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),

Row /*ou Column*/( 
  mainAxisSize: MainAxisSize.min,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),

 

IntrinsicWidth e IntrinsicHeight

Deseja que todos os widgets dentro de Linha ou Coluna sejam tão altos/largos quanto o widget mais alto/largo? Não procure mais!

Caso você tenha esse tipo de layout:

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('IntrinsicWidth')),
    body: Center(
      child: Column(
        children: <Widget>[
          ElevatedButton(
            onPressed: () {},
            child: Text('Short'),
          ),
          ElevatedButton(
            onPressed: () {},
            child: Text('A bit Longer'),
          ),
          ElevatedButton(
            onPressed: () {},
            child: Text('The Longest text button'),
          ),
        ],
      ),
    ),
  );
}

 

Mas você gostaria de ter todos os botões tão largos quanto o mais largo, basta usar

IntrinsicWidth :

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('IntrinsicWidth')),
    body: Center(
      child: IntrinsicWidth(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            ElevatedButton(
              onPressed: () {},
              child: Text('Short'),
            ),
            ElevatedButton(
              onPressed: () {},
              child: Text('A bit Longer'),
            ),
            ElevatedButton(
              onPressed: () {},
              child: Text('The Longest text button'),
            ),
          ],
        ),
      ),
    ),
  );
}

Caso você tenha um problema semelhante, mas gostaria de ter todos os widgets tão altos quanto o mais alto, basta usar uma combinação dos widgets IntrinsicHeight e Row.

 

Stack

Perfeito para sobrepor Widgets uns sobre os outros

@override
Widget build(BuildContext context) {
  Widget main = Scaffold(
    appBar: AppBar(title: Text('Stack')),
  );

  return Stack(
    fit: StackFit.expand,
    children: <Widget>[
      main,
      Banner(
        message: "Top Start",
        location: BannerLocation.topStart,
      ),
      Banner(
        message: "Top End",
        location: BannerLocation.topEnd,
      ),
      Banner(
        message: "Bottom Start",
        location: BannerLocation.bottomStart,
      ),
      Banner(
        message: "Bottom End",
        location: BannerLocation.bottomEnd,
      ),
    ],
  );
}

Com seus próprios Widgets, você precisa colocá-los no Positioned Widget

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('Stack')),
    body: Stack(
      fit: StackFit.expand,
      children: <Widget>[
        Material(color: Colors.yellowAccent),
        Positioned(
          top: 0,
          left: 0,
          child: Icon(Icons.star, size: 50),
        ),
        Positioned(
          top: 340,
          left: 250,
          child: Icon(Icons.call, size: 50),
        ),
      ],
    ),
  );
}

Se você não quiser adivinhar os valores superior/inferior, pode usar o LayoutBuilder para recuperá-los

Widget build(BuildContext context) {
  const iconSize = 50;
  return Scaffold(
    appBar: AppBar(title: Text('Stack with LayoutBuilder')),
    body: LayoutBuilder(
      builder: (context, constraints) =>
        Stack(
          fit: StackFit.expand,
          children: <Widget>[
            Material(color: Colors.yellowAccent),
            Positioned(
              top: 0,
              child: Icon(Icons.star, size: iconSize),
            ),
            Positioned(
              top: constraints.maxHeight - iconSize,
              left: constraints.maxWidth - iconSize,
              child: Icon(Icons.call, size: iconSize),
            ),
          ],
        ),
    ),
  );
}

 

Expanded

Expanded funciona com layout Flex \ Flexbox e é ótimo para distribuir espaço entre vários itens.

Row(
  children: <Widget>[
    Expanded(
      child: Container(
        decoration: const BoxDecoration(color: Colors.red),
      ),
      flex: 3,
    ),
    Expanded(
      child: Container(
        decoration: const BoxDecoration(color: Colors.green),
      ),
      flex: 2,
    ),
    Expanded(
      child: Container(
        decoration: const BoxDecoration(color: Colors.blue),
      ),
      flex: 1,
    ),
  ],
),

 

ConstrainedBox

Por padrão, a maioria dos widgets ocupará o mínimo de espaço possível:

Card(child: const Text('Hello World!'), color: Colors.yellow)

ConstraindBox permite que um widget use o espaço restante conforme desejado.

ConstrainedBox( 
  constraints: BoxConstraints.expand(),
  child: const Card(
    child: const Text('Hello World!'), 
    color: Colors.yellow,
  ), 
),

Usando BoxConstraints, você especifica quanto espaço um widget pode ter — você especifica min/max de height/width.

BoxConstraints.expand usa uma quantidade infinita (todo o disponível) de espaço, a menos que seja especificado:

ConstrainedBox(
  constraints: BoxConstraints.expand(height: 300),
  child: const Card(
    child: const Text('Hello World!'), 
    color: Colors.yellow,
  ),
),

E é o mesmo que:

ConstrainedBox(
  constraints: BoxConstraints(
    minWidth: double.infinity,
    maxWidth: double.infinity,
    minHeight: 300,
    maxHeight: 300,
  ),
  child: const Card(
    child: const Text('Hello World!'), 
    color: Colors.yellow,
  ),
),

 

Align

Às vezes, você luta para definir nosso widget para um tamanho adequado – por exemplo, ele é constantemente esticado quando você não deseja:

O exemplo acima acontece por exemplo quando você tem uma Coluna com CrossAxisAlignment.stretch e deseja apenas que o botão não fique esticado:

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('Align: without Align')),
    body: Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: <Widget>[
        Align(
          child: ElevatedButton(
            onPressed: () {},
            child: const Text('Button'),
          ),
        ),
      ],
    ),
  );
}

Sempre que seu widget não atender às restrições que você tenta configurar, primeiro tente envolvê-lo com Align.

 

Container

Um dos Widgets mais usados — e por boas razões:

Container como ferramenta de layout

Quando você não especifica a altura e a largura do Container, ele corresponderá ao tamanho de seu filho

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('Container as a layout')),
    body: Container(
      color: Colors.yellowAccent,
      child: Text("Hi"),
    ),
  );
}

Se você quiser esticar o Container para corresponder a seu pai, use double.infinity para as propriedades de altura e largura

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('Container as a layout')),
    body: Container(
      height: double.infinity,
      width: double.infinity,
      color: Colors.yellowAccent,
      child: Text("Hi"),
    ),
  );
}

Container como decoration

Você pode usar a color property para afetar o Container’s background, mas a decoration e foregroundDecoration. (Com essas duas propriedades, você pode mudar completamente a aparência do Container, mas falarei sobre diferentes decorations mais tarde, pois é um tópico bastante extenso) a decorations é sempre colocada atrás do child, enquanto o foregroundDecoration fica em cima do child

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('Container.decoration')),
    body: Container(
      height: double.infinity,
      width: double.infinity,
      decoration: BoxDecoration(color: Colors.yellowAccent),
      child: Text("Hi"),
    ),
  );
}

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('Container.foregroundDecoration')),
    body: Container(
      height: double.infinity,
      width: double.infinity,
      decoration: BoxDecoration(color: Colors.yellowAccent),
      foregroundDecoration: BoxDecoration(
        color: Colors.red.withOpacity(0.5),
      ),
      child: Text("Hi"),
    ),
  );
}

Container como Transform

Se você não quiser usar o widget Transform para alterar seu layout, pode usar a propriedade transform diretamente do Container

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('Container.transform')),
    body: Container(
      height: 300,
      width: 300,
      transform: Matrix4.rotationZ(pi / 4),
      decoration: BoxDecoration(color: Colors.yellowAccent),
      child: Text(
        "Hi",
        textAlign: TextAlign.center,
      ),
    ),
  );
}

 

BoxDecoration

A decoration geralmente é usada em um widget Container para alterar a aparência do container.

imagem: DecorationImage

Coloca uma imagem como background:

Scaffold(
  appBar: AppBar(title: Text('image: DecorationImage')),
  body: Center(
    child: Container(
      height: 200,
      width: 200,
      decoration: BoxDecoration(
        color: Colors.yellow,
        image: DecorationImage(
          fit: BoxFit.fitWidth,
          image: NetworkImage(
            'https://flutter.io/images/catalog-widget-placeholder.png',
          ),
        ),
      ),
    ),
  ),
);

border: Border

Especifica como deve ser a aparência do border do Container.

Scaffold(
  appBar: AppBar(title: Text('border: Border')),
  body: Center(
    child: Container(
      height: 200,
      width: 200,
      decoration: BoxDecoration(
        color: Colors.yellow,
        border: Border.all(color: Colors.black, width: 3),
      ),
    ),
  ),
);

borderRadius: BorderRadius

Permite que os cantos da borda sejam arredondados.

borderRadius não funciona se o shape da decoração for BoxShape.circle

Scaffold(
  appBar: AppBar(title: Text('borderRadius: BorderRadius')),
  body: Center(
    child: Container(
      height: 200,
      width: 200,
      decoration: BoxDecoration(
        color: Colors.yellow,
        border: Border.all(color: Colors.black, width: 3),
        borderRadius: BorderRadius.all(Radius.circular(18)),
      ),
    ),
  ),
);

shape: BoxShape

A decoração da caixa pode ser um retângulo/quadrado ou uma elipse/círculo.

Para qualquer outra forma, você pode usar ShapeDecoration em vez de BoxDecoration

Scaffold(
  appBar: AppBar(title: Text('shape: BoxShape')),
  body: Center(
    child: Container(
      height: 200,
      width: 200,
      decoration: BoxDecoration(
        color: Colors.yellow,
        shape: BoxShape.circle,
      ),
    ),
  ),
);

boxShadow: List<BoxShadow>

Adiciona sombra ao Container.

Este parâmetro é uma lista porque você pode especificar várias sombras diferentes e mesclá-las.

Scaffold(
  appBar: AppBar(title: Text('boxShadow: List<BoxShadow>')),
  body: Center(
    child: Container(
      height: 200,
      width: 200,
      decoration: BoxDecoration(
        color: Colors.yellow,
        boxShadow: const [
          BoxShadow(blurRadius: 10),
        ],
      ),
    ),
  ),
);

gradient

Existem três tipos de gradientes: LinearGradient, RadialGradient e SweepGradient.

Scaffold(
  appBar: AppBar(title: Text('gradient: LinearGradient')),
  body: Center(
    child: Container(
      height: 200,
      width: 200,
      decoration: BoxDecoration(
        gradient: LinearGradient(
          colors: const [
            Colors.red,
            Colors.blue,
          ],
        ),
      ),
    ),
  ),
);

Scaffold(
  appBar: AppBar(title: Text('gradient: RadialGradient')),
  body: Center(
    child: Container(
      height: 200,
      width: 200,
      decoration: BoxDecoration(
        gradient: RadialGradient(
          colors: const [Colors.yellow, Colors.blue],
          stops: const [0.4, 1.0],
        ),
      ),
    ),
  ),
);

Scaffold(
  appBar: AppBar(title: Text('gradient: SweepGradient')),
  body: Center(
    child: Container(
      height: 200,
      width: 200,
      decoration: BoxDecoration(
        gradient: SweepGradient(
          colors: const [
            Colors.blue,
            Colors.green,
            Colors.yellow,
            Colors.red,
            Colors.blue,
          ],
          stops: const [0.0, 0.25, 0.5, 0.75, 1.0],
        ),
      ),
    ),
  ),
);

backgroundBlendMode

backgroundBlendMode é a propriedade mais complexa de BoxDecoration.
Ele é responsável por misturar cores/gradientes de BoxDecoration e qualquer BoxDecoration que esteja por cima.

Com backgroundBlendMode você pode usar uma longa lista de algoritmos especificados em BlendMode enum.

Primeiro, vamos definir BoxDecoration como foregroundDecoration que é desenhado em cima do filho do Container (enquanto a decoration é desenhada atrás do filho).

Scaffold(
  appBar: AppBar(title: Text('backgroundBlendMode')),
  body: Center(
    child: Container(
      height: 200,
      width: 200,
      foregroundDecoration: BoxDecoration(
        backgroundBlendMode: BlendMode.exclusion,
        gradient: LinearGradient(
          colors: const [
            Colors.red,
            Colors.blue,
          ],
        ),
      ),
      child: Image.network(
        'https://flutter.io/images/catalog-widget-placeholder.png',
      ),
    ),
  ),
);

backgroundBlendMode não afeta apenas o Container em que está localizado.

backgroundBlendMode altera a cor de qualquer coisa que esteja acima da árvore de widgets do Container.
O código a seguir tem um Container pai que desenha uma image e um Container filho que usa backgroundBlendMode. Ainda assim, você obteria o mesmo efeito de antes.

Scaffold(
  appBar: AppBar(title: Text('backgroundBlendMode')),
  body: Center(
    child: Container(
      decoration: BoxDecoration(
        image: DecorationImage(
          image: NetworkImage(
            'https://flutter.io/images/catalog-widget-placeholder.png',
          ),
        ),
      ),
      child: Container(
        height: 200,
        width: 200,
        foregroundDecoration: BoxDecoration(
          backgroundBlendMode: BlendMode.exclusion,
          gradient: LinearGradient(
            colors: const [
              Colors.red,
              Colors.blue,
            ],
          ),
        ),
      ),
    ),
  ),
);

 

Material

Border com cantos cortados

Scaffold(
  appBar: AppBar(title: Text('shape: BeveledRectangleBorder')),
  body: Center(
    child: Material(
      shape: const BeveledRectangleBorder(
        borderRadius: BorderRadius.all(Radius.circular(20)),
        side: BorderSide(color: Colors.black, width: 4),
      ),
      color: Colors.yellow,
      child: Container(
        height: 200,
        width: 200,
      ),
    ),
  ),
);

 

Slivers

SliverFillRemaining

Este Widget é insubstituível quando você deseja centralizar seu conteúdo mesmo que não haja espaço suficiente para ele.

Scaffold(
  appBar: AppBar(title: Text('SliverFillRemaining')),
  body: CustomScrollView(
    slivers: [
      SliverFillRemaining(
        hasScrollBody: false,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: const [
            FlutterLogo(size: 200),
            Text(
              'This is some longest text that should be centered'
              'together with the logo',
              textAlign: TextAlign.center,
            ),
          ],
        ),
      ),
    ],
  ),
);

Caso não haja espaço suficiente para o conteúdo centralizado, SliverFillRemaining se tornará rolável:

Se não fosse por SliverFillRemaining , o conteúdo transbordaria assim:

 

Preenchendo o espaço restante

Além de ser útil para centralizar seu conteúdo, SliverFillRemaining preencherá o espaço livre da viewport restante. Para fazer isso, este widget deve ser colocado no CustomScrollView e precisa ser a última sliver

Caso não haja espaço suficiente, o widget se torna scrollable:

Scaffold(
  appBar: AppBar(title: Text('SliverFillRemaining')),
  body: CustomScrollView(
    slivers: [
      SliverList(
        delegate: SliverChildListDelegate(const [
          ListTile(title: Text('First item')),
          ListTile(title: Text('Second item')),
          ListTile(title: Text('Third item')),
          ListTile(title: Text('Fourth item')),
        ]),
      ),
      SliverFillRemaining(
        hasScrollBody: false,
        child: Container(
          color: Colors.yellowAccent,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: const [
              FlutterLogo(size: 200),
              Text(
                'This is some longest text that should be centered'
                'together with the logo',
                textAlign: TextAlign.center,
              ),
            ],
          ),
        ),
      ),
    ],
  ),
);

 

SizedBox

É um dos Widgets mais simples, mas mais úteis

SizedBox como ConstraindBox

SizedBox pode funcionar de maneira semelhante a ConstraindBox

SizedBox.expand(
  child: Card(
    child: Text('Hello World!'),
    color: Colors.yellowAccent,
  ),
),

 

SizedBox como padding

Quando precisar adicionar padding ou margem, você pode escolher os widgets Padding ou Container. Mas eles podem ser mais detalhados e menos legíveis do que adicionar um Sizedbox

Column(
  children: <Widget>[
    Icon(Icons.star, size: 50),
    const SizedBox(height: 100),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),

SizedBox como um objeto invisível

Muitas vezes você gostaria de ocultar/mostrar um widget dependendo de um bool

Widget build(BuildContext context) {
  bool isVisible = ...
  return Scaffold(
    appBar: AppBar(
      title: Text('isVisible = $isVisible'),
    ),
    body: isVisible 
      ? Icon(Icons.star, size: 150) 
      : const SizedBox(),
  );
}

Como SizedBox tem um construtor const, usar const SizedBox() é muito barato**.

** Uma solução mais barata seria usar o widget Opacity e alterar o valor da opacidade para 0,0 . A desvantagem desta solução é que o widget dado seria apenas invisível, ainda ocuparia o espaço.

 

SafeArea

Em diferentes plataformas, existem áreas especiais como a barra de status no Android ou o Notch no iPhone X que podemos evitar desenhar.

A solução para este problema é o widget SafeArea (exemplo sem/com SafeArea)

Widget build(BuildContext context) {
  return Material(
    color: Colors.blue,
    child: SafeArea(
      child: SizedBox.expand(
        child: Card(color: Colors.yellowAccent),
      ),
    ),
  );
}