在〈頂端工具列 AppBar〉中的範例出現了 TabBar
,也就是 Flutter 中提供的分頁元件,當時只是隨便塞到 bottom
,實際上你按下各個 Tab
元件是不會動作的,因為沒有指定各個 Tab
要顯示哪個 Widget
作為分頁。
TabBar
可以透過 tabs
,設置多個 Tab
,至於各個 Tab
對應的 Widget
,可以用 TabBarView
來組織,例如,若有三個分頁內容:
TabBarView(
children: [
Book(
imgSrc: 'https://openhome.cc/Gossip/images/ACL059300.jpg',
name: 'Java SE 14 技術手冊',
),
Book(
imgSrc: 'https://openhome.cc/Gossip/images/ACL054400.jpg',
name: 'Python 3.7 技術手冊',
),
Book(
imgSrc: 'https://openhome.cc/Gossip/images/AEL022800.jpg',
name: 'JavaScript 技術手冊',
),
],
)
接著就看你要將 TabBarView
擺在哪了,如果使用 Scaffold
,通常會是擺到 body
。
TabBar
的 tabs
與 TabBarView
的 children
要能對應起來,必須透過 TabController
,如果應用程式的 UI 很單純,只會有一個 TabBar
,最簡單的方式就是使用 DefaultTabController
,這是個可以被子節點共用的 TabController
,例如:
import 'package:flutter/material.dart';
void main() => runApp(
MaterialApp(
home: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: TabBar(
tabs: [
Tab(text: 'Java'),
Tab(text: 'Python'),
Tab(text: 'JavaScript'),
],
),
),
body: TabBarView(
children: [
Book(
imgSrc: 'https://openhome.cc/Gossip/images/ACL059300.jpg',
name: 'Java SE 14 技術手冊',
),
Book(
imgSrc: 'https://openhome.cc/Gossip/images/ACL054400.jpg',
name: 'Python 3.7 技術手冊',
),
Book(
imgSrc: 'https://openhome.cc/Gossip/images/AEL022800.jpg',
name: 'JavaScript 技術手冊',
),
],
),
),
)
)
);
class Book extends StatelessWidget {
final String imgSrc;
final String name;
Book({this.imgSrc, this.name});
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: Image.network(imgSrc),
),
Text(name)
],
);
}
}
在這個例子中,因為 AppBar
的 title
接受的是 Widget
,我就直接把 TabBar
擺上去了,DefaultTabController
要設置 length
,表示可用來管理三個分頁,來看一下切換效果:
如果不想共用 TabController
的話,也可以自行建立 TabController
,例如以下的程式實作,執行結果同上,然而是自行建立 TabController
:
import 'package:flutter/material.dart';
void main() => runApp(
MaterialApp(
home: Home()
)
);
class Home extends StatefulWidget {
@override
State<StatefulWidget> createState() => _HomeState();
}
// mixin SingleTickerProviderStateMixin 的實作
// 讓這個類別能提供 Ticker,這是做動畫時需要的元件
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
// 宣告 TabController
TabController tabController;
@override
void initState() {
// 建立 TabController,vsync 接受的型態是 TickerProvider
tabController = new TabController(length: 3, vsync: this);
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: TabBar(
tabs: [
Tab(text: 'Java'),
Tab(text: 'Python'),
Tab(text: 'JavaScript'),
],
controller: tabController, // 指定 TabController
),
),
body: TabBarView(
controller: tabController, // 指定 TabController
children: [
Book(
imgSrc: 'https://openhome.cc/Gossip/images/ACL059300.jpg',
name: 'Java SE 14 技術手冊',
),
Book(
imgSrc: 'https://openhome.cc/Gossip/images/ACL054400.jpg',
name: 'Python 3.7 技術手冊',
),
Book(
imgSrc: 'https://openhome.cc/Gossip/images/AEL022800.jpg',
name: 'JavaScript 技術手冊',
),
],
),
);
}
}
class Book extends StatelessWidget {
同前一個範...略
}
基本上需注意的地方,範例程式碼中都加上了註解,至於建立 TabController
時,為什麼要指定 TickerProvider
?TickerProvider
其實是給 TabController
內部的 AnimationController
使用的,要詳細談的話,是與動畫操作有關,如果不關心動畫,就像範例這麼撰寫就可以了。
如果想知道原理的話,之後談動畫會再說明,這邊先簡單地說一下,Flutter 基本上希望能在畫面顯示時,提供每秒 60 個畫框(frame)的更新,而 TickerProvider
可以提供 Ticker
實例,每次 Flutter 在更新畫框(Frame),會呼叫指定給 Ticker
的函式,這個函式中可以操作 AnimationController
,例如更新它的 value
,動畫相關的邏輯依 AnimationController
的 value
等資訊來產生動畫。
也就是說,每次更新畫框時要做什麼,是由 Ticker
來決定,而 TickerProvider
會提供 Ticker
,TabController
需要 TickerProvider
,只是為了能實現切換分頁的動畫罷了,從這點來看,曝露其實是個有點奇怪的設計,畢竟你可能並不怎麼關心動畫,覺得預設的就足夠了,然而實作上還是要撰寫 with SingleTickerProviderStateMixin
之類的程式碼。