[譯] Flutter 佈局備忘錄

Yuqi發表於2019-06-10

[譯] Flutter 佈局備忘錄

你需要了解 Flutter 的簡單佈局模版嗎? 現在我將展示給你我總結的一系列 Flutter 佈局程式碼片段。我會盡量保證程式碼簡短易懂,並且會給出效果圖。 但是我們仍舊需要循序漸進 —— 模版目錄將會隨之逐步深入。我將會將更多的篇幅集中於 Flutter 部件的應用,而不是單純陳列元件(Flutter Gallery 在這一點做的很好!) 如果你對於 Flutter 佈局還有其他疑問,或者想要分享你的程式碼,請留言給我!


目錄

  • Row 和 Column
  • IntrinsicWidth 和 IntrinsicHeight
  • Stack
  • Expanded
  • ConstrainedBox
  • Container
    • 裝飾(decoration):BoxDecoration
    • 圖片(image):DecorationImage
    • 邊框(border):Border
    • 邊框半徑(borderRadius):BorderRadius
    • 形狀(shape):BoxShape
    • 陰影(boxShadow):List<BoxShadow>
    • 漸變(gradient):RadialGradient
    • 背景混合模式(backgroundBlendMode):BlendMode
  • SizedBox
  • SafeArea

Row 和 Column

MainAxisAlignment

[譯] Flutter 佈局備忘錄

[譯] Flutter 佈局備忘錄

Row /*或 Column*/( 
  mainAxisAlignment: MainAxisAlignment.start,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
複製程式碼

[譯] Flutter 佈局備忘錄

[譯] Flutter 佈局備忘錄

Row /*或 Column*/( 
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
複製程式碼

[譯] Flutter 佈局備忘錄

[譯] Flutter 佈局備忘錄

Row /*或 Column*/( 
  mainAxisAlignment: MainAxisAlignment.end,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
複製程式碼

[譯] Flutter 佈局備忘錄

[譯] Flutter 佈局備忘錄

Row /*或 Column*/( 
  mainAxisAlignment: MainAxisAlignment.spaceBetween,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
複製程式碼

[譯] Flutter 佈局備忘錄

[譯] Flutter 佈局備忘錄

Row /*或 Column*/( 
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
複製程式碼

[譯] Flutter 佈局備忘錄

[譯] Flutter 佈局備忘錄

Row /*或 Column*/( 
  mainAxisAlignment: MainAxisAlignment.spaceAround,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
複製程式碼

[譯] Flutter 佈局備忘錄

如果你想要不同字元的基線對齊,你應該使用 CrossAxisAlignment.baseline

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

[譯] Flutter 佈局備忘錄

[譯] Flutter 佈局備忘錄

Row /*或 Column*/( 
  crossAxisAlignment: CrossAxisAlignment.start,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 200),
    Icon(Icons.star, size: 50),
  ],
),
複製程式碼

[譯] Flutter 佈局備忘錄

[譯] Flutter 佈局備忘錄

Row /*或 Column*/( 
  crossAxisAlignment: CrossAxisAlignment.center,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 200),
    Icon(Icons.star, size: 50),
  ],
),
複製程式碼

[譯] Flutter 佈局備忘錄

[譯] Flutter 佈局備忘錄

Row /*或 Column*/( 
  crossAxisAlignment: CrossAxisAlignment.end,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 200),
    Icon(Icons.star, size: 50),
  ],
),
複製程式碼

[譯] Flutter 佈局備忘錄

[譯] Flutter 佈局備忘錄

Row /*或 Column*/( 
  crossAxisAlignment: CrossAxisAlignment.stretch,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 200),
    Icon(Icons.star, size: 50),
  ],
),
複製程式碼

MainAxisSize

[譯] Flutter 佈局備忘錄

[譯] Flutter 佈局備忘錄

Row /*或 Column*/( 
  mainAxisSize: MainAxisSize.max,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
複製程式碼

[譯] Flutter 佈局備忘錄

[譯] Flutter 佈局備忘錄

Row /*或 Column*/( 
  mainAxisSize: MainAxisSize.min,
  children: <Widget>[
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
    Icon(Icons.star, size: 50),
  ],
),
複製程式碼

IntrinsicWidth 和 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

[譯] Flutter 佈局備忘錄

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'),
            ),
          ],
        ),
      ),
    ),
  );
}
複製程式碼

如果你需要的是讓所有部件和最高的部件,可以結合使用 IntrinsicHeightRow 部件。


Stack

非常適用於將部件疊加在一起

[譯] Flutter 佈局備忘錄

@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,
      ),
    ],
  );
}
複製程式碼

