從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎

yuxiyu發表於2018-07-24

我的上一篇部落格中記錄了Flutter基礎和一些常用的Widgets,這一篇中主要記錄Flutter常用的一些佈局,希望自己在記錄的同時能溫故知新,同時給初學者一些幫助。

索引 文章
1 從0開始寫一個基於Flutter的開源中國客戶端(1)
Flutter簡介及開發環境搭建 | 掘金技術徵文
2 從0開始寫一個基於Flutter的開源中國客戶端(2)
Dart語法基礎
3 從0開始寫一個基於Flutter的開源中國客戶端(3)
初識Flutter & 常用的Widgets
?4 從0開始寫一個基於Flutter的開源中國客戶端(4)
Flutter佈局基礎
5 從0開始寫一個基於Flutter的開源中國客戶端(5)
App整體佈局框架搭建
6 從0開始寫一個基於Flutter的開源中國客戶端(6)
各個靜態頁面的實現
7 從0開始寫一個基於Flutter的開源中國客戶端(7)
App網路請求和資料儲存
8 從0開始寫一個基於Flutter的開源中國客戶端(8)
外掛的使用

Flutter佈局容器

在Android開發中,我們使用xml檔案寫佈局,有諸如LinearLayoutRelativeLayoutConstraintLayout等佈局方式,在ReactNative或WEEX開發中,我們使用的佈局方式都是基於前端的flex佈局,無論是Android還是RN或者WEEX,他們的佈局特點都是程式碼和佈局是分開的,而在Flutter開發中,佈局比較另類一點,因為邏輯程式碼和佈局程式碼都寫在一起了,都是使用Dart來寫。

說到佈局就不得不說到容器,不論使用原生或者RN、WEEX這類跨平臺移動開發方式,佈局都會涉及到容器,比如原生Android開發中,LinearLayout是個佈局,同時是一個可以包含多個子元件的容器,在RN開發中,<View>是一個元件,同時也是可以包含多個子元件的容器,在WEEX開發中<div>也是一個可以包含多個子元件的容器。

Flutter中的佈局容器主要分為兩類:只能包含一個子Widget的佈局容器和可以包含多個子Widget的容器,下面分別說明其用法。

包含單個子Widget的佈局容器

Center元件

Center元件中的子元件會居中顯示。Center元件會盡可能的大,如果你不給它設定任何約束。下面是Center元件的使用方法:

import 'package:flutter/material.dart';

main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: "Test",
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text("Test")
        ),
        body: new Center(
          child: new Text("hello world")
        ),
      ),
    );
  }
}
複製程式碼

Container元件

Container是使用非常多的一個佈局容器,關於Container容器的顯示規則,有如下幾條:

  1. 如果Container中沒有子元件,則Container會盡可能的大
  2. 如果Container中有子元件,則Container會適應子元件的大小
  3. 如果給Container設定了大小,則Container按照設定的大小顯示
  4. Container的顯示規則除了跟自身約束和子元件有關,跟它的父元件也有關

下面的程式碼展示了Container的用法:

import 'package:flutter/material.dart';

main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: "Test",
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text("Test")
        ),
        body: new Container(
          width: 100.0,
          height: 100.0,
          color: Colors.red,
          child: new Text("Flutter!"),
        )
      ),
    );
  }
}
複製程式碼

如果我們分別註釋掉上面Container程式碼中的width/height、child屬性,顯示出的介面就會有所不同:

從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎 從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎 從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎

Container還可以設定內邊距和外邊距,如下程式碼所示:

body: new Container(
  // 設定外邊距都為20.0
  margin: const EdgeInsets.all(20.0),
  // 設定內邊距,4個邊分別設定
  padding: const EdgeInsets.fromLTRB(10.0, 20.0, 30.0, 40.0),
  width: 100.0,
  height: 100.0,
  color: Colors.red,
  child: new Text("Flutter!"),
)
複製程式碼

Padding元件

Padding元件專門用於給它的子元件設定內邊距,用法比較簡單:

new Padding(
  padding: new EdgeInsets.all(8.0),
  child: const Card(child: const Text('Hello World!')),
)
複製程式碼

Align元件

