Flutter(四)之Flutter的佈局Widget

Flutter將佈局組件分成了 單子佈局組件(Single-child layout widgets) 和 多子佈局組件(Multi-child layout widgets)

一. 單子佈局組件

單子佈局組件的含義是其只有一個子組件,可以通過設置一些屬性設置該子組件所在的位置信息等。

比較常用的單子佈局組件有:Align、Center、Padding、Container。

1.1. Align組件

1.1.1. Align介紹

看到Align這個詞,我們就知道它有我們的對齊方式有關。

在其他端的開發中(iOS、Android、前端)Align通常只是一個屬性而已,但是Flutter中Align也是一個組件。

我們可以通過源碼來看一下Align有哪些屬性:

const Align({
  Key key,
  this.alignment: Alignment.center, // 對齊方式,默認居中對齊
  this.widthFactor, // 寬度因子,不設置的情況,會儘可能大
  this.heightFactor, // 高度因子,不設置的情況,會儘可能大
  Widget child // 要佈局的子Widget
})

這裏我們特別解釋一下widthFactorheightFactor作用:

  • 因爲子組件在父組件中的對齊方式必須有一個前提,就是父組件得知道自己的範圍(寬度和高度);
  • 如果widthFactorheightFactor不設置,那麼默認Align會儘可能的大(儘可能佔據自己所在的父組件);
  • 我們也可以對他們進行設置,比如widthFactor設置爲3,那麼相對於Align的寬度是子組件跨度的3倍;

1.1.2. Align演練

我們簡單演練一下Align

class MyHomeBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Align(
      child: Icon(Icons.pets, size: 36, color: Colors.red),
      alignment: Alignment.bottomRight,
      widthFactor: 3,
      heightFactor: 3,
    );
  }
}

1.2. Center組件

1.2.1. Center介紹

Center組件我們在前面已經用過很多次了。

事實上Center組件繼承自Align,只是將alignment設置爲Alignment.center。

源碼分析:

class Center extends Align {
  const Center({
    Key key,
    double widthFactor,
    double heightFactor,
    Widget child
  }) : super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child);
}

1.2.2. Center演練

我們將上面的代碼Align換成Center

class MyHomeBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Icon(Icons.pets, size: 36, color: Colors.red),
      widthFactor: 3,
      heightFactor: 3,
    );
  }
}

1.3. Padding組件

1.3.1. Padding介紹

Padding組件在其他端也是一個屬性而已,但是在Flutter中是一個Widget,但是Flutter中沒有Margin這樣一個Widget,這是因爲外邊距也可以通過Padding來完成。

Padding通常用於設置子Widget到父Widget的邊距(你可以稱之爲是父組件的內邊距或子Widget的外邊距)。

源碼分析:

const Padding({
  Key key,
  @requiredthis.padding, // EdgeInsetsGeometry類型(抽象類),使用EdgeInsets
  Widget child,
})

1.3.2. Padding演練

代碼演練:

class MyHomeBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.all(20),
      child: Text(
        "莫聽穿林打葉聲,何妨吟嘯且徐行。竹杖芒鞋輕勝馬,誰怕?一蓑煙雨任平生。",
        style: TextStyle(
          color: Colors.redAccent,
          fontSize: 18
        ),
      ),
    );
  }
}

1.4. Container組件

Container組件類似於其他Android中的View,iOS中的UIView。

如果你需要一個視圖,有一個背景顏色、圖像、有固定的尺寸、需要一個邊框、圓角等效果,那麼就可以使用Container組件。

1.4.1. Container介紹

Container在開發中被使用的頻率是非常高的,特別是我們經常會將其作爲容器組件。

下面我們來看一下Container有哪些屬性:

Container({
  this.alignment,
  this.padding, //容器內補白,屬於decoration的裝飾範圍
  Color color, // 背景色
  Decoration decoration, // 背景裝飾
  Decoration foregroundDecoration, //前景裝飾
  double width,//容器的寬度
  double height, //容器的高度
  BoxConstraints constraints, //容器大小的限制條件
  this.margin,//容器外補白,不屬於decoration的裝飾範圍
  this.transform, //變換
  this.child,
})