如果想使用自己的部件,需要將它們放置在 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),
        ),
      ],
    ),
  );
}
複製程式碼

如果你不想去猜測 top 或 bottom 的值,你可以使用 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 指定部件可以使用多大的空間 —— 通過指定 height/widthmin/max 屬性。

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,
  ),
),
複製程式碼

Container

最常用的部件之一 —— 並且它之所以這麼常用是有原因的:

用於佈局工具的 Container

如果你沒有指定 Containerheightwidth,它將和 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 擴大到和它的父級元素相等,對 heightwidth 屬性使用 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 的裝飾

你可以使用 color 屬性來改變 Container 的背景色,但是 decorationforegroundDecoration 則可以做更多。(使用這兩個屬性,你可以徹底改變 Container 的外觀,這部分我將在後續討論,因為這部分內容很多) decoration 總會放置在 child 後面,而 foregroundDecoration 則在 child 的上面。

decoration

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"),
    ),
  );
}
複製程式碼

decoration and foregroundDecoration

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 的變換

如果你不想使用 Transform 部件來改變你的佈局,你可以使用 Containertransform 屬性

[譯] 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

裝飾效果通常用於容器元件,來改變元件的外觀。

圖片(image):DecorationImage

將圖片作為背景:

[譯] Flutter 佈局備忘錄

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

指定容器的邊框樣式。

[譯] Flutter 佈局備忘錄

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

讓邊框可以是圓角。

如果裝飾的 shapeBoxShape.circle,那麼 borderRadius 將無效

[譯] Flutter 佈局備忘錄

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

盒子的形狀可以是長方形、正方形、橢圓或者圓形。

對於其他任意形狀,你應該使用 ShapeDecoration 而不是 BoxDecoration

[譯] Flutter 佈局備忘錄

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>

可以給容器新增陰影。

這個引數是一個列表,這樣你就可以定義多種不同的陰影,然後將它們組合在一起。

[譯] Flutter 佈局備忘錄

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)

有三種型別的漸變:LinearGradientRadialGradientSweepGradient

`LinearGradient`

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,
          ],
        ),
      ),
    ),
  ),
);
複製程式碼

RadialGradient

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],
        ),
      ),
    ),
  ),
);
複製程式碼

SweepGradient

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)

backgroundBlendModeBoxDecoration 中最複雜的屬性。 它可以混合 BoxDecoration 的顏色和漸變,並且無論 BoxDecoration 在何種元素之上。

有了 backgroundBlendMode,你可以使用 BlendMode 列舉型別中的一長串演算法。

首先,配置 BoxDecorationforegroundDecoration,它被渲染於 Container 子元素的上方(而 decoration 被渲染於子元素的後面)。

[譯] Flutter 佈局備忘錄

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 不僅影響它所在的 Container

backgroundBlendMode 能改變從 Container 的部件樹中任意部件的顏色。 下面這段程式碼中,有一個作為父級元素的 Container,它渲染了一張圖片 image 和一個使用了 backgroundBlendMode 的子元素 Container。你仍舊會得到和前一段程式碼相同的效果。

[譯] Flutter 佈局備忘錄

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,
            ],
          ),
        ),
      ),
    ),
  ),
);
複製程式碼

SizedBox

這是最簡單但是最有用的部件

用作 ConstrainedBox 的 SizedBox

SizedBox 可以實現和 ConstrainedBox 相似的效果

[譯] Flutter 佈局備忘錄

SizedBox.expand(
  child: Card(
    child: Text('Hello World!'),
    color: Colors.yellowAccent,
  ),
),
複製程式碼

用作內邊距的 SizedBox

如果你需要新增內邊距或者外邊距,你可以選擇 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

很多時候你希望通過一個布林值(bool)來控制元件的顯示和隱藏

[譯] Flutter 佈局備忘錄

[譯] Flutter 佈局備忘錄

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

由於 SizedBox 有一個 const 建構函式,使用 const SizedBox() 就變得非常簡單。

更簡單的解決方案是使用 Opacity 部件,然後將 opacity 的值改成 0.0。這個方案的缺點是雖然元件不可見,但是它依舊佔據空間。


SafeArea

在不同的平臺上,有很多特殊的位置,比如 Android 系統的狀態列,或者 iPhone X 的“齊劉海”,我們應該避免在這些位置放置元素。

解決方案就是使用 SafeArea 部件(下面的例子分別是使用和沒使用 SafeArea 的效果)

[譯] Flutter 佈局備忘錄

[譯] Flutter 佈局備忘錄

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

更多內容敬請期待

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章