用 Flutter 開發一個 Android App 吧 - Day4

Build Android app with Flutter - Day4

Posted by Bobson Lin on Friday, September 13, 2019

Day 4. 首頁、路由

本系列同步發表在 第11屆鐵人賽

中秋節到拉~ 先祝大家中秋快樂~

好了,廢話不多說,進入今天正題囉~

主頁面 - 首頁

lib/pages/home.dart

import 'package:flutter/material.dart';

// 主頁面
class MainPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 4,
      child: Scaffold(
        appBar: AppBar(
          titleSpacing: 0.0,
          title: TabBar(
            labelPadding: EdgeInsets.zero,
            tabs: <Widget>[
              Tab(text: "Home"),
              Tab(text: "Repo"),
              Tab(text: "Activity"),
              Tab(text: "Issues"),
            ],
          ),
          actions: <Widget>[
            IconButton(
              icon: Icon(Icons.search),
              onPressed: () {},
            )
          ],
        ),
        body: TabBarView(
          children: <Widget>[
            HomePage(),
            Text("Repo"),
            Text("Activity"),
            Text("Issues"),
          ],
        ),
        drawer: Drawer(
        ...()
        ),
      ),
    );
  }
}

// 首頁
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: ListView(
        children: <Widget>[
          Container(
            child: Divider(
              height: 8.0,
              color: Colors.grey[200],
            ),
            color: Colors.grey[200],
          ),
          ListTile(
            dense: true,
            title: Text("Hackernews Top"),
            trailing: Icon(Icons.chevron_right),
            onTap: () {},
          ),
          Divider(
            height: 0.0,
          ),
          ListTile(
            title: Text(
                "Pre-industrial workers had a shorter workweek than today's"),
            subtitle: Text("by jammygit 2 hours ago | 18 comments"),
            onTap: () {},
          ),
          
          
          ...(中間省略)
          
          ListTile(
            title: Text("MisterBooo / LeetCodeAnimation"),
            subtitle: Text("Java   38,443   6,483"),
            onTap: () {},
          ),
        ],
      ),
    );
  }
}

這邊程式碼有點多,沒關係,先來簡單看個分解圖~

day4-1.png

這邊登入後會進入主畫面(MainPage),根據 TabBar 四個 Tab 分成四個頁面,首頁(HomePage)、倉庫頁(RepoPage)、近況頁(ActivityPage)、議題頁(IssuePage)

可以注意到,主畫面跟登入頁一樣使用 Scaffold 這個 Widget,主要搭配 MaterialApp 作使用,它包含了

  • Material Design 的元件(如屬性 appBar, bottomAppBar, floatingActionButton…)
  • 互動性的元素(如 drawers, snack bars, and bottom sheets)
  • 一定程度的可客製化(如 theme, l10n)

對我這個沒什麼設計感的工程師可說是一大便利呢~

一樣起手都是 StatelessWidget,上圖特別我用紅色框起來都是可以對應程式碼裡寫的 Widget,

目前都只是再拉排版的動作,程式碼看起來不免看起來臃腫
之後都是需要慢慢重構,讓程式碼好維護管理

欸!? 怪怪的

眼尖(PM?)的同學可能看到有些地方 怪怪的

柯P-怪怪的

對,沒錯上面的 TabBar 每個 Tab 根本文字展示不完全,而且離兩邊圖示有些落差…

這時候就可以開啟 Flutter debug 工具或 Devtools
(在 VSCode 中可以 Ctrl+Shift+p 直接找 Flutter: Toggle Debug Painting 或 Dart: Open DevTools)

day4-2.png

可以清楚看到 Layout 長什麼樣子,加上查詢文件後發現有兩處可調整

  1. Tab 根本文字展示不完全 => TabBar.labelPadding: EdgeInsets.zero,
  2. 離兩邊圖示有些落差 => AppBar.titleSpacing: 0.0

修改完結果

day4-3.gif

路由 (Route)

好了,到現在我們有兩個頁面了,那要如何作跳轉呢?

稍微用簡單的圖來表示

LoginPage (/login) <== [Navigator] ==> MainPage (/home)

Flutter 裡在 Route 切換時會有個中間人 Navigator 來作導覽。
而導覽機制跟 Stack 一樣用 pop/push 操作,詳細可以參考這篇 Medium 文章(Flutter: Push, Pop, Push)

routes.dart

class GitmeRebornRoutes {
  static const root = "/";
  static const login = "/login";
  static const home = "/home";
}

個人會習慣另外定義一個 Routes 類別,來對應路由名稱。

materialapp_variables

接下來簡單談一下 MaterialApp 裡是怎麼設定路由的。從上圖 紅框 中可以看到是影響頁面轉換的屬性。

主要判斷頁面如何轉換的步驟為

  1. 若有設定 home 屬性,便認之為 / ([root]根路由)
  2. 否則查找 routes 屬性,有無對應路由設定。
  3. 否則會調用 onGenerateRoute,來判斷是否有提供對應路由的回傳。
  4. 若上述都沒成立,則會調用到 onUnknownRoute。

(翻譯自 https://api.flutter.dev/flutter/material/MaterialApp-class.html )

接下來,我修改了些原本的程式碼

lib/main.dart day4-4.jpeg

主要將 home 屬性移除,加上 routes 對應表格並且用 onGenerateRoute 來跳轉根路由到登入畫面(LoginPage)。

lib/pages/home.dart & lib/pages/login.dart day4-5.jpeg

再來,登入畫面中的登入按鈕和主畫面中的登出按鈕,當點擊他們時會使用 Navigator 導覽至新的頁面。

今日成果

day4-6.gif

好了,今日就先到這邊吧
看完可以去吃烤肉囉~