初探 Flutter 的 Layout

紅寶鐵軌客
來關注...
關注/停止關注:紅寶鐵軌客
關注有什麼好處?:當作者有新文章發佈時,「思書」就會自動通知您,讓您更容易與作者互動。
現在就加入《思書》,你就可以關注本作者了!
《思書》是一個每個人的寫作與論壇平台,特有的隱私管理,讓你寫作不再受限,討論更深入真實,而且免費。 趕快來試試!
還未加入《思書》? 現在就登錄! 已經加入《思書》── 登入
寫程式中、折磨中、享受中 ......
1.4k   0  
·
2021/06/07
·
21分鐘


延續前一篇的文章:

穿梭在不同的畫面中 - 固定名稱路由法 — 如前文所述,Flutter 有四種 Navigation & Routing 的方式: 我們已經學過了「N1/直接導航法」,現在來看第二種,「固定名稱路由法」。
WriterShelf 思書: 紅寶鐵軌客

我們會從這裡開始修改程式碼,如果你是中途插隊進來的讀者,請參考裡面的程式碼。

按照順序,我們現在應該是要繼續介紹第三種切換畫面的方法:「動態名稱路由法」,但是有一個問題,我們目前開發中的 App 內並沒有動態內容,根本就不能測試,所以我們必須要先建一個動態的內容,才能繼續開發測試。既然如此,那我們就先把「快樂錄音機 Happy recorder」的主介面建立起來吧。


介面設計

要開發 Flutter 的 screen 內容,也就是使用者介面 UI,最重要的就是先要了解 Flutter 的 screen layout(畫面佈局),但是要做 Layout 前,我們必須先有 UI 的設計草圖,在大型開發團隊裡,設計 UI 都會有專人負責,如果是一人團隊,很多人都會偷懶不做,一路改,我的建議是在下手寫程式前,務必要先做個 UI 草圖,不然,相信我,未來一定會讓你改到怕。

我不是設計師,根本沒資格介紹怎麼設計 UI,我只能說我覺得 Figma 不錯用,下面這個 Happy recorder 的 UI 草圖就是用 Figma 做出來的。

我們的「快樂錄音機 Happy recorder」目前的動作還很簡單:

  • 左邊是 MyHomePage,按其中任何一個列表 List 項目會連到右邊的 AudioSession,
  • 右邊是 AudioSession,按右下角停止鍵,回到 MyHomePage。

當然,未來還有很多功能要做,例如:新增及修改標題及說明,錄音、暫停,等等⋯⋯ ,別急,我們一步一步來。

我是真覺得 Figma 很簡單好用,它有網頁版不用安裝,又有免費的方案,還可以模擬畫面的動作,對我來說,很夠用了,簡單的用法可以參考下面這個教學,它講的很清楚:

Figma 教學:讓技術開發人員都能輕鬆實作畫面設計 — 全端開發者是否應該具備 UI/UX 設計的技能呢?幸好,UI/UX 設計工具加速了畫面設計 (Screen design) 的開發速度,我將會分享使用 Figma 為我的新 app 設計 UI/UX 的經驗。  AppCoda

很好,我們有 UI 構圖了,接下來就要把它在 Flutter 上「刻」出來。


Flutter Layout Widget

Layout 的英文 = 佈局,就是在畫面上排版,我們現在功力不足,只能用 Flutter 給你的組件 widget 來排版,所以,你要先了解 Flutter 的排版 widget。

一個很重要的觀念,在 Flutter 中的 Layout widget 分成兩大類:

  • 只有一個小孩的:一個小孩在英文叫做 child:
    • 常用的有:Align、Center、Container、⋯⋯
  • 可以有很多小孩的:兩個以上的小孩在英文叫做 children:
    • 常用的有:Column、ListView、Row、Stack ⋯⋯

我不是在教你英文,國中有讀畢業的人都知道小孩的單數是 child,複數是 children,我想要講的是在 Flutter 中,Layout widget 一定要使用正確的單複數,要用對 child 及 children,例如:

