Flutter - 基礎佈局

歡迎關注微信公衆號:FSA全棧行動 👋css

1、單子佈局 Widget

單子佈局, 顧名思義就是隻能包含一個子控件的 widgetgit

一、Align(Center)

Center 能夠將子控件居中顯示, 默認會盡量拉伸填滿父控件:微信

class CenterDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Icon(Icons.pets),
    );
  }
}
複製代碼

經過查看 Center 的源碼能夠得知, Center 本質上就是沒法指定 alignmentAlign:markdown

class Center extends Align {
  /// Creates a widget that centers its child.
  const Center({ Key? key, double? widthFactor, double? heightFactor, Widget? child })
    : super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child);
}
class Align extends SingleChildRenderObjectWidget {
  /// Creates an alignment widget.
  ///
  /// The alignment defaults to [Alignment.center].
  const Align({
    Key? key,
    this.alignment = Alignment.center,
    this.widthFactor,
    this.heightFactor,
    Widget? child,
  }) : assert(alignment != null),
       assert(widthFactor == null || widthFactor >= 0.0),
       assert(heightFactor == null || heightFactor >= 0.0),
       super(key: key, child: child);
複製代碼

所以, 徹底可使用 Align 來代替 Center:less

  • widthFactor: 指定 Align 的寬度是子控件寬度的幾倍
  • heightFactor: 指定 Align 的高度是子控件高度的幾倍
  • alignment :
    • Alignment.bottomCenter : 底部居中
    • Alignment.center : 居中
    • Alignment(x, y) : 左上角是(-1, -1),右下角是(1, 1)
class AlignDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Center其實就是指定了alignment爲center的Align
    // return Center(
    // child: Icon(Icons.pets),
    // );
    return Align(
      widthFactor: 5, // 寬度是child寬度的5倍
      heightFactor: 5, // 高度是child高度的5倍
      alignment: Alignment.center,
      child: Icon(Icons.pets),
    );
  }
}
複製代碼

通常狀況下, 會直接在外層嵌套 Container 直接指定確切的寬度值, 而不會使用 widthFactoride

二、Padding

通常的 Widget 是沒有 padding 屬性的(Container 除外), 若是但願對子 widget 有 padding 效果的話, 能夠爲子 widget 套一層 Padding, Padding 只有 2 個屬性, 分別是 child 和 padding, padding 屬性對應 EdgeInsetsGeometry 類型的對象, 通常會結合 EdgeInsets 的幾個常量命名構造函數來使用:函數

  • padding: 內間距
    • EdgeInsets.all(8.0): 統一指定內間距
    • EdgeInsets.symmetric(horizontal: 8, vertical: 8): 縱向、橫向分開指定內間距
    • EdgeInsets.fromLTRB(8, 8, 8, 8): 上下左右分開指定內間距
    • EdgeInsets.only(left: 8): 只指定一個方向內間距
class PaddingDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Padding(
          padding: EdgeInsets.all(8.0),
          child: item("hello lqr"),
        ),
        Divider(height: 1, color: Colors.black),
        Padding(
          padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8),
          child: item("hello gitlqr"),
        ),
        Divider(height: 1, color: Colors.black),
        Padding(
          padding: EdgeInsets.fromLTRB(8, 8, 8, 8),
          child: item("hello charylin"),
        ),
        Divider(height: 1, color: Colors.black),
        Padding(
          padding: EdgeInsets.only(left: 8),
          child: item("hello charylin"),
        ),
        Divider(height: 1, color: Colors.black),
      ],
    );
  }

  Widget item(String content) {
    return Text(
      content,
      style: TextStyle(
        fontSize: 30,
        backgroundColor: Colors.red,
        color: Colors.white,
      ),
    );
  }
}
複製代碼

三、Container

