8天讓iOS開發者上手Flutter之二

franky發表於2021-07-19

Flutter佈局

Alignment

Container類裡有一個alignment屬性,翻譯過來應該叫對齊方式,這個屬性用來控制Container的子控制元件相對於它自身的一個位置。在我們iOS開發中,我們知道座標系的原點是在左上角。而在flutter中,座標系的原點在父控制元件的正中心,可以使用這個alignment屬性來控制子控制元件在父控制元件中的位置,它有兩個引數分別是double型別的x,y。取值是-1到1,當0,0的時候表示子控制元件在父控制元件的正中心;當1,0的時候,表示子控制元件位於x方向上的最右側,y方向上居中;當-1,-1的時候,表示子控制元件位於父控制元件的左上角位置。有點類似於CALayer的anchorPoint屬性。如圖程式碼如下: image.png

8天讓iOS開發者上手Flutter之二

Row

Row表示水平佈局,它有一個children屬性,用來存放它的子控制元件。程式碼如下: image.png

8天讓iOS開發者上手Flutter之二
其中Icon類是flutter提供的一個快速建立一些常用圖示的類。如果想給每個Icon都加一個背景色,直接設定Icon的color是不行的,這樣修改的是圖示的顏色而不是背景色,給Icon包一層Container容器,然後再設定Container顏色這樣就可以實現了。

最開始我們嘗試了alignment屬性的作用,當它是0,0的時候,Text的位置預設是在螢幕中央的。為什麼這裡換成我們的Row之後,Row的子控制元件位置不在螢幕中央呢?

mainAxisAlignment

Row 和 Column 都有一個mainAxisAlignment屬性,叫作主軸對齊方式,預設是MainAxisAlignment.start意思沿著主軸方向開始,Row佈局下就是從左至右,Column佈局下就是從上至下。
MainAxisAlignment.spaceAround:將剩下的空間平均分配
MainAxisAlignment.spaceBetween:將剩下的空間分配到子控制元件之間
MainAxisAlignment.spaceEvenly:等間距分配子控制元件

crossAxisAlignment

交叉軸對齊方式,start,end,center這幾種方式試一下很好理解,stretch會將子控制元件拉伸。而baseline用的比較少,單獨使用它會報錯,需要和Text文字結合,還需要配合textBaseline屬性一起使用。如下圖所示,如果不設定CrossAxisAlignment.baselineTextBaseline.alphabetic就會根據控制元件高度水平對齊,而如果設定了就會根據控制元件內文字的基線對齊。
Snip20210713_177.png Snip20210713_178.png

Column

這個和Row是對應的,Row是水平佈局,這個Column是垂直佈局

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

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.green,
      alignment: Alignment(0, 0),
      child: Column(
        children: [
          Container(
            child: Icon(Icons.add, size: 180,),
            color: Colors.red,
          ),
          Container(
            child: Icon(Icons.ac_unit, size: 120,),
            color: Colors.yellow,
          ),
          Container(
            child: Icon(Icons.access_alarm, size: 60,),
            color: Colors.blue,
          ),
        ],
      ),
    );
  }
}
複製程式碼

顯示效果如圖:

8天讓iOS開發者上手Flutter之二

mainAxisAlignment

這個跟Row類似

crossAxisAlignment

這個跟Row類似

Stack

這個是用在Z軸上的佈局的,row是X軸,column是Y軸。children陣列第一個放在最底部,最後一個放在上面,離使用者最近的地方。

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

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.green,
      alignment: Alignment(0, 0),
      child: Stack(
        children: [
          Container(
            child: Icon(Icons.add, size: 180,),
            color: Colors.red,
          ),
          Container(
            child: Icon(Icons.ac_unit, size: 120,),
            color: Colors.yellow,
          ),
          Container(
            child: Icon(Icons.access_alarm, size: 60,),
            color: Colors.blue,
          ),
        ],
      ),
    );
  }
}
複製程式碼

APP顯示效果:

8天讓iOS開發者上手Flutter之二

alignment

Stack裡有一個alignment屬性,它用來控制所有子控制元件相對於最大那個子控制元件的位置

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

  @override
  Widget build(BuildContext context) {
    return Stack(
      alignment: Alignment.bottomRight,
      children: [
        Container(
          child: Icon(Icons.add, size: 180,),
          color: Colors.red,
        ),
        Container(
          child: Icon(Icons.ac_unit, size: 120,),
          color: Colors.yellow,
        ),
        Container(
          child: Icon(Icons.access_alarm, size: 60,),
          color: Colors.blue,
        ),
      ],
    );
  }
}
複製程式碼
8天讓iOS開發者上手Flutter之二

Positioned

Stack裡配合Positioned類使用的話,跟我們iOS的約束有點類似了,可以設定上,左間距之類的

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

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Positioned(
          child: Container(
            child: Icon(
              Icons.add,
              size: 180,
            ),
            color: Colors.red,
          ),
        ),
        Positioned(
          child: Container(
            child: Icon(
              Icons.ac_unit,
              size: 120,
            ),
            color: Colors.yellow,
          ),
        ),
        Positioned(
          top: 20,
          left: 20,
          right: 20,
          child: Container(
            child: Icon(
              Icons.access_alarm,
              size: 60,
            ),
            color: Colors.blue,
          ),
        ),
      ],
    );
  }
}
複製程式碼
8天讓iOS開發者上手Flutter之二
可以看到最小的藍色檢視的上左右均設定了20的間距,是不是熟悉的約束味道。。。