Column( // 這是有很多小孩的 Widget
  children: const <Widget>[
    Text('第一行文字'),
    Text('第二行文字'),
    FittedBox( // 這是只有一個小孩的 Widget
      child: FlutterLogo(),
    ),
  ],
),

看到不同點了嗎?

  • 第 1 行的 Column 就是一個可以有很多小孩的 widget,所以它的裡面,第 2 行,就是 childern。
  • 第 5 行的 FittedBox 就只能有一個小孩的 widget,所以它的裡面,第 6 行,就是 child。

有了單複數 layout widget 的觀念後,再來就是要把 Layout Widgets 想成試一棵「樹」,是的,在 Flutter 中,layout 就是一棵樹,我借用了官網中介紹 Layout 的圖,如下:

這張圖中很清楚的展示了單複數 layout widget 的不同,最上層的 container 只能有一個小孩,但是第二層的 row 可以有很多小孩,這就是 Flutter layout 的主要概念。

這種樹狀的 Layout 方式不是 Flutter 首創,Web 也是這樣,只是在 Web 的開發上,我們都會將網頁內容與樣式分開寫,但是 Flutter 卻是混在一起的,所以程式看起來很雜亂又很長,這也是 Flutter 很讓人詬病的一個問題,我們要想辦法讓它好寫好看一些。

Flutter 中還有一些以 Sliver(銀)開頭的 Widgets,例如:SliverList,這是 layout Widget 中的一個進階版本,Silver 代表的是整個畫面中一小塊可以捲動的區域,所以你可以在畫面中組合不同的 Sliver Widgets 各管各的區域。 

好了,就這樣,你已經對 Flutter 的 layout 有概念了,接下來請點開下面兩個 Widgets 的軍火庫網址:

這裡就是 Flutter Widget 軍火庫,各位可以先瀏覽一下就好,試試找找看 text widget 在哪裏?哇,原來 Flutter 有那麼多 widget,layout 也有那麼多種 widget,頭很大,怎麼記得起來,沒關係,常用的不多啦,如下:

標準 widgets

到處都可以用!

  • Container: 如果你熟悉 HTML 的 style,這個就很像一個 DOM,它就是一個容器,外面可以加上 padding、margins、borders,也可以設定 background color ......
  • GridView: 例如可以顯示 3x8 個圖。
  • ListView: 就是在 App 中很常看到的單行列,我們的 MyHomePage 就要用它。
  • Stack: 將 widget 疊起來。
Material widgets

只能用在 Material Design 中,Material Design 是 Google 開發的一種設計語言,Flutter 也是 Google 創立的,自然 Flutter 也會全力支援 Material design,所以 Flutter 有一大堆 Material widgets,請點開這個 Material Widgets 的網頁看看,這也是為什麼在 Flutter 中,Material design 是最多人用的。最常用的有以下兩個,我們也會用到其中的 Card Widget。

  • Card: Material design 的卡片的設計元件。
  • ListTile: Material design 的三行式的設計元件。


需求式學習法:從需求開始學起

我們不可能把所有的 Widget 都背起來,學習就是要從需求開始,我們現在的需求就只要把「快樂錄音機 Happy recorder」的畫面做出來就好。

建立測試資料

我們要做動態名稱,當然要先有動態名稱資料庫,照理,開發程式當然要先做好資料庫分析,看看要怎麼存取,但是我們還在學習,還不會做資料庫,所以就先做一個簡單的測試資料庫。

我們的測試資料格式很簡單,就是一個 AudioRec Class,還記得我們之前談過的「Flutter 程式寫作風格與管理」嗎?沒錯,這個就是屬於資料分類,要放在 models 目錄裡,而其檔案名稱要用小寫底線,我們新增了以下這個程式:

libs/models/audio_rec.dart:

class AudioRec {
  final String title;
  final String description;

  AudioRec(this.title, this.description);
}