Container 是 Flutter 中最特殊的 widget, 能夠指定尺寸、內外間距、2D 轉換等:oop

  • width: 寬度
  • height: 高度
  • alignment: 子 widget 對齊方式
  • padding: 內間距, EdgeInsetsGeometry 類型, 通常使用子類 EdgeInsets
  • margin: 外間距, EdgeInsetsGeometry 類型, 通常使用子類 EdgeInsets
  • transform: 2D 轉換, Matrix4 類型
  • color: 背景色(注意:與 decoration 中的 color 衝突,只能選擇一個設置)
  • decoration:BoxDecoration()
    • color: 背景色
    • border: 邊框樣式, BoxBorder 類型, 經常使用 Border.all(width: 5) 來指定
    • borderRadius: 邊框圓角, BorderRadiusGeometry 類型, 經常使用 BorderRadius.circular(8) 來指定
    • boxShadow:BoxShadow()
      • color: 陰影顏色
      • offset: 陰影偏移量
      • spreadRadius: 延伸,在 offset 的基礎上對 x,y 分別增長
class ContainerDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          // width、height不指定,默認是包裹內容
          width: 200,
          height: 200,
          child: Icon(Icons.pets, size: 50, color: Colors.white),
          // 子元素所在位置
          alignment: Alignment.topLeft,
          padding: EdgeInsets.all(20),
          margin: EdgeInsets.all(10),
          // 旋轉5度,縮小一半
          transform: Matrix4.rotationZ(degree2Radia(5)).scaled(0.5),
          color: Colors.red,
        ),
        Container(
          width: 200,
          height: 200,
          child: Icon(Icons.accessibility, size: 50, color: Colors.white),
          // color與decoration衝突,二者只有選擇其中一個
          // color: Colors.red,
          decoration: BoxDecoration(
              color: Colors.red, // 背景色
              border: Border.all(width: 5, color: Colors.blueAccent), // 邊框
              borderRadius: BorderRadius.circular(8), // 圓角
              boxShadow: [
                BoxShadow(
                  color: Colors.blueGrey, // 陰影顏色
                  offset: Offset(10, 10), // 陰影偏移量
                  spreadRadius: 5, // 延伸,至關於offset爲 15,15
                ),
              ]),
        )
      ],
    );
  }

  double degree2Radia(double degree) {
    return degree * pi / 180;
  }
}
複製代碼

2、多子佈局 Widget

多子佈局, 顧名思義就是隻能包含多個子控件的 widget佈局

一、Flex

Flutter 中的 Flex 與 css 中的 flex 佈局很相似, 能夠很靈活的控制內部子 widget 的擺放, 不過通常狀況下不會直接使用, 而是使用其子類 Row / Column:flex

  • Row/Column 繼承自 Flex
  • Row = Flex(direction: Axis.horizontal)
    • mainAxis(主軸): 水平向右
    • crossAxis(交叉軸): 豎直向下
  • Column = Flex(direction: Axis.vertical)
    • mainAxis(主軸): 豎直向下
    • crossAxis(交叉軸): 水平向右

默認狀況下, Row 在水平方向上會盡量佔據比較大的空間, 這是由於其 mainAxisSize 屬性默認爲 MainAxisSize.max 致使:

Column 與 Row 除了方向不一樣, 其它基本一致, 故掌握 Row 的狀況後, Column 天然也會掌握

class ButtonRowDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    /// Row特色:
    ///  - 水平方向儘量佔據比較大的空間
    ///  * 若是水平方向但願包裹內容,能夠設置 mainAxisSize = min
    ///  - 垂直方向包裹內容
    return Column(
      children: [
        RaisedButton(
          child: Row(
            children: [Icon(Icons.bug_report), Text("bug報告(MainAxisSize.max)")],
          ),
          onPressed: () {},
        ),
        RaisedButton(
          child: Row(
            mainAxisSize: MainAxisSize.min, // 包裹內容。包裹是max佔滿父widget
            children: [Icon(Icons.bug_report), Text("bug報告(MainAxisSize.min)")],
          ),
          onPressed: () {},
        ),
      ],
    );
  }
}
複製代碼

二、Row