Align元件用於將它的子元件放置到確定的位置,比如下面的程式碼展示了將Text元件放置到100*100的容器的右下角:

new Container(
  width: 100.0,
  height: 100.0,
  color: Colors.red,
  child: new Align(
    child: new Text("hello"),
    alignment: Alignment.bottomRight,
  ),
)
複製程式碼

Alignment類中有如下一些靜態常量:

  /// The top left corner.
  static const Alignment topLeft = const Alignment(-1.0, -1.0);

  /// The center point along the top edge.
  static const Alignment topCenter = const Alignment(0.0, -1.0);

  /// The top right corner.
  static const Alignment topRight = const Alignment(1.0, -1.0);

  /// The center point along the left edge.
  static const Alignment centerLeft = const Alignment(-1.0, 0.0);

  /// The center point, both horizontally and vertically.
  static const Alignment center = const Alignment(0.0, 0.0);

  /// The center point along the right edge.
  static const Alignment centerRight = const Alignment(1.0, 0.0);

  /// The bottom left corner.
  static const Alignment bottomLeft = const Alignment(-1.0, 1.0);

  /// The center point along the bottom edge.
  static const Alignment bottomCenter = const Alignment(0.0, 1.0);

  /// The bottom right corner.
  static const Alignment bottomRight = const Alignment(1.0, 1.0);
複製程式碼

FittedBox元件

FittedBox元件根據fit屬性來確定子元件的位置,fit屬性是一個BoxFit型別的值,BoxFit是個列舉類,取值有如下幾種:

enum BoxFit {
  fill,
  contain,
  cover,
  fitWidth,
  fitHeight,
  none,
  scaleDown,
}
複製程式碼

在我的上一篇博文中,在說到Image元件時,已有對於這幾種BoxFit型別的介紹,這裡再用一段程式碼和截圖來直觀說明上面幾種BoxFit,在下面的程式碼中,我們在大小為200*100的Container中放置一個Text,使用FittedBox來控制Text的不同顯示狀態:

new Container(
  width: 200.0,
  height: 100.0,
  color: Colors.red,
  child: new FittedBox(
    child: new Text("hello world"),
    fit: BoxFit.fill,
  )
)
複製程式碼

當fit取不同值時,上面的程式碼執行結果如下圖所示:

從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎 從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎 從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎
從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎 從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎 從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎

AspectRatio元件

AspectRatio元件用於讓它的子元件按一定的比例顯示,下面是示例程式碼:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: "Test",
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text("Test")
        ),
        body: new AspectRatio(
          // Container元件按16:9(width / height)顯示
          aspectRatio: 16.0 / 9.0,
          child: new Container(
            color: Colors.red,
          ),
        )
      ),
    );
  }
}
複製程式碼

如果將aspectRatio設定為1.0,則Container顯示為正方形。(注意,Dart中/代表除法運算,不是取整運算,使用~/做取整運算)

ConstrainedBox元件

ConstrainedBox元件用於給它的子元件強制加上一些約束,比如下面的程式碼:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: "Test",
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text("Test")
        ),
        body: new ConstrainedBox(
          constraints: const BoxConstraints.expand(width: 50.0, height: 50.0),
          child: new Container(
            color: Colors.red,
            width: 200.0,
            height: 200.0,
          )
        )
      ),
    );
  }
}
複製程式碼

在上面的程式碼中,我們給Container設定了長寬都為200,但是Container被ConstrainedBox元件包裹了,而且ConstrainedBox設定了約束constraints: const BoxConstraints.expand(width: 50.0, height: 50.0),由於ConstrainedBox的約束是強制性的,所以最後Container顯示出的大小是50而不是200,如下圖所示:

從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎

IntrinsicWidth & IntrinsicHeight

這兩個元件的作用是將他們的子元件調整到元件本身的寬度/高度。

這個類是非常有用的,例如,當寬度/高度沒有任何限制時,你會希望子元件按更合理的寬度/高度顯示而不是無限的擴充套件。

LimitedBox元件

LimitedBox是一個當其自身不受約束時才限制其大小的容器。

如果這個元件的最大寬度是沒有約束,那麼它的寬度就限制在maxWidth。類似地,如果這個元件的最大高度沒有約束,那麼它的高度就限制在maxHeight

