天天看點

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏

#頭條創作挑戰賽#

本文同步本人掘金平台的原創翻譯:https://juejin.cn/post/7108530957976043528

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏

banner

你是否需要了解 Flutter 布局的案例?

這裡我将展示我在使用 Flutter 布局的代碼片段。我将通過精美的代碼片段結合可視化的圖形來舉例。

本文注重 Flutter 部件中比較有用的一些來展示,而不是走馬觀花展示一大堆的部件内容。

Row and Column

行(Row)和列(Column)的布局

MainAxisAlignment

Row Column
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Row /*or Column*/(
  mainAxisAlignment: MainAxisAlignment.start,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
複制代碼           
Row Column
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Row /*or Column*/(
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
複制代碼           
Row Column
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Row /*or Column*/(
  mainAxisAlignment: MainAxisAlignment.end,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
複制代碼           
Row Column
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Row /*or Column*/(
  mainAxisAlignment: MainAxisAlignment.spaceBetween,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
複制代碼           
Row Column
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Row /*or Column*/(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
複制代碼           
Row Column
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Row /*or Column*/(
  mainAxisAlignment: MainAxisAlignment.spaceAround,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
複制代碼           

CrossAxisAlignment

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏

如果你需要文本是針對基線對齊,那麼你應該使用 CrossAxisAlignment.baseline。

Row(
  crossAxisAlignment: CrossAxisAlignment.baseline,
  textBaseline: TextBaseline.alphabetic,
  children: <Widget>[
    Text(
      'Baseline',
      style: Theme.of(context).textTheme.headline2,
    ),
    Text(
      'Baseline',
      style: Theme.of(context).textTheme.bodyText2,
    ),
  ],
),
複制代碼           
Row Column
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Row /*or Column*/(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 200),
    Icon(Icons.star, size: 50),
  ],
),
複制代碼           
Row Column
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Row /*or Column*/(
  crossAxisAlignment: CrossAxisAlignment.center,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 200),
    Icon(Icons.star, size: 50),
  ],
),
複制代碼           
Row Column
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Row /*or Column*/(
  crossAxisAlignment: CrossAxisAlignment.end,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 200),
    Icon(Icons.star, size: 50),
  ],
),
複制代碼           
Row Column
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Row /*or Column*/(
  crossAxisAlignment: CrossAxisAlignment.stretch,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 200),
    Icon(Icons.star, size: 50),
  ],
),
複制代碼           

MainAxisSize

Row Column
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Row /*or Column*/(
  mainAxisSize: MainAxisSize.max,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
複制代碼           
Row Column
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Row /*or Column*/(
  mainAxisSize: MainAxisSize.min,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
複制代碼           

IntrinsicWidth and IntrinsicHeight

在行列布局中,如何使得所有的部件跟寬度/高度最大的部件同寬/同高呢?如下:

我們假有下面的布局:

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('IntrinsicWidth')),
    body: Center(
      child: Column(
        children: <Widget>[
          RaisedButton(
            onPressed: () {},
            child: Text('Short'),
          ),
          RaisedButton(
            onPressed: () {},
            child: Text('A bit Longer'),
          ),
          RaisedButton(
            onPressed: () {},
            child: Text('The Longest text button'),
          ),
        ],
      ),
    ),
  );
}
複制代碼           

那麼,你想所有的按鈕的寬度都跟最寬的按鈕那麼寬,那就使用 IntrinsicWidth:

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

同理,如果你想所有的部件的高度跟最高的部件一樣高,你需要結合 IntrinsicHeight 和 Row 來實作。

Stack

Stack 很适合小部件互相疊加。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
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,
      ),
    ],
  );
}
複制代碼           

使用自己的部件,你需要将它們放在 Positioned 部件中。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
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),
        ),
      ],
    ),
  );
}
複制代碼           

如果你不想猜測頂部/底部的值,你可以使用 LayoutBuilder 部件來檢索它們的值。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
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 配合 Flex\Flexbox 布局實作,它對于多項目配置設定空間很棒。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
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

預設的,很多部件多盡量使用小空間,比如:

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Card(
  child: const Text('Hello World!'),
  color: Colors.yellow,
),
複制代碼           