Row 比較重點的是主軸及交叉軸的對齊:

  • MainAxisAlignment:
    • start: 主軸的開始位置挨個擺放元素
    • end: 主軸的結束位置挨個擺放元素
    • center: 主軸的中心點對齊
    • spaceBetween: 左右兩邊的間距爲 0,其它元素之間平分間距
    • spaceAround: 左右兩邊的間距是其它元素之間的間距的一半
    • spaceEvenly: 全部的間距平分空間
  • CrossAxisAlignment:
    • start: 交叉軸的起始位置對齊
    • end: 交叉軸的結束位置對齊
    • center: 中心點對齊(默認值)
    • baseline: 基線對齊(必須有文本的時候才起效果)
      • 使用 baseline 對齊必須指定 textBaseline, 不然會報錯.
    • stretch: 先讓 Row 佔據交叉軸儘量大的空間, 將全部子 widget 交叉軸的高度, 拉伸到最大
class RowDemo1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        itemRow("start", MainAxisAlignment.start, "center", CrossAxisAlignment.center),
        itemRow("end", MainAxisAlignment.end, "center", CrossAxisAlignment.center),
        itemRow("center", MainAxisAlignment.center, "center", CrossAxisAlignment.center),
        itemRow("spaceBetween", MainAxisAlignment.spaceBetween, "center", CrossAxisAlignment.center),
        itemRow("spaceAround", MainAxisAlignment.spaceAround, "center", CrossAxisAlignment.center),
        itemRow("spaceEvenly", MainAxisAlignment.spaceEvenly, "center", CrossAxisAlignment.center),
      ],
    );
  }

  Widget itemRow(
      String mainAxisAlignmentStr,
      MainAxisAlignment mainAxisAlignment,
      String crossAxisAlignmentStr,
      CrossAxisAlignment crossAxisAlignment) {
    return Container(
      height: 120,
      margin: const EdgeInsets.only(bottom: 8.0),
      color: Colors.pink[100],
      child: Stack(
        fit: StackFit.expand,
        children: [
          Row(
            mainAxisAlignment: mainAxisAlignment,
            crossAxisAlignment: crossAxisAlignment,
            // textDirection: TextDirection.ltr, // rtl: 從右到左排版; ltr: 從左到右排版(默認)
            children: [
              Container(width: 80, height: 60, color: Colors.red),
              Container(width: 120, height: 100, color: Colors.green),
              Container(width: 90, height: 80, color: Colors.blue),
              Container(width: 50, height: 120, color: Colors.orange),
            ],
          ),
          Positioned(
            left: 0,
            bottom: 0,
            child: Text(
              "GitLqr >>> main:$mainAxisAlignmentStr , cross:$crossAxisAlignmentStr",
              style: TextStyle(fontSize: 20, backgroundColor: Colors.black54, color: Colors.white),
            ),
          )
        ],
      ),
    );
  }
}
複製代碼

class RowDemo2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 基線對齊
    return Column(
      children: [
        itemRow("spaceEvenly", MainAxisAlignment.spaceEvenly, "start", CrossAxisAlignment.start),
        itemRow("spaceEvenly", MainAxisAlignment.spaceEvenly, "center", CrossAxisAlignment.center),
        itemRow("spaceEvenly", MainAxisAlignment.spaceEvenly, "end", CrossAxisAlignment.end),
        itemRow("spaceEvenly", MainAxisAlignment.spaceEvenly, "stretch", CrossAxisAlignment.stretch),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          crossAxisAlignment: CrossAxisAlignment.baseline,
          // alphabetic 與 ideographic 這2種基線幾乎沒差
          textBaseline: TextBaseline.ideographic,
          children: [
            Container(width: 80,height: 60, color: Colors.red, child: Text("Hellxo", style: TextStyle(fontSize: 20))),
            Container(width: 120, height: 100, color: Colors.green, child: Text("Woxrld", style: TextStyle(fontSize: 30)),),
            Container(width: 90, height: 80, color: Colors.blue, child: Text("abxc", style: TextStyle(fontSize: 12))),
            Container(width: 50, height: 120, color: Colors.orange, child: Text("cxba", style: TextStyle(fontSize: 40))),
          ],
        ),
      ],
    );
  }

  Widget itemRow(
      String mainAxisAlignmentStr,
      MainAxisAlignment mainAxisAlignment,
      String crossAxisAlignmentStr,
      CrossAxisAlignment crossAxisAlignment) {
    return Container(
      height: 140,
      margin: const EdgeInsets.only(bottom: 8.0),
      color: Colors.pink[100],
      child: Stack(
        fit: StackFit.expand,
        children: [
          Row(
            mainAxisAlignment: mainAxisAlignment,
            crossAxisAlignment: crossAxisAlignment,
            // textDirection: TextDirection.ltr, // rtl: 從右到左排版; ltr: 從左到右排版(默認)
            children: [
              Container(width: 80, height: 60, color: Colors.red),
              Container(width: 120, height: 100, color: Colors.green),
              Container(width: 90, height: 80, color: Colors.blue),
              Container(width: 50, height: 120, color: Colors.orange),
            ],
          ),
          Positioned(
            left: 0,
            bottom: 0,
            child: Text(
              "GitLqr >>> main:$mainAxisAlignmentStr , cross:$crossAxisAlignmentStr",
              style: TextStyle(fontSize: 20, backgroundColor: Colors.black54, color: Colors.white),
            ),
          )
        ],
      ),
    );
  }
}
複製代碼

