Flutter中的佈局&狀態管理

語言: CN / TW / HK

佈局

若想將文字置於當前頁面的中間位置,可以使用Center或者Alignment控制元件

Center

``` class LayoutDemo extends StatelessWidget { const LayoutDemo({Key? key}) : super(key: key);

@override Widget build(BuildContext context) {

return const Center(
    child: Text('LayoutDemo', style:TextStyle(fontSize: 20, color: Colors.red, backgroundColor: Colors.cyan))
);

} } ```

Simulator Screen Shot - iPhone 13 Pro Max - 2022-11-13 at 01.24.11.png

Alignment

使用Container包裝一層,設定子控制元件的成員變數Alignment

``` class LayoutDemo extends StatelessWidget { const LayoutDemo({Key? key}) : super(key: key);

@override Widget build(BuildContext context) { return Container( color: Colors.orange, alignment: const Alignment(0, 0), child: const Text('LayoutDemo', style:TextStyle(fontSize: 20, backgroundColor: Colors.cyan)) );

} } ```

Simulator Screen Shot - iPhone 13 Pro Max - 2022-11-13 at 01.29.39.png

通過除錯Alignment(x, y)可知座標系如下圖,原點位於中心位置,xy的取值範圍均為[-1,1] image.png

佈局

Flutter中的佈局與前端中的佈局方式很相似,都是FlexBox模式,下面分別介紹橫向佈局縱向佈局層級佈局

Row(橫向佈局)

使用Row(橫向佈局)佈局的控制元件會在橫軸方向佔滿父部件

注意這個屬性mainAxisAlignment(主軸方向佈局)共有以下幾個選項:

  • start:佈局從主軸開始的位置正向佈局
  • end:佈局從主軸結束的位置開始反向佈局
  • center:佈局位於主軸中心的位置
  • spaceBetween:把主軸方向剩餘的空間均勻的分佈於控制元件之間
  • spaceAround:把主軸方向剩餘的空間平均分配到空間周圍
  • spaceEvenly:把主軸發現剩餘的空間平均分配在控制元件周圍空餘的部分

``` class LayoutDemo extends StatelessWidget { const LayoutDemo({Key? key}) : super(key: key);

@override Widget build(BuildContext context) { return Container( color: Colors.orange, alignment: const Alignment(0, 0), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ Container( color: Colors.red, width: 50, height: 50, child: cosnt Icon(Icons.add), ), Container( color: Colors.blue, width: 50, height: 50, child: cosnt Icon(Icons.search), ), Container( color: Colors.white, width: 50, height: 50, child: cosnt Icon(Icons.battery_alert), ), ], ) );

} } ```

mainAxisAlignment

start

佈局沿主軸開始的方向,剛才已經闡述過這裡的座標系,又是Row排列,則主軸是x方向,從左至右

image.png

end

佈局沿主軸結束的方向,所以是從右至左來排布

image.png

center

佈局位於主軸中心的位置

image.png

spaceBetween

把主軸方向剩餘的空間均勻的分佈於控制元件之間

image.png

spaceAround

把主軸方向剩餘的空間平均分配到空間周圍

image.png

spaceEvenly

把主軸發現剩餘的空間平均分配在控制元件周圍空餘的部分

image.png

crossAxisAlignment

這裡舉例仍然是在Row(橫向佈局)的情況下來討論crossAxisAlignment屬性

crossAxisAlignment中有以下幾個選項:

  • start:向交叉軸開始的方向對齊
  • end:向交叉軸結束的方向對齊
  • center:交叉軸方向居中對齊
  • stretch:填滿交叉軸方向
  • baseline:文字基線對齊

``` class LayoutDemo extends StatelessWidget { const LayoutDemo({Key? key}) : super(key: key);

@override Widget build(BuildContext context) { return Container( color: Colors.orange, alignment: const Alignment(0, 0), child: Row( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( color: Colors.red, child: const Icon(Icons.add, size: 50), ), Container( color: Colors.blue, child: const Icon(Icons.search, size: 100), ), Container( color: Colors.white, child: const Icon(Icons.battery_alert, size: 150), ), ], ) );

} } ```

start

在交叉軸方向從上到下進行佈局,相當於頂部對齊 image.png

end

在交叉軸方向從下到上進行佈局,相當於底部對齊

image.png

center

在交叉軸方向上居中佈局,這也是預設屬性

image.png

stretch

子控制元件將撐滿交叉軸方向

image.png

baseline

baseline要配合文字來進行演示,同時也需要設定textBaseline,意思就是以文字的基線來進行對齊

``` class LayoutDemo extends StatelessWidget { const LayoutDemo({Key? key}) : super(key: key);

@override Widget build(BuildContext context) { return Container( color: Colors.orange, alignment: const Alignment(0, 0), child: Row( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.baseline, textBaseline: TextBaseline.alphabetic, children: [ Container( height: 80, color: Colors.red, child: const Text('好好學習', style: TextStyle(fontSize: 15)) ), Container( height: 80, color: Colors.blue, child: const Text('天天要吃飯', style: TextStyle(fontSize: 30)) ), Container( height: 80, color: Colors.white, child: const Text('晚上', style: TextStyle(fontSize: 60)) ), ], ) );

} } ```

image.png

Expanded(填充佈局)

Expanded(填充佈局):

  • 在主軸方向不會剩下間隙,將被Expanded包裝的部件進行拉伸和壓縮
  • 主軸橫向,設定寬度沒有意義
  • 主軸縱向,設定高度沒有意義
  • TextExpanded包裝後文字可以自動換行,所以Expanded也被稱為靈活佈局

