## 主題
Theme Widget可以為Material APP定義主題數據(ThemeData),Material組件庫里很多Widget都使用了主題數據,如導航欄顏色、標題字體、Icon樣式等。Theme內會使用InheritedWidget來為其子樹Widget共享樣式數據。
### ThemeData
ThemeData是Material Design Widget庫的主題數據,Material庫的Widget需要遵守相應的設計規范,而這些規范可自定義部分都定義在ThemeData,所以我們可以通過ThemeData來自定義應用主題。我們可以通過`Theme.of`方法來獲取當前的ThemeData。
> 注意,Material Design 設計規范中有些是不能自定義的,如導航欄高度,ThemeData只包含了可自定義部分。
我們看看ThemeData部分數據:
```
ThemeData({
Brightness brightness, //深色還是淺色
MaterialColor primarySwatch, //主題顏色樣本,見下面介紹
Color primaryColor, //主色,決定導航欄顏色
Color accentColor, //次級色,決定大多數Widget的顏色,如進度條、開關等。
Color cardColor, //卡片顏色
Color dividerColor, //分割線顏色
ButtonThemeData buttonTheme, //按鈕主題
Color cursorColor, //輸入框光標顏色
Color dialogBackgroundColor,//對話框背景顏色
String fontFamily, //文字字體
TextTheme textTheme,// 字體主題,包括標題、body等文字樣式
IconThemeData iconTheme, // Icon的默認樣式
TargetPlatform platform, //指定平臺,應用特定平臺控件風格
...
})
```
上面只是ThemeData的一小部分屬性,完整列表讀者可以查看SDK定義。上面屬性中需要說明的是`primarySwatch`,它是主題顏色的一個"樣本",通過這個樣本可以在一些條件下生成一些其它的屬性,例如,如果沒有指定`primaryColor`,并且當前主題不是深色主題,那么`primaryColor`就會默認為`primarySwatch`指定的顏色,還有一些相似的屬性如`accentColor` 、`indicatorColor`等也會受`primarySwatch`影響。
### 示例
我們實現一個路由換膚功能:
```
class ThemeTestRoute extends StatefulWidget {
@override
_ThemeTestRouteState createState() => new _ThemeTestRouteState();
}
class _ThemeTestRouteState extends State<ThemeTestRoute> {
Color _themeColor = Colors.teal; //當前路由主題色
@override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
return Theme(
data: ThemeData(
primarySwatch: _themeColor, //用于導航欄、FloatingActionButton的背景色等
iconTheme: IconThemeData(color: _themeColor) //用于Icon顏色
),
child: Scaffold(
appBar: AppBar(title: Text("主題測試")),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
//第一行Icon使用主題中的iconTheme
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.favorite),
Icon(Icons.airport_shuttle),
Text(" 顏色跟隨主題")
]
),
//為第二行Icon自定義顏色(固定為黑色)
Theme(
data: themeData.copyWith(
iconTheme: themeData.iconTheme.copyWith(
color: Colors.black
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.favorite),
Icon(Icons.airport_shuttle),
Text(" 顏色固定黑色")
]
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () => //切換主題
setState(() =>
_themeColor =
_themeColor == Colors.teal ? Colors.blue : Colors.teal
),
child: Icon(Icons.palette)
),
),
);
}
}
```
運行后點擊右下角懸浮按鈕則可以切換主題:

需要注意的有三點:
- 可以通過局部主題覆蓋全局主題,正如代碼中通過Theme為第二行圖標指定固定顏色(黑色)一樣,這是一種常用的技巧,Flutter中會經常使用這種方法來自定義子樹主題。那么為什么局部主題可以覆蓋全局主題?這主要是因為Widget中使用主題樣式時是通過`Theme.of(BuildContext context)`來獲取的,我們看看其簡化后的代碼:
- ```
static ThemeData of(BuildContext context, { bool shadowThemeOnly = false }) {
// 簡化代碼,并非源碼
return context.inheritFromWidgetOfExactType(_InheritedTheme)
}
```
`context.inheritFromWidgetOfExactType` 會在widget樹中從當前位置向上查找第一個類型為`_InheritedTheme`的Widget。所以當局部使用Theme后,其子樹中`Theme.of()`找到的第一個`_InheritedTheme`便是該Theme的。
- 本示例是對單個路由換膚,如果相對整個應用換膚,可以去修改MaterialApp的theme屬性。
- 緣起
- 起步
- 移動開發技術簡介
- Flutter簡介
- 搭建Flutter開發環境
- 常見配置問題
- Dart語言簡介
- 第一個Flutter應用
- 計數器示例
- 路由管理
- 包管理
- 資源管理
- 調試Flutter APP
- Dart線程模型及異常捕獲
- 基礎Widgets
- Widget簡介
- 文本、字體樣式
- 按鈕
- 圖片和Icon
- 單選框和復選框
- 輸入框和表單
- 布局類Widgets
- 布局類Widgets簡介
- 線性布局Row、Column
- 彈性布局Flex
- 流式布局Wrap、Flow
- 層疊布局Stack、Positioned
- 容器類Widgets
- Padding
- 布局限制類容器ConstrainedBox、SizeBox
- 裝飾容器DecoratedBox
- 變換Transform
- Container容器
- Scaffold、TabBar、底部導航
- 可滾動Widgets
- 可滾動Widgets簡介
- SingleChildScrollView
- ListView
- GridView
- CustomScrollView
- 滾動監聽及控制ScrollController
- 功能型Widgets
- 導航返回攔截-WillPopScope
- 數據共享-InheritedWidget
- 主題-Theme
- 事件處理與通知
- 原始指針事件處理
- 手勢識別
- 全局事件總線
- 通知Notification
- 動畫
- Flutter動畫簡介
- 動畫結構
- 自定義路由過渡動畫
- Hero動畫
- 交錯動畫
- 自定義Widget
- 自定義Widget方法簡介
- 通過組合現有Widget實現
- 實例:TurnBox
- CustomPaint與Canvas
- 實例:圓形漸變進度條(自繪)
- 文件操作與網絡請求
- 文件操作
- Http請求-HttpClient
- Http請求-Dio package
- 實例:Http分塊下載
- WebSocket
- 使用Socket API
- Json轉Model
- 包與插件
- 開發package
- 插件開發:平臺通道簡介
- 插件開發:實現Android端API
- 插件開發:實現IOS端API
- 系統能力調用
- 國際化
- 讓App支持多語言
- 實現Localizations
- 使用Intl包
- Flutter核心原理
- Flutter UI系統
- Element和BuildContext
- RenderObject與RenderBox
- Flutter從啟動到顯示