Offstage元件

Offstage元件用於顯示或隱藏它的子元件,如下程式碼所示:

new Offstage(
  offstage: false, // true: 隱藏, false: 顯示
  child: new Text("hello world"),
)
複製程式碼

OverflowBox & SizedOverflowBox

OverflowBox元件它給它的子元件帶來不同的約束,而不是從它的父元件中得到,可能允許子元件溢位到父元件中。

SizedOverflowBox元件是一個指定大小的元件,它的約束會傳遞給子元件,子元件可能溢位。

SizedBox元件

SizedBox是一個指定了大小的容器。

如果指定了SizedBox的大小,則子元件會使用SizedBox的大小,如果沒有指定SizedBox的大小,則SizedBox會使用子元件的大小。如果SizedBox沒有子元件,SizedBox會按它自己的大小來顯示,將nulls當作0。

new SizedBox(
  // 如果指定width和height,則Container按照指定的大小顯示,而不是Container自己的大小,如果沒有指定width和height,則SizedBox按照Container的大小顯示
  width: 50.0,
  height: 50.0,
  child: new Container(
    color: Colors.red,
    width: 300.0,
    height: 300.0,
  ),
)
複製程式碼

Transform元件

Transform用於在繪製子元件前對子元件進行某些變換操作,比如平移、旋轉、縮放等。

示例程式碼如下:

new Container(
  color: Colors.black,
  child: new Transform(
    alignment: Alignment.topRight,
    // 需要導包:import 'dart:math' as math;
    transform: new Matrix4.skewY(0.3)..rotateZ(-math.pi / 12.0),
    child: new Container(
      padding: const EdgeInsets.all(8.0),
      color: const Color(0xFFE8581C),
      child: const Text('Apartment for rent!'),
    ),
  ),
)
複製程式碼

執行效果如下圖:

從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎

包含多個子Widget的佈局容器

Row元件

Row元件字面理解就是代表一行,在一行中可以放入多個子元件。

下面是示例程式碼:

import 'package:flutter/material.dart';

main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: "Test",
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text("Test")
        ),
        body: new Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text("hello"),
            new Container(
              width: 50.0,
              height: 50.0,
              color: Colors.red,
            ),
            new Text("world")
          ],
        )
      ),
    );
  }
}
複製程式碼

在模擬器上執行的效果如下圖:

從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎

Row元件的構造方法中,children引數是一個陣列,表示可以有多個子元件,mainAxisAlignment表示Row中的子元件在主軸(Row元件主軸表示水平方向,交叉軸表示垂直方向,Column元件主軸表示垂直方向,交叉軸表示水平方向)上的對齊方式,可以有如下幾個取值:

  • MainAxisAlignment.start
  • MainAxisAlignment.center
  • MainAxisAlignment.end
  • MainAxisAlignment.spaceBetween
  • MainAxisAlignment.spaceAround
  • MainAxisAlignment.spaceEvenly

關於上面幾個取值,用如下幾個圖來說明:

從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎 從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎 從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎 從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎 從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎 從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎

Column元件

Column元件表示一列,可以在一列中放入多個元件,如下程式碼所示:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: "Test",
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text("Test")
        ),
        body: new Column(
          children: <Widget>[
            new Text("hello"),
            new Text("world"),
            new Text("nihao~")
          ],
        )
      ),
    );
  }
}
複製程式碼

Column和Row元件一樣,可以通過MainAxisAlignment或者CrossAxisAlignment來設定主軸和交叉軸的對齊方式,這裡不再贅述。

Stack元件

Stack元件類似於Android中的FrameLayout,其中的子元件是一層層堆起來的,並不像Row或者Column中的子元件,按水平或垂直方向排列,下面用程式碼說明:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: "Test",
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text("Test")
        ),
        body: new Stack(
          children: <Widget>[
            new Container(
              width: 100.0,
              height: 100.0,
              color: Colors.red,
            ),
            new Container(
              width: 30.0,
              height: 30.0,
              color: Colors.green,
            )
          ],
        )
      ),
    );
  }
}
複製程式碼

在上面的Stack元件中,放入了兩個Container,其中第一個Container是100x100大小,第二個Container是30x30大小,在模擬器上執行效果如下圖:

從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎

IndexedStack元件

IndexedStack用於根據索引來顯示子元件,index為0則顯示第一個子元件,index為1則顯示第二個子元件,以此類推,下面用程式碼說明:

new IndexedStack(
  index: 1,
  children: <Widget>[
    new Container(
      width: 100.0,
      height: 100.0,
      color: Colors.red,
      child: new Center(
        child: new Text("index: 0", style: new TextStyle(fontSize: 20.0),),
      ),
    ),
    new Container(
      width: 100.0,
      height: 100.0,
      color: Colors.green,
      child: new Center(
        child: new Text("index: 1", style: new TextStyle(fontSize: 20.0),),
      ),
    )
  ],
)
複製程式碼

IndexedStack的構造方法中有個index屬性,上面的index屬性為1,則顯示的是children陣列中的第2個元素(綠色方塊),如果index改為0,則顯示的是第1個元素(紅色方塊),如果index的大小超過了children陣列的長度,則會報錯。

Table元件

Table元件用於顯示多行多列的佈局,如果只有一行或者一列,使用Row或者Column更高效。下面用一段程式碼展示Table的用法:

class MyApp extends StatelessWidget {

  // 生成Table中的資料
  List<TableRow> getData() {
    var data = [
      "hello",
      "world"
    ];
    List<TableRow> result = new List<TableRow>();
    TextStyle style = new TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold);
    for (int i = 0; i < data.length; i++) {
      String str = data[i];
      List<Widget> row = new List();
      for (int j = 0; j < str.length; j++) {
        row.add(new Text(" ${str[j]} ", style: style));
      }
      result.add(new TableRow(
        children: row
      ));
    }
    return result;
  }

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: "Test",
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text("Test")
        ),
        body: new Table(
          children: getData()
        )
      ),
    );
  }
}
複製程式碼

在模擬器中執行上面的程式碼效果如下圖:

從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎

Wrap元件

Wrap元件可以在水平或垂直方向上多行顯示其子元件,下面是示例程式碼:

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: "Test",
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text("Test")
        ),
        body: new Wrap(
          spacing: 5.0, // 水平方向上兩個子元件的間距
          runSpacing: 20.0, // 兩行的垂直間距
          children: <Widget>[
            new Text("hello"),
            new Text("hello"),
            new Text("hello"),
            new Text("hello"),
            new Text("hello"),
            new Text("hello"),
            new Text("hello"),
            new Text("hello"),
            new Text("hello"),
            new Text("hello"),
            new Text("hello"),
            new Text("hello"),
            new Text("hello"),
            new Text("hello"),
            new Text("hello"),
            new Text("hello"),
            new Text("hello"),
            new Text("hello"),
          ],
        )
      ),
    );
  }
}
複製程式碼

模擬器上執行效果如下圖:

從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎

如果你把上面程式碼中的Wrap換成Row,你會發現Row中的子元件超過螢幕寬度後,不會自動換行顯示。

ListView元件

ListView是一個非常常用的元件,在移動端,基本上大多數頁面都需要使用ListView來顯示資料,關於ListView的基本使用方法,在上一篇中已有記錄。

後記

本篇記錄的是Flutter開發中常用的一些佈局,如果有移動開發經驗,這些知識做類比學習應該很容易掌握,即使不怎麼熟悉,通過檢視文件也可以找到詳細用法,感謝Flutter中文網對官方英文的翻譯,我在學習Flutter的過程中,也參考了很多Flutter中文網的內容,希望大家能一起學習,一起進步,一起愉快地使用Flutter!

我的開源專案

  1. 基於Google Flutter的開源中國客戶端,希望大家給個Star支援一下,原始碼:
從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎 從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎
  1. 基於Flutter的俄羅斯方塊小遊戲,希望大家給個Star支援一下,原始碼:
從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎 從0開始寫一個基於Flutter的開源中國客戶端(4)——Flutter佈局基礎
上一篇 下一篇
從0開始寫一個基於Flutter的開源中國客戶端(3)
——初識Flutter & 常用的Widgets
從0開始寫一個基於Flutter的開源中國客戶端(5)——
App整體佈局框架搭建

相關文章