``` class LayoutDemo extends StatelessWidget { const LayoutDemo({Key? key}) : super(key: key);

@override Widget build(BuildContext context) {

// return const Center(
//     child: Text('LayoutDemo', style:TextStyle(fontSize: 20, color: Colors.red, backgroundColor: Colors.cyan))
// );

return Container(
  color: Colors.orange,
  alignment: const Alignment(0, 0),
  child: Row(
    mainAxisAlignment: MainAxisAlignment.end,
    crossAxisAlignment: CrossAxisAlignment.baseline,
    textBaseline: TextBaseline.alphabetic,
    children: [
      Expanded(child: Container(
          height: 80,
          color: Colors.red,
          child: const Text('好好學習', style: TextStyle(fontSize: 15))
      )),
      Expanded(child: Container(
          height: 80,
          color: Colors.blue,
          child: const Text('天天要吃飯', style: TextStyle(fontSize: 30))
      )),
      Expanded(child: Container(
          height: 80,
          color: Colors.white,
          child: const Text('晚上', style: TextStyle(fontSize: 60))
      ))
    ],
  )
  //child: const Text('LayoutDemo', style:TextStyle(fontSize: 20, backgroundColor: Colors.cyan))
);

} }

```

image.png

Column(縱向佈局)

Row一樣,使用Column(縱向佈局)佈局的控制元件會在縱軸方向佔滿父部件,mainAxisAlignmentcrossAxisAlignment屬性同上,這裡不再演示

``` class LayoutDemo extends StatelessWidget { const LayoutDemo({Key? key}) : super(key: key);

@override Widget build(BuildContext context) { return Container( color: Colors.orange, alignment: const Alignment(0, 0), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, // textBaseline: TextBaseline.alphabetic, children: [ Expanded(child: Container( height: 80, color: Colors.red, child: const Text('好好學習', style: TextStyle(fontSize: 15)) )), Expanded(child: Container( height: 80, color: Colors.blue, child: const Text('天天要吃飯', style: TextStyle(fontSize: 30)) )), Expanded(child: Container( height: 80, color: Colors.white, child: const Text('晚上', style: TextStyle(fontSize: 60)) )) ], )

);

} } ```

Stack(層次佈局)

``` class StackDemo extends StatelessWidget { const StackDemo({Key? key}) : super(key: key);

@override Widget build(BuildContext context) { return Stack( alignment: const Alignment(-1,-1), children: [ Container( color: Colors.white, width: 200, height: 200, child: const Icon(Icons.add) ), Container( color: Colors.blue, width: 100, height: 100, child: const Icon(Icons.search) ),Container( color: Colors.cyan, width: 50, height: 50, child: const Icon(Icons.alarm) ), ], ); } }

```

image.png

Stack也叫層次佈局,如果這裡需要子控制元件一個在左一個在右的話則需要使用到Stack中的Positioned屬性來完成控制子控制元件的相對位置

``` class StackDemo extends StatelessWidget { const StackDemo({Key? key}) : super(key: key);

@override Widget build(BuildContext context) { return Stack( alignment: const Alignment(-1,-1), children: [ Positioned(child: Container( color: Colors.white, width: 200, height: 200, child: const Icon(Icons.add) )), Positioned( left: 1, bottom: 1, child: Container( color: Colors.blue, width: 100, height: 100, child: const Icon(Icons.search) )), Positioned( right: 1, child: Container( color: Colors.cyan, width: 50, height: 50, child: const Icon(Icons.alarm) ))

  ],
);

} } ```

image.png

AspectRatio(設定寬高比)

``` class AspectRatioDemo extends StatelessWidget { const AspectRatioDemo({Key? key}) : super(key: key);

@override Widget build(BuildContext context) { return Container( //padding: const EdgeInsets.all(10), color: Colors.blue, //width: 400, height: 150, child: AspectRatio( aspectRatio: 2/1, child: Container( alignment: const Alignment(0,0), color: Colors.red, ), ), ); } } `` 通過設定aspectRatio(寬高比)2/1來設定寬度為300`,如果父控制元件中設定了寬度,則以父控制元件為主

image.png

狀態管理

現在需要是如果有資料變化了需要實時渲染到頁面上以便看到變化,舉例如下

``` class StateManageDemo extends StatelessWidget { StateManageDemo({Key? key}) : super(key: key); int count = 0;

@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('StateManageDemo', style: TextStyle(fontSize: 15),)), body: Center( child: Chip( label: Text('$count'), ), ), floatingActionButton: FloatingActionButton( child: const Icon(Icons.add), onPressed: (){ count++; debugPrint('count = $count'); }, ), ); } } ```

image.png

但是執行後發現,每次點選按鈕後確實已經改變了count的值,但是並未更新到頁面上,甚至熱更新也不能更新結果上去。原因是StatelessWidget是一個無狀態的控制元件,也就是說在建立完成後就不會再進入其build方法了,那麼也就無法完成更新,所以如果想使用資料有變化的控制元件可以使用StatefullWidget

``` class StateManageDemo extends StatefulWidget { const StateManageDemo({Key? key}) : super(key: key);

@override State createState() => _SMDState();

}

class _SMDState extends State{ int count = 0;

@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('StateManageDemo', style: TextStyle(fontSize: 15),)), body: Center( child: Chip( label: Text('$count'), ), ), floatingActionButton: FloatingActionButton( child: const Icon(Icons.add), onPressed: (){ count++; setState(() {

      });
      debugPrint('count = $count');
    },
  ),
);

} } ```

使用StatefullWidget控制元件時需要搭配使用一個狀態管理的類,原來的build方法放在這個類中實現,也就是說資料由這個類來管理和渲染,並且在狀態改變後需要設定回撥setState,這樣就可以完成上面的需求了。