大多數屬性在介紹其它容器時都已經介紹過了,不再贅述,但有兩點需要說明:

  • 容器的大小可以通過widthheight屬性來指定,也可以通過constraints來指定,如果同時存在時,widthheight優先。實際上Container內部會根據widthheight來生成一個constraints
  • colordecoration是互斥的,實際上,當指定color時,Container內會自動創建一個decoration;
  • decoration屬性稍後我們詳細學習;

1.4.2. Container演練

簡單進行一個演示:

class MyHomeBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        color: Color.fromRGBO(3, 3, 255, .5),
        width: 100,
        height: 100,
        child: Icon(Icons.pets, size: 32, color: Colors.white),
      ),
    );
  }
}

1.4.3. BoxDecoration

Container有一個非常重要的屬性 decoration

  • 他對應的類型是Decoration類型,但是它是一個抽象類。
  • 在開發中,我們經常使用它的實現類BoxDecoration來進行實例化。

BoxDecoration常見屬性:

const BoxDecoration({
    this.color, // 顏色,會和Container中的color屬性衝突
    this.image, // 背景圖片
    this.border, // 邊框,對應類型是Border類型,裏面每一個邊框使用BorderSide
    this.borderRadius, // 圓角效果
    this.boxShadow, // 陰影效果
    this.gradient, // 漸變效果
    this.backgroundBlendMode, // 背景混合
    this.shape = BoxShape.rectangle, // 形變
  })

部分效果演示:

class MyHomeBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
//        color: Color.fromRGBO(3, 3, 255, .5),
        width: 150,
        height: 150,
        child: Icon(Icons.pets, size: 32, color: Colors.white),
        decoration: BoxDecoration(
          color: Colors.amber, // 背景顏色
          border: Border.all(
            color: Colors.redAccent,
            width: 3,
            style: BorderStyle.solid
          ), // 這裏也可以使用Border.all統一設置
//            top: BorderSide(
//              color: Colors.redAccent,
//              width: 3,
//              style: BorderStyle.solid
//            ),
          borderRadius: BorderRadius.circular(20), // 這裏也可以使用.only分別設置
          boxShadow: [
            BoxShadow(
              offset: Offset(5, 5),
              color: Colors.purple,
              blurRadius: 5
            )
          ],
//          shape: BoxShape.circle, // 會和borderRadius衝突
          gradient: LinearGradient(
            colors: [
              Colors.green,
              Colors.red
            ]
          )
        ),
      ),
    );
  }
}

1.4.4. 實現圓角圖像

上一個章節我們提到可以通過 Container+BoxDecoration來實現圓角圖像。

實現代碼如下:

class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        width: 200,
        height: 200,
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(20),
          image: DecorationImage(
            image: NetworkImage("https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg"),
          )
        ),
      ),
    );
  }
}

二. 多子佈局組件

在開發中,我們經常需要將多個Widget放在一起進行佈局,比如水平方向、垂直方向排列,甚至有時候需要他們進行層疊,比如圖片上面放一段文字等;

這個時候我們需要使用多子佈局組件(Multi-child layout widgets)。

比較常用的多子佈局組件是Row、Column、Stack,我們來學習一下他們的使用。

2.1. Flex組件

事實上,我們即將學習的Row組件和Column組件都繼承自Flex組件。

  • Flex組件和Row、Column屬性主要的區別就是多一個direction。
  • 當direction的值爲Axis.horizontal的時候,則是Row。
  • 當direction的值爲Axis.vertical的時候,則是Column。

在學習Row和Column之前,我們先學習主軸和交叉軸的概念。

因爲Row是一行排布,Column是一列排布,那麼它們都存在兩個方向,並且兩個Widget排列的方向應該是對立的。

它們之中都有主軸(MainAxis)和交叉軸(CrossAxis)的概念:

  • 對於Row來說,主軸(MainAxis)和交叉軸(CrossAxis)分別是下圖
  • 對於Column來說,主軸(MainAxis)和交叉軸(CrossAxis)分別是下圖