// Generate test data
List audioRec = List.generate( 20, (i) => AudioRec(
    'Audio $i',
    'An audio description for audio $i title',
  ),
);
  • 第 1 行:還記得 class 名稱要用大駱駝嗎!?
  • 第 2 & 3 行:這個 AudioRec 資料結構很簡單,就兩個欄位:title 跟 description 字串;
  • 第 9~13 行:我們用 AudioRec 的資料結構,建立了一個名叫 audioRec 的 List 變數,別忘了變數名稱是小駱駝。裡面的 List.generate 就是一個 Dart List 內建的 constructor,用它來建立了一個 AudioRec List 簡單又方便,裡面有 20 筆資料。

好啦,就這樣,我們的測試資料建好了。

別忘了要 import 到用到的地方,兩個 screen 都要,因為它們都會用到 audioRec。

import 'package:happy_recorder/models/audio_rec.dart';


更新 MyHomePage screen 畫面 

我們先從 MyHomePage screen 開始改起,其實仔細一看,就只是把原來的中間 body 的部分改了,如果你之前有乖乖聽話的去喵了一眼 Flutter 的 Widget 列表網頁,你可能就會發現,原來我們要做的 MyHomePage 音檔列表,不過就是一棵小小的 Widget 樹,如下圖:

我已經把每一個 Widget 都附上說明文件連結了,各位可以點開來看看,每一個 Widget 都有範例,有些甚至有影片介紹,所以很容易懂的。

接下來就是修改 my_home_page.dart 的程式了,我們不在需要原來的 _counter 及 _incrementCount() 了,就把它們 remark 或刪除吧,如下圖:

libs/screens/my_home_page.dart:

class _MyHomePageState extends State<MyHomePage> {
  //int _counter = 0;
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

  //void _incrementCounter() {
  //  setState(() {
  //    _counter++;
  //  });
  //} // _incrementCounter()

再來,就是修改其中的 body 部分:

/* body: Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Text(
        'You have pushed the button this many times:',
      ),
      Text(
        '$_counter',
        style: Theme.of(context).textTheme.headline4,
      ),
    ],
  ),
), */
body: ListView.builder(
  itemCount: audioRec.length,
  itemBuilder: (context, index) {
    return Card(
      child: ListTile(
        title: Text(audioRec[index].title),
        subtitle: Text(audioRec[index].description),
        trailing: Icon(
          Icons.play_arrow,
          color: Colors.blueAccent,
          semanticLabel: 'Play $audioRec[index].title audio',
        ),
      ),
    );
  },
),
  • 把原來的第 1~14 行換成新的第 15~30 行內容。
  • 第 15 行:就是建立一個 ListView,
  • 第 16 行:itemCount 告訴 Flutter 這個 List 有幾個,audioRec.length 就會傳回 List 的數目。
  • 第 18 行:List 中包著一個 Card widget。
  • 第 19 行:Card 只能有一個小孩,它的小孩是一個 ListTile widget,ListTile 基本上就是為了 ListView 跟 Card 量身定做的,三個一起使用很方便,一下就把我們要的介面做出來了,更多的用法可以參考一下它的文件
  • 第 22~24 行:使用 Google 的 material design icon,還改了顏色。
  • 第 25 行:隨手建立一個 semanticLabel,讓盲人更容易使用。

好了,你可以試試看 run 一下,鐺鐺,你應該可以看到下面的畫面出現了:

很不錯,短短的幾行,我們就建立了一個 ListView。在我們繼續下一個 screen 前,我們來介紹一些很重要的資源及工具:


Google Fonts Icons

Flutter 有一個巨大的 icons 軍火庫,藏在 Google Fonts 的 icons 裡,隨時要用,只要在網頁上 Google:「google font icon」,你就可以找到你要的 icon 了,它很好用,連 Flutter 的程式碼都已經寫好等你來用。



Widget 太多層了,好多括弧啊!

Flutter 中的 Widget 包來包去,你剛剛如果不是複製貼上而是自己手打輸入,你就一定會覺得寫的好辛苦,有沒有覺得像掉進括弧的無限迴圈中?沒錯,所以有人稱它是括弧地獄了,這時你就要善用工具了,Android Studio 深知民間疾苦,所以它有特異功能,可以幫你把任一個 Widget 包起來或是移除,秘笈如下:

  1. 先點開 Flutter Outline,它在 Android Studio 的最右邊,對,就是圖片中的那個極右直排的東西,
  2. 再點選你要包起來或是刪除的 widget,
  3. 選一個最接近的動作來改,鐺鐺,快速又不會錯的套圈圈!


Flutter devTools

當你在做 Layout 時,別忘了 Flutter 有一個很好用的工具:devTools,它一定要 run 執行程式後,才可以把開啟,它就在 Android Studio Run console 的旁邊,如下圖的中間:

run 了以後,就可以打開 Flutter devTools

這時瀏覽器上就會自動打開 devTools 的頁面,你應該可以看到以下的圖:

這是一個很新的玩具,把它跟模擬器放在一起,你就可以看到每一個 Widget 的 layout,兩邊都點點玩玩看:

  • 點選「Slow Animations」:可以讓你看慢動作 screen 切換動畫,
  • 查看每個 Widget 的 Layout,我們現在就可以看看我們的新 Layout,
  • 打開「Select Widget Mode」,再操作一下模擬器,Layout 的線就出來了。
  • 點選效能,記憶體用量等等的功能看看,看不懂沒關係,知道有這麼一個東西就好。

你的畫面可能跟我的圖不一樣,我現在使用的版本是 DevTools 2.2.4,它還在開發中,現在是有些限制,不過未來的功能一定會更強大,這個工具會幫助你進一步的知道每一個 widget 的大小與位置,這很重要,是幫助 layout 抓蟲的利器。


更新 AudioSession screen 畫面 

這一個 screen 的程式改變可就大了,我試著依照原先 UI 構圖去做 screen UI,但是遇到很大的挑戰,問題主要是發生在原先 UI 構圖下面的兩個按鈕,我們先來回顧一下原先的 UI 構圖:

我花了兩個小時,試了以下幾種不同的方法,可是都遇到困難,各位目前不需要了解困難在哪裡,我要說的重點是:UI 可以很簡單的畫出來,但是真要用程式刻出來,就要考慮花的時間值不值得了。

表面上,這是我所遇到的問題如下,大家可以快速的瀏覽一下,不需要花時間糾結:

  • 使用 BottomNavigationBar 來做這兩顆按鈕:但是它的原始設計是一個導覽列,所以在任何時間,一定會有一個按鈕是被按下 active 的,這不合乎我們的需求。
  • 使用 floatingActionButton:當有兩個 floatingActionButtons 時,它的 UX 會跟 MyHomePage 的 Screen UX 不一致。(其實是可以用,只是個人有點不喜歡。)
  • 用 Stack:可是按鈕凸出的部分會被遮蓋。

其實真正的問題是:

  • 我對每一個 Flutter 的 widget 並不熟悉,這也是 Flutter 的開發經驗的價值所在,每個 widget 看似簡單,但是都有它的主要用途與限制,唯有用過才會真正的了解。
  • UI 構圖天馬行空,任何的 UI 一定可以刻的出來,只是花多少時間而已,設計者如果能選用 Flutter 已經有的 Widget,也知道每一個 Widget 的限制,就可以大幅減少開發的時間。

好啦,我承認,我 UI 構圖天馬行空,亂畫的結果就是浪費很多時間嘗試,還好我是在學習,就當作學到失敗的經驗吧。

這是後來做出來的 UI⋯⋯ 其實我覺得比原來的好看,我想使用者可能根本也感覺不出來差別在哪裡⋯⋯

在公佈程式碼前,各位讀者其實你們已經有足夠的功力與武器了,要不要自己先試試看,自己試試寫程式,把上面的 UI 刻出來,真的不想動手,那至少也要想想,這個 UI 上面到底有那些 Widgets,它是怎麼排列的。

.

.

.

.

好啦,下面就是我的程式碼,因為幾乎全改了,所以就一次全上:

lib/screens/audio_session.dart

import 'package:flutter/material.dart';
import 'package:happy_recorder/models/audio_rec.dart';

class AudioSession extends StatefulWidget {
  @override
  _AudioSessionState createState() => _AudioSessionState();
}

class _AudioSessionState extends State<AudioSession> {
  int _selectedBottomBarItemIndex = 0;