ConstrainedBox 允許小部件根據需要使用剩下的空間。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
ConstrainedBox(
  constraints: BoxConstraints.expand(),
  child: const Card(
    child: const Text('Hello World!'),
    color: Colors.yellow,
  ),
),
複制代碼           

使用 BoxConstraints,你可以指定一個小部件可以有多少空間,你可以指定高度/寬度的最小/最大值。

除非指定值,否則 BoxConstraints.expand 使用無限的空間量(也就是使用剩下的所有空間):

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
ConstrainedBox(
  constraints: BoxConstraints.expand(height: 300),
  child: const Card(
    child: const Text('Hello World!'),
    color: Colors.yellow,
  ),
),
複制代碼           

上面的寫法等同下面的寫法:

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

有時候,我們很難設定我們的小部件到正确的大小 -- 比如,它們自由伸展,但是這不是你想要的。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏

當你在 Column 中使用 CrossAxisAlignment.stretch 的時候,上面的現象就會發生,而你想要的是這個按鈕不伸展。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Column(
  crossAxisAlignment: CrossAxisAlignment.stretch,
  children: <Widget>[
    Align(
      child: RaisedButton(
        onPressed: () {},
        child: const Text('Button'),
      ),
    ),
  ],
),
複制代碼           

當你的小部件并不受限你設定的限制時,那麼你可以嘗試使用 Align 部件包裹它。

Container

Container 是最常用的部件之一 -- 有如下的好處:

Container as a layout tool

當你沒有指定 Container 的高度 height 或者寬度 width 的時候,它會自動适配 child 子部件的大小。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('Container as a layout')),
    body: Container(
      color: Colors.yellowAccent,
      child: Text("Hi"),
    ),
  );
}
複制代碼           

如果你想伸展 Container 來适配它的父部件,請為屬性高度 height 或寬度 width 設定值 double.infinity。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
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 as decoration

你可以使用 Container 的 color 屬性來更改其背景顔色,但是你也可以使用 decoration 和 foregroundDecoration 來更改。(使用這兩個屬性,你完全可以更改 Container 的樣子,這個我們遲點說)。

decoration 總是在 child 屬性的後面,而 foregroundDecoration 總是在 child 屬性的後面。(這也不一定)

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
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"),
    ),
  );
}
複制代碼           
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
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 as Transform

如果你不想使用 Transform 部件來更改布局,你可以直接使用 Container 中的 transform 屬性。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
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

decoration 通常用于更改 Container 部件的外觀。

image: DecorationImage

圖檔作為背景:

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Widget build(BuildContext context) {
  return 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.dev/images/catalog-widget-placeholder.png', // 位址已經無效
            ),
          ),
        ),
      ),
    ),
  );
}
複制代碼           

border: Border

指定 Container 的邊框看起來該怎樣。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Widget build(BuildContext context) {
  return 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

使得邊框角變圓。

如果裝飾中 shape 屬性的值是 BoxShape.circle,那麼 borderRadius 不會起作用。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Widget build(BuildContext context) {
  return 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

BoxDecoration 可以是矩形/正方形或者橢圓/圓形。

對于其他形狀,你可以使用 ShapeDecoration 代替 BoxDecoration。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Widget build(BuildContext context) {
  return 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

為 Container 添加陰影。

這個參數是一個清單,你可以指定多個不同的陰影并将它們合并在一起。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Widget build(BuildContext context) {
  return 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

有三種類型的漸變:LinearGradient,RadialGradient 和 SweepGradient。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Widget build(BuildContext context) {
  return 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,
            ],
          ),
        ),
      ),
    ),
  );
}
複制代碼           
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Widget build(BuildContext context) {
  return 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],
          ),
        ),
      ),
    ),
  );
}
複制代碼           
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Widget build(BuildContext context) {
  return 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 是 BoxDecoration 中最複雜的屬性之一。

它負責将 BoxDecoration 中顔色/漸變,以及 BoxDecoration 上的任何内容混合一起。

使用 backgroundBlendMode, 你可以使用 BlendMode 枚舉中指定的一長串算法。

首先,讓我們将 BoxDecoration 設定為 foregroundDecoration,它被繪制在 Container 子部件之上(而 decoration 會繪制在子部件之後)。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Widget build(BuildContext context) {
  return 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', // 圖檔 404
        ),
      ),
    ),
  );
}
複制代碼           