2.1. Row組件

2.1.1. Row介紹

Row組件用於將所有的子Widget排成一行,實際上這種佈局應該是借鑑於Web的Flex佈局。

如果熟悉Flex佈局,會發現非常簡單。

從源碼中查看Row的屬性:

Row({
  Key key,
  MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start, // 主軸對齊方式
  MainAxisSize mainAxisSize = MainAxisSize.max, // 水平方向儘可能大
  CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center, // 交叉處對齊方式
  TextDirection textDirection, // 水平方向子widget的佈局順序(默認爲系統當前Locale環境的文本方向(如中文、英語都是從左往右,而阿拉伯語是從右往左))
  VerticalDirection verticalDirection = VerticalDirection.down, // 表示Row縱軸(垂直)的對齊方向
  TextBaseline textBaseline, // 如果上面是baseline對齊方式,那麼選擇什麼模式(有兩種可選)
  List<Widget> children = const <Widget>[],
})

部分屬性詳細解析:(不過文字是真的難描述,後續推出視頻學習較差)

mainAxisSize

  • 表示Row在主軸(水平)方向佔用的空間,默認是MainAxisSize.max,表示儘可能多的佔用水平方向的空間,此時無論子widgets實際佔用多少水平空間,Row的寬度始終等於水平方向的最大寬度

  • MainAxisSize.min表示儘可能少的佔用水平空間,當子widgets沒有佔滿水平剩餘空間,則Row的實際寬度等於所有子widgets佔用的的水平空間;

mainAxisAlignment:表示子Widgets在Row所佔用的水平空間內對齊方式

  • 如果mainAxisSize值爲MainAxisSize.min,則此屬性無意義,因爲子widgets的寬度等於Row的寬度
  • 只有當mainAxisSize的值爲MainAxisSize.max時,此屬性纔有意義
  • MainAxisAlignment.start表示沿textDirection的初始方向對齊,
  • 如textDirection取值爲TextDirection.ltr時,則MainAxisAlignment.start表示左對齊,textDirection取值爲TextDirection.rtl時表示從右對齊。
  • MainAxisAlignment.endMainAxisAlignment.start正好相反;
  • MainAxisAlignment.center表示居中對齊。

crossAxisAlignment:表示子Widgets在縱軸方向的對齊方式

  • Row的高度等於子Widgets中最高的子元素高度
  • 它的取值和MainAxisAlignment一樣(包含startendcenter三個值)
  • 不同的是crossAxisAlignment的參考系是verticalDirection,即verticalDirection值爲VerticalDirection.downcrossAxisAlignment.start指頂部對齊,verticalDirection值爲VerticalDirection.up時,crossAxisAlignment.start指底部對齊;而crossAxisAlignment.endcrossAxisAlignment.start正好相反;

2.1.2. Row演練

我們來對部分屬性進行簡單的代碼演練,其他一些屬性大家自己學習一下

class MyHomeBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      crossAxisAlignment: CrossAxisAlignment.end,
      mainAxisSize: MainAxisSize.max,
      children: <Widget>[
        Container(color: Colors.red, width: 60, height: 60),
        Container(color: Colors.blue, width: 80, height: 80),
        Container(color: Colors.green, width: 70, height: 70),
        Container(color: Colors.orange, width: 100, height: 100),
      ],
    );
  }
}

2.1.3. mainAxisSize

默認情況下,Row會儘可能佔據多的寬度,讓子Widget在其中進行排布,這是因爲mainAxisSize屬性默認值是MainAxisSize.max。

我們來看一下,如果這個值被修改爲MainAxisSize.max會什麼變化:

2.1.4. TextBaseline

關於TextBaseline的取值解析在這裏插入圖片描述
ideographic: 以漢字爲基線
alphabetic: 以字母爲基線
line-height(行高): 是指bottom到top的距離
text-height(文本的高度): 文字的高度
行距: (行高 - 文本的高度) / 2

2.1.5. Expanded

如果我們希望紅色和黃色的Container Widget不要設置固定的寬度,而是佔據剩餘的部分,這個時候應該如何處理呢?

