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

個人上一篇博客中記錄了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來寫。git

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

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

包含單個子Widget的佈局容器

Center組件

Center組件中的子組件會居中顯示。Center組件會盡量的大,若是你不給它設置任何約束。下面是Center組件的使用方法:bash

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的用法:app

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屬性,顯示出的界面就會有所不一樣:框架

Container還能夠設置內邊距和外邊距,以下代碼所示:less

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組件專門用於給它的子組件設置內邊距,用法比較簡單:ide

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取不一樣值時,上面的代碼運行結果以下圖所示:

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,以下圖所示:

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!'),
    ),
  ),
)
複製代碼

運行效果以下圖:

包含多個子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")
          ],
        )
      ),
    );
  }
}
複製代碼

在模擬器上運行的效果以下圖:

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

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

關於上面幾個取值,用以下幾個圖來講明:

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大小,在模擬器上運行效果以下圖:

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()
        )
      ),
    );
  }
}
複製代碼

在模擬器中運行上面的代碼效果以下圖:

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

模擬器上運行效果以下圖:

若是你把上面代碼中的Wrap換成Row,你會發現Row中的子組件超過屏幕寬度後,不會自動換行顯示。

ListView組件

ListView是一個很是經常使用的組件,在移動端,基本上大多數頁面都須要使用ListView來顯示數據,關於ListView的基本使用方法,在上一篇中已有記錄。

後記

本篇記錄的是Flutter開發中經常使用的一些佈局,若是有移動開發經驗,這些知識作類比學習應該很容易掌握,即便不怎麼熟悉,經過查看文檔也能夠找到詳細用法,感謝Flutter中文網對官方英文的翻譯,我在學習Flutter的過程當中,也參考了不少Flutter中文網的內容,但願你們能一塊兒學習,一塊兒進步,一塊兒愉快地使用Flutter!

個人開源項目

  1. 基於Google Flutter的開源中國客戶端,但願你們給個Star支持一下,源碼:
  1. 基於Flutter的俄羅斯方塊小遊戲,但願你們給個Star支持一下,源碼:
上一篇 下一篇
從0開始寫一個基於Flutter的開源中國客戶端(3)
——初識Flutter & 經常使用的Widgets
從0開始寫一個基於Flutter的開源中國客戶端(5)——App總體佈局框架搭建
相關文章
相關標籤/搜索