backgroundBlendMode 不僅僅影響它所在的 Container。

backgroundBlendMode 改變其所在 Container 及其一下部件樹的内容的顔色。

下面的代碼又一個父部件 Container 來繪制一個 image,然後有一個子部件 Container 來使用 backgroundBlendMode,但是你還是擷取到和之前的效果。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('backgroundBlendMode')),
    body: Center(
      child: Container(
        decoration: BoxDecoration(
          image: DecorationImage(
            image: NetworkImage(
              'https://flutter.io/images/catalog-widget-placeholder.png', // 404
            ),
          ),
        ),
        child: Container(
          height: 200,
          width: 200,
          foregroundDecoration: BoxDecoration(
            backgroundBlendMode: BlendMode.exclusion,
            gradient: LinearGradient(
              colors: const [
                Colors.red,
                Colors.blue,
              ],
            ),
          ),
        ),
      ),
    ),
  );
}
複制代碼           

Material

有切角的邊框。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Widget build(BuildContext context) {
  return 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

即便沒有足夠的空間,當你想要将内容居中,這個部件也是不可替代的。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Widget build(BuildContext context) {
  return 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,
              ),
            ],
          ),
        ),
      ],
    ),
  );
}
複制代碼           

如果居中的内容沒有足夠的空間,SliverFillRemaining 将變為可滾動。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏

如果沒使用 SliverFillRemaining,内容将會像下面這樣溢出:

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏

Filling the remaining space

除了對内容居中有用之外,SliverFillRemaining 還會填充剩餘視口的可用空間。為此,此部件必須放置在 CustomScrollView 中,并且必須是最後一個 sliver。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏

如果沒有足夠的空間,部件将變為可滾動。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Widget build(BuildContext context) {
  return 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

SizedBox 是最簡單但是最常用的小部件之一。

SizedBox as ConstrainedBox

SizedBox 工作方式跟 ConstrainedBox 有些類似。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
SizedBox.expand(
  child: Card(
    child: Text('Hello World!'),
    color: Colors.yellowAccent,
  ),
),
複制代碼           

SizedBox as padding

當需要添加内邊距和外邊距的時候,你可以選擇 Padding 或 Container 小部件。但是,它們可以比添加 Sizedbox 更冗長且可讀性更低。

Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Column(
  children: <Widget>[
    Icon(Icons.star, size: 50),
    const SizedBox(height: 100),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
複制代碼           

SizedBox as an Invisible Object

很多時候,你想通過設定一個 bool 值來隐藏/展示小部件。

Show Hide
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Widget build(BuildContext context) {
  bool isVisible = true; // true or false
  return Scaffold(
    appBar: AppBar(
      title: Text('isVisible = $isVisible'),
    ),
    body: isVisible 
      ? Icon(Icons.star, size: 150) 
      : const SizedBox(),
  );
}
複制代碼           

因為 SizedBox 有一個 const 構造函數,是以使用 const SizeBox() 真的很便宜。一種更便宜的解決方案是使用 Opacity 小部件,将其 opacity 值更改為 0.0。這種解決方案的缺點是給定的小部件隻是不可見,但是還是占用空間。

SafeArea

在不同的平台,有些特定的區域,比如安卓的狀态欄或者 iPhone X 的劉海區塊,我們不應該在其下面繪制内容。

解決方案是使用 SafeArea 小部件。(下面截圖是沒使用/使用 SafeArea)

without with
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Flutter 布局備忘錄 -- 多圖警告,幹貨建議收藏
Widget build(BuildContext context) {
  return Material(
    color: Colors.blue,
    child: SafeArea(
      child: SizedBox.expand(
        child: Card(color: Colors.yellowAccent),
      ),
    ),
  );
}
複制代碼           

代碼已經驗證,需要留意 RaisedButton 已經被 ElevatedButton 替代,在現實使用中需要留意。本文重點是其布局思路和技巧。