Expanded

Expanded是一個類似Container的常用的佈局容器,它用來填充佈局,使用了填充佈局在主軸方向上是不會有間隔的,所以Expanded用在Row裡面的時候,子控制元件的寬度設定就沒有意義了,而在Column裡面使用的使用,子控制元件的高度設定就沒有意義了。這裡以Column為例:

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

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.green,
      alignment: Alignment(0, 0),
      child: Column(
        children: [
          Expanded(
              child: Container(
                child: Icon(Icons.add, size: 180,),
                color: Colors.red,
              ),
          ),
          Expanded(
              child: Container(
                child: Icon(Icons.ac_unit, size: 120,),
                color: Colors.yellow,
              ),
          ),
          Expanded(
              child: Container(
                child: Icon(Icons.access_alarm, size: 60,),
                color: Colors.blue,
              ),
          ),
        ],
      ),
    );
  }
}
複製程式碼
8天讓iOS開發者上手Flutter之二

AspectRatio

AspectRatio是一個容器類,它有一個屬性aspectRatio表示寬高比。如果指定了寬度,根據這個aspectRatio可以自動算出高度;如果指定了高度,根據aspectRatio可以自動算出寬度。如下面程式碼指定了父檢視高度為100,aspectRatio寬高比為2,子檢視寬度就是200,再把父檢視撐起來也是200。

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

  @override
  Widget build(BuildContext context) {
    return Container(
        color: Colors.green,
        alignment: Alignment(0, 0),
        child: Container(
          color: Colors.blue,
          height: 100,
          child: AspectRatio(
            aspectRatio: 2,
            child: Icon(
              Icons.add,
            ),
          ),
        ));
  }
}
複製程式碼
8天讓iOS開發者上手Flutter之二

Flutter狀態管理

之前介紹的這麼多類都是無狀態的,意思是顯示之後沒辦法更新UI的,如果想要實時更新UI的話,就不能繼承無狀態的類了。我們先來看一個例子:明明count變化了,但是介面顯示沒有變化 image.png 記得修改APP的home檢視 image.png 然後點選螢幕右下角的加號按鈕,可以發現明明控制檯列印了count的值已經發生了變化,但是介面顯示依然是0 image.png 下面我們解決這個問題,將StateManagerDemo繼承改為StatefulWidget,實現createState方法返回一個自定義的State物件,自定義的State物件裡面實現build方法。還需要注意在按鈕的點選方法裡呼叫一下setState方法。這樣每次點選加號按鈕就能實時更新UI了。改造完之後如下圖所示: image.png

專案搭建之底部TabBar

到目前為止,我們對flutter的一些基礎知識就算是介紹的差不多了。接下來我們開始做一個簡單的仿微信APP。我們應該都有經驗,理論的知識學得再多,不動手開始敲程式碼,不在專案中運用,是很難真正掌握一門知識的。

新建一個flutter工程,命名wechat_demo: image.png

刪掉多餘的程式碼,可以全部重新自己寫: image.png

建立底部的TabBar和item,預設的type是白色的,顯示效果很難看所以改為fixed,還可以設定fixedColor: image.png

8天讓iOS開發者上手Flutter之二
這樣底部的TabBar就顯示出來了,會發現點選沒有效果,對比iOS會發現這塊地方還是iOS提供的UITabBarController封裝的更舒服,每個平臺都各有優劣吧。

BottomNavigationBar有一個屬性currentIndex即代表了當前選中的下標。我們可以通過設定它的值來控制哪個按鈕被選中。既然需要改變UI了,說明我們需要將StatelessWidget改為StatefulWidget了。還有一個引數onTap是用來回撥點選事件的。實現點選事件,切換currentIndex,重新setState就可以實現,點選切換了。我們將bottomNavigationBar相關程式碼放到一個新的檔案rootPage中。程式碼如下: image.png 記得修改main.dart檔案中 image.png 這樣就實現了APP的底部TabBar的展示,點選功能。點選每個item的時候,會發現flutter的bottomNavigationBar還自帶了動畫效果...

我們知道Scaffold還有一個body的屬性,表示展示在螢幕上的內容。我們每個item對應的介面都需要一個AppBar,那麼也許意味著,body屬性還需要一個Scaffold來展示我們的每個item對應的內容。 image.png 可以看到微信首頁就已經大概出來了,但是點選的時候只會顯示這個微信頁面,怎麼實現切換不同的頁面呢,肯定需要一個陣列,來存放對應的每個頁面了。 image.png
然後body裡,根據我們的_currentIndex返回對應的body image.png 這樣點選每個item都會跳轉到對應的介面了,APP的主框架的搭建好了。

總結

今天主要講了flutter的三大布局類Row,Column,Stack以及他們的一些屬性。然後是有狀態的Widget和無狀態的Widget,最後搭建了一下我們要做的仿微信APP的底部bottomNavigationBar和切換頁面功能

相關文章