  void _onBottomBarItemTapped(int index) {
    setState(() {
      _selectedBottomBarItemIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Record"),
        // automaticallyImplyLeading: false, // not display <- back btn
      ),
      body: Container(
        padding: const EdgeInsets.all(5.0),
        color: Colors.lightBlueAccent,
        child:
          Column(
            children: [
              Card(
                child: ListTile(
                  title: Text(audioRec[3].title),
                  subtitle: Text(audioRec[3].description),
                ),
              ),
              Container(
                width: double.infinity, // make it max width,
                padding: const EdgeInsets.all(10.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    // Dart 2.3 以上才支援
                    if (_selectedBottomBarItemIndex != 0)
                      Text(
                        "Bottom bar $_selectedBottomBarItemIndex selected",
                      )
                    else
                      Text("nothing selected"),
                    Text('Audio info'),
                  ],
                ),
              ),
            ],
          ),
      ),
      bottomNavigationBar: Container(
        height: 110,
        padding: const EdgeInsets.only(top: 10.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            Column(
              children: [
                Ink(
                  decoration: const ShapeDecoration(
                    color: Colors.blueAccent,
                    shape: CircleBorder(),
                  ),
                  child: IconButton(
                    icon: const Icon(Icons.record_voice_over),
                    iconSize: 38,
                    color: Colors.white,
                    onPressed: () {
                      _onBottomBarItemTapped(1);
                    },
                  ),
                ),
                Text(
                  'record',
                  style: TextStyle(color: Colors.blueAccent.withOpacity(0.8)),
                ),
              ],
            ),
            Column(
              children: [
                Ink(
                  decoration: const ShapeDecoration(
                    color: Colors.blueAccent,
                    shape: CircleBorder(),
                  ),
                  child: IconButton(
                    icon: const Icon(Icons.stop),
                    iconSize: 38,
                    color: Colors.white,
                    onPressed: () {
                      Navigator.pop(context);
                    },
                  ),
                ),
                Text(
                  'stop',
                  style: TextStyle(color: Colors.blueAccent.withOpacity(0.8)),
                ),
              ],
            ),
          ],
        ),
      )
    );
  }
}
  • 第 4 行:把原來的 StatelessWidget 換成 Stateful 了,主要是為了未來畫面及按鈕可以更新變化,而且你知道將 StatelessWidget 換成 StatefulWidget 有多簡單嗎?在 Android Studio 會按右鍵就好:

  • 第 9 行:有 StatefulWidget 就會有相對應的 State,這我們已經知道了;
  • 第 12~16 行:就是 setState() 的動作,我們用了一個變數來記錄那個按鈕按下;
  • 第 19 行:以下就是我們的 UI 了。
  • 第 25~56 行:是畫面中間,也就是 body 部分的 Layout。 
  • 第 57~109 行:是畫面下面,也就是 bottomNavigationBar 部分的 Layout。

好啦,介紹完畢。

啥?就這麼簡單?是的,這總共 112 行的程式中,幾乎都是 Layout 的 Widget,而且各位只要真的很努力、很專心、很細心的看,就會發現不過都是一個包著一個的 widget,可是,說真的,好「難」看,一堆括弧,好啦,各位只是忘記了你們手上的武器了,而且有兩的,一個是 Andriod Studio 的 Flutter Outline,另一個是 devTools,我們都介紹過了,就讓我們打開 devTools 吧。

先看一下 body 的 Layout:

如同我上面介紹的方法,把模擬器跟 devTools 放在一起肩並肩,把「Select Widget Mode」按下,你就可以看到每一個 widget 在哪裏,還有它的爸媽小孩。

上圖中,我們點選了 body 裡的 Cloumn,我們從 devTools 的左邊馬上就可以看到:

  • 這個 Column 是包在一個 Container 裡面,不這樣包不行,因為 Container 只能有一個小孩,一定要包一個可以有很多小孩的 widget 才可以放很多 widget 在裡面。
  • 這個 Column 下面有兩個小孩:
    • 一個是 Card,Card 的使用方式已經在 MyHomePage 中介紹過了,這裡一模一樣。
    • 一個是 Container,它也很簡單,就包一個 Column 讓它有兩個 Text 小孩。
  • 點選的 body Cloumn 也同時在模擬器被標示了,所以可以清楚的知道它的位置。
  • 上圖的中間還畫出了這個 Cloumn 的 layout 架構,可以清楚的知道它的大小與排列方式。
  • 有注意到所有的 Widget 都是大寫開頭的嗎?是的,跟 class 一樣。