最後一組是 CrossAxisAlignment.baseline 的效果, 能夠看到無論文字多大, 字母 x 的底部都是在一條線上的, 這就是基線對齊.
值得注意的是, 使用 CrossAxisAlignment.baseline 必須同時指定基線 textBaseline(默認值爲null), 其值 TextBaseline.ideographicTextBaseline.alphabetic 幾乎沒差

三、Column

Column 與 Row 都是繼承自 Flex, 二者除了在方向上有區別外, 其餘特性幾乎徹底同樣, 這裏只補充一點它們排版方向上的不一樣之處:

  • Row: 排版方向 TextDirection
    • rtl: 從右到左排版
    • ltr: 從左到右排版(默認)
  • Column: 排版方向 VerticalDirection
    • up: 從下到上排版
    • down: 從上到下排版(默認)
class ColumnDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row(children: [
      Expanded(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          crossAxisAlignment: CrossAxisAlignment.center,
          verticalDirection: VerticalDirection.down,
          // up: 從下到上排版; down: 從上到下排版(默認)
          children: [
            Container(width: 80, height: 60, color: Colors.red),
            Container(width: 120, height: 100, color: Colors.green),
            Container(width: 90, height: 80, color: Colors.blue),
            Container(width: 50, height: 120, color: Colors.orange),
            Text(
              "GitLqr >>> VerticalDirection.down",
              style: TextStyle(fontSize: 20, backgroundColor: Colors.black54, color: Colors.white),
            ),
          ],
        ),
      ),
      Expanded(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          crossAxisAlignment: CrossAxisAlignment.center,
          verticalDirection: VerticalDirection.up,
          // up: 從下到上排版; down: 從上到下排版(默認)
          children: [
            Container(width: 80, height: 60, color: Colors.red),
            Container(width: 120, height: 100, color: Colors.green),
            Container(width: 90, height: 80, color: Colors.blue),
            Container(width: 50, height: 120, color: Colors.orange),
            Text(
              "GitLqr >>> VerticalDirection.up",
              style: TextStyle(fontSize: 20, backgroundColor: Colors.black54, color: Colors.white),
            ),
          ],
        ),
      ),
    ]);
  }
}
複製代碼

四、Flexible(Expanded)

  • Flexible 中的屬性:

    • fit: 填充模式
      • tight: 子控件強制填滿可用空間
      • loose: 子控件只佔用自己大小
    • flex: 當 fit 爲 tight 時纔會生效 (重點: width 比 = flex 比)
      • 不指定 flex 時: 按等分的方式來拉伸 Flexible 直至填滿可用空間, 至關於 flex 都是 1
      • 有指定 flex 時: 按 flex 的比例來拉伸 Flexible 直至填滿可用空間, 此時本來的 width 已經無效了
  • Expanded = Flexible(fit: FlexFit.tight)

    • 當 Flex(Row/Column)還有可用空間時, 拉伸子控件大小
    • 當子控件超出 Flex(Row/Column)空間時, 縮小子控件大小
class ExpandedDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        item1(),
        tip("Flexible fit: FlexFit.tight flex: 1"),
        item2(),
        tip("Expanded flex: 1 , flex: 1 (width: 120)"),
        item3(),
        tip("Expanded flex: 1 , flex: 2 (width: 10000)"),
      ],
    );
  }

  Widget item1() {
    return Row(
      children: [
        Flexible(
          fit: FlexFit.tight,
          flex: 1,
          child: Container(width: 80, height: 60, color: Colors.red),
        ),
        Flexible(
          fit: FlexFit.tight,
          flex: 1,
          child: Container(width: 120, height: 100, color: Colors.green),
        ),
        Container(width: 90, height: 80, color: Colors.blue),
        Container(width: 50, height: 120, color: Colors.orange),
      ],
    );
  }

  Widget item2() {
    return Row(
      children: [
        Expanded(
          flex: 1,
          child: Container(width: 80, height: 60, color: Colors.red),
        ),
        Expanded(
          flex: 1,
          child: Container(width: 120, height: 100, color: Colors.green),
        ),
        Container(width: 90, height: 80, color: Colors.blue),
        Container(width: 50, height: 120, color: Colors.orange),
      ],
    );
  }

  Widget item3() {
    return Row(
      children: [
        Expanded(
          flex: 1,
          child: Container(width: 80, height: 60, color: Colors.red),
        ),
        Expanded(
          flex: 2,
          child: Container(width: 10000, height: 100, color: Colors.green),
        ),
        Container(width: 90, height: 80, color: Colors.blue),
        Container(width: 50, height: 120, color: Colors.orange),
      ],
    );
  }

  Widget tip(String content) {
    return Text(
      "GitLqr >>> $content",
      style: TextStyle(
        fontSize: 20,
        color: Colors.white,
        backgroundColor: Colors.black54,
      ),
    );
  }
}
複製代碼

五、Stack

Stack 可讓子 Widget 堆疊在一塊兒, 默認的大小是包裹內容的, 其屬性有:

  • alignment: 指定從什麼位置開始擺放 全部的子 Widget
    • Positioned(Widget): 對 單個子 Widget 進行定位
  • fit: expand(不多用) 將子元素拉伸到儘量大
  • overflow: 超出部分如何處理, 好比: 超出仍顯示的話, 可使用 Overflow.visible
class StackDemo1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Stack(
      alignment: AlignmentDirectional.bottomStart,
      // fit: StackFit.expand,
      overflow: Overflow.visible,
      children: [
        Image.asset("assets/images/FSA_QR.png"),
        Positioned(
          left: 20,
          bottom: -50,
          child: Container(width: 150, height: 150, color: Colors.red),
        ),
        Positioned(
          right: 0,
          child: Text(
            "lqr",
            style: TextStyle(fontSize: 30, color: Colors.white, backgroundColor: Colors.black),
          ),
        )
      ],
    );
  }
}
複製代碼

3、綜合案例

class StackDemo2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Image.asset("assets/images/FSA_QR.png"),
        Positioned(
          left: 0,
          right: 0,
          bottom: 0,
          child: Container(
            padding: EdgeInsets.symmetric(horizontal: 8.0),
            color: Color.fromARGB(160, 0, 0, 0),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(
                  "歡迎關注公衆號: FSA全棧行爲",
                  style: TextStyle(fontSize: 20, color: Colors.white),
                ),
                IconButton(
                  icon: Icon(Icons.favorite),
                  color: Colors.red,
                  onPressed: () => print("點擊了收藏"),
                )
              ],
            ),
          ),
        )
      ],
    );
  }
}
複製代碼

若是文章對您有所幫助, 請不吝點擊關注一下個人微信公衆號:FSA全棧行動, 這將是對我最大的激勵. 公衆號不只有Android技術, 還有iOS, Python等文章, 可能有你想要了解的技能知識點哦~

相關文章
相關標籤/搜索