這個時候我們可以使用 Expanded 來包裹 Container Widget,並且將它的寬度不設置值;

  • flex屬性,彈性係數,Row會根據兩個Expanded的彈性係數來決定它們佔據剩下空間的比例
class MyHomeBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      crossAxisAlignment: CrossAxisAlignment.end,
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[
        Expanded(
          flex: 1,
          child: Container(color: Colors.red, height: 60),
        ),
        Container(color: Colors.blue, width: 80, height: 80),
        Container(color: Colors.green, width: 70, height: 70),
        Expanded(
          flex: 1,
          child: Container(color: Colors.orange, height: 100),
        )
      ],
    );
  }
}

2.2. Column組件

Column組件用於將所有的子Widget排成一列,學會了前面的Row後,Column只是和row的方向不同而已。

2.2.1. Column介紹

我們直接看它的源碼:我們發現和Row屬性是一致的,不再解釋

 Column({
    Key key,
    MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
    MainAxisSize mainAxisSize = MainAxisSize.max,
    CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
    TextDirection textDirection,
    VerticalDirection verticalDirection = VerticalDirection.down,
    TextBaseline textBaseline,
    List<Widget> children = const <Widget>[],
  })

2.2.2. Column演練

我們直接將Row的代碼中Row改爲Column,查看代碼運行效果:

class MyHomeBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      crossAxisAlignment: CrossAxisAlignment.end,
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[
        Expanded(
          flex: 1,
          child: Container(color: Colors.red, width: 60),
        ),
        Container(color: Colors.blue, width: 80, height: 80),
        Container(color: Colors.green, width: 70, height: 70),
        Expanded(
          flex: 1,
          child: Container(color: Colors.orange, width: 100),
        )
      ],
    );
  }
}

2.3. Stack組件

在開發中,我們多個組件很有可能需要重疊顯示,比如在一張圖片上顯示文字或者一個按鈕等。

在Android中可以使用Frame來實現,在Web端可以使用絕對定位,在Flutter中我們需要使用層疊佈局Stack。

2.3.1. Stack介紹

我們還是通過源碼來看一下Stack有哪些屬性:

Stack({
  Key key,
  this.alignment = AlignmentDirectional.topStart,
  this.textDirection,
  this.fit = StackFit.loose,
  this.overflow = Overflow.clip,
  List<Widget> children = const <Widget>[],
})

參數解析:

  • alignment:此參數決定如何去對齊沒有定位(沒有使用Positioned)或部分定位的子widget。所謂部分定位,在這裏特指沒有在某一個軸上定位. left、right爲橫軸,top、bottom爲縱軸,只要包含某個軸上的一個定位屬性就算在該軸上有定位。
  • textDirection:和Row、Wrap的textDirection功能一樣,都用於決定alignment對齊的參考系即:textDirection的值爲TextDirection.ltr,則alignment的start代表左,end代表右;textDirection的值爲TextDirection.rtl,則alignment的start代表右,end代表左。
  • fit:此參數用於決定 沒有定位 的子widget如何去適應Stack的大小。StackFit.loose表示使用子widget的大小,StackFit.expand表示擴伸到Stack的大小。
  • overflow:此屬性決定如何顯示超出Stack顯示空間的子widget,值爲Overflow.clip時,超出部分會被剪裁(隱藏),值爲Overflow.visible 時則不會。

2.3.2. Stack演練

Stack會經常和Positioned一起來使用,Positioned可以決定組件在Stack中的位置,用於實現類似於Web中的絕對定位效果。

我們來看一個簡單的演練:

class MyHomeBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        Container(
          color: Colors.purple,
          width: 300,
          height: 300,
        ),
        Positioned(
          left: 20,
          top: 20,
          child: Icon(Icons.favorite, size: 50, color: Colors.white)
        ),
        Positioned(
          bottom: 20,
          right: 20,
          child: Text("你好啊,李銀河", style: TextStyle(fontSize: 20, color: Colors.white)),
        )
      ],
    );
  }
}

注意: Positioned組件只能在Stack中使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章