我們現在回頭來看程式,是不是清楚易懂多了,有沒有豁然開朗的感覺,基本上,這些 widget 都幾乎不用設定就能用了,我只做了一些排列與大小設定,說明如下:

  • 顏色是用 Colors 來設定,這是 material design 已經調好色的顏色,也可以用 theme。
  • 很多小孩時,怎麼排隊是用 Main/Corss Axis,看下圖就懂了,Row 跟 Column 相反,常用的排列選項有 center、start、end、spaceAround、spaceEvenly。這些都不用死記啦,上網查就好: 
  • 程式中的 Container 裡面有 padding: const EdgeInsets.all(x.0),如果你寫過網站,就知道這是跟 DOM 一樣的東西,如下圖,container 可以設定 margin、padding,這也是為什麼要用 Container 的主因,大小設定要用 EdgeInsets:四邊都一樣就是 all(),兩邊一樣就是 symmetric(vertical: x),單邊就是 only(left: x):(幫助記憶:Edge 是邊邊角角 + Inset 是插圖 = 插圖的邊邊角角)

好啦,body 講完了,我們來看一下 bottomNavigationBar 的 Layout:

跟上面一樣,把模擬器跟 devTools 放在一起看,我們這次用「怎麼選擇 widget 的角度」來分析,我在上圖中點選了 bottomNavigationBar 裡的 Row,沒特別理由,就只是好介紹而已:

  • 整個 bottomNavigationBar 是一大塊,因為裡面要橫著排兩個按鈕,所以必須放一個橫向的 Row 結構,這樣才能橫放兩個按鈕在裡面,我們又需要在按鈕邊上留空白,所以需要用 Container 包一個 Row。這兩個按鈕我們想要平均排列,所以用:mainAxisAlignment: MainAxisAlignment.spaceAround,Flutter 有好幾種排列方式,我們下面詳細說明。
  • 每一個按鈕的上面是 button,下面是文字,所以是一個垂直的按鈕+文字的組合,自然這是要用直向的 Column 來排列。
  • 每一個按鈕的上方是個 IconButton,但是我們要將顏色設定為藍色,形狀變成圓形,所以我們將 IconButton 包在 Ink 裡,Ink 是個很有趣的東西,顧名思義它就是個墨水,有墨水就可以有顏色,還可以有水波紋動畫,更有趣的是,它可以設定形狀,我們就是用 CircleBorder() 來做出我們要的圓形按鈕,要改成方形可以用 RoundedRectangleBorder(),這兩個都是 painting library 成員,painting library 可不止這兩個功能,它收集了一大堆 Flutter 繪圖功能,舉凡大小、顏色、形狀、外框等等都歸他管,不需要一次就想把 Flutter 的 painting 全部搞懂,不實際,依照你要做出的形狀,再從網路找答案,這才是正確方式。
  • Icon 及 ShapeDecoration 都宣告為使用 const,const 是常數的意思,不用也可以,各位可以拿掉試試,一樣會動,那為什麼要宣告使用 const 呢?主要原因是提高執行效能,宣告為 const 的 widget 或變數都只有第一次會被「計算」,所以不管這個 const 以後在程式裡被執行了幾次,以後都不會再被計算了,這樣當然可以提高效能,知道這樣就好了。如果你是讀電腦科學的,一定要知道背後的魔鬼,其實宣告 const 就是 Complier 變數,跟 C 語言一樣,在 compile 編譯時就會被算好,所以不管 Flutter 是 JIT 還是 AOT,反正都只會被算一次啦。另外,如果有人對 Const,Static 及 Final 的不同有興趣,這篇文章寫得很好,有空可以讀讀。
  • Ink 中的水波紋是個很有趣的東西,我們先不用搞懂怎麼做,但是要先知道 Flutter 有這個功能,在模擬器上還很容易漏看,把 devTools 中的慢動作打開(就是那個 Slow Animations 啦),再點 record 那個按鈕,你就會看到如下圖的水波紋了!

MainAxisAlignment

話說 Flutter 的文件也很奇怪,寫了一大堆關於 MainAxisAlignment 的排列方式,但是就是沒有一個整合的圖例,到底 MainAxisAlignment.XXXX 會怎麼排列,好吧,那就只有辛苦我自己來做一個圖說明了:

左邊就是 MainAxisAlignment.XXXX,右邊就是相對應的排列方式,用圖一點都不難懂,用寫的就像武功秘笈,真搞不懂 Google Flutter team 在想什麼,不過我想也沒人會記得啦,寫程式時,大家都會試試看,反正又不是什麼難事。話又說,CrossAxisAlignment 也很像,不過只有:start/center/end 及 stretch 四種,行為跟 MainAxisAlignment 一樣啦,只有 stretch 不同,它會膨脹填滿,嘿嘿嘿,怎麼膨脹填滿呢?各位有機會試試就知道,反正又不會咬人。

程式中的其他部分應該都很好懂,就算還有我沒提到的,查一下網路應該也很容易找到答案,恭喜各位能堅持到這裡,各位應該也對 Flutter 的 layout 有初步的了解了,大家可以把程式亂改一下,做一些簡單的 UI 變化,反正又不會爆炸咬人,試完後倒退回原來的程式碼就好了,很好玩的。

在結束這篇已經太長的文章前,我還必須要強調一大點,這篇只是開始,要用 Flutter 做一個好的 layout 還有很多事情要考量,例如:

  1. 要整合 style / theme 的設定:想像一下你的老闆有一天突然要你把所有的「按鈕」換顏色,你的程式如果每一個按鈕都是個別設定,那你可能會改到很氣,而實務上,這可能天天發生,所以就不只是生氣,而是厭世了。
  2. 多國語言/I18n:一開始就支援多國語言是絕對不會有壞處的,你早晚要做,爲什麼不一開始就做呢?
  3. Responsive / Adaptive:Responsive 指的是 App 對螢幕大小及方向的 layout 變化,有點像是網頁上的 RWD。Adaptive 則是指 App 對不同輸入方式的支援,例如:如何讓同一個 App 在有觸控跟沒有觸控的裝置上使用,當你的 App 要在更多不同的裝置上執行時,Adaptive 也會更困難。

所以做 layout 時,要一直記得:Layout 是給人用的,Layout 會一直被改,Layout 要支援不同的裝置,Layout 要能跟著螢幕轉動變化,Layout 要能出國比賽,⋯⋯

再次恭喜各位已經「認識」Flutter 的 layout 了。


最後的程式碼,請自行參考本書:程式碼備份中的 Code milestone 1

 

 




喜歡作者的文章嗎?馬上按「關注」,當作者發佈新文章時,思書™就會 email 通知您。

思書是公開的寫作平台,創新的多筆名寫作方式,能用不同的筆名探索不同的寫作內容,無限寫作創意,如果您喜歡寫作分享,一定要來試試! 《 加入思書》

思書™是自由寫作平台,本文為作者之個人意見。


文章資訊

本文摘自:
Categories:
Tags:
Date:
Published: 2021/06/07 - Updated: 2021/07/11
Total: 6050 words


分享這篇文章:
關於作者

很久以前就是個「寫程式的」,其實,什麼程式都不熟⋯⋯
就,這會一點點,那會一點點⋯⋯




參與討論!
現在就加入《思書》,馬上參與討論!
《思書》是一個每個人的寫作與論壇平台,特有的隱私管理,用筆名來區隔你討論內容,讓你的討論更深入,而且免費。 趕快來試試!
還未加入《思書》? 現在就登錄! 已經加入《思書》── 登入


×
登入
申請帳號

需要幫助
關於思書

暗黑模式?
字體大小
成人內容未過濾
更改語言版本?