介紹
動態切換主題功能,使用Provider狀態管理完成
學習本章內容,必須掌握Provider狀態管理,如果有不太理解的同學,請開啟我的主頁搜尋 Provider 觀看後再返回觀看本部落格
主題樣式大全
factory ThemeData({
Brightness brightness, // 應用整體主題的亮度。用於按鈕之類的小部件,以確定在不使用主色或強調色時選擇什麼顏色。
MaterialColor primarySwatch,// 定義一個單一的顏色以及十個色度的色塊。
Color primaryColor, // 應用程式主要部分的背景顏色(toolbars、tab bars 等)
Brightness primaryColorBrightness, // primaryColor的亮度。用於確定文字的顏色和放置在主顏色之上的圖示(例如工具欄文字)。
Color primaryColorLight, // primaryColor的淺色版
Color primaryColorDark, // primaryColor的深色版
Color accentColor, // 小部件的前景色(旋鈕、文字、覆蓋邊緣效果等)。
Brightness accentColorBrightness, // accentColor的亮度。
Color canvasColor, // MaterialType.canvas 的預設顏色
Color scaffoldBackgroundColor, // Scaffold的預設顏色。典型Material應用程式或應用程式內頁面的背景顏色。
Color bottomAppBarColor, // BottomAppBar的預設顏色
Color cardColor, // Card的顏色
Color dividerColor, // Divider和PopupMenuDivider的顏色,也用於ListTile之間、DataTable的行之間等。
Color highlightColor, // 選中在潑墨動畫期間使用的突出顯示顏色,或用於指示選單中的項。
Color splashColor, // 墨水飛濺的顏色。InkWell
InteractiveInkFeatureFactory splashFactory, // 定義由InkWell和InkResponse反應產生的墨濺的外觀。
Color selectedRowColor, // 用於突出顯示選定行的顏色。
Color unselectedWidgetColor, // 用於處於非活動(但已啟用)狀態的小部件的顏色。例如,未選中的核取方塊。通常與accentColor形成對比。也看到disabledColor。
Color disabledColor, // 禁用狀態下部件的顏色,無論其當前狀態如何。例如,一個禁用的核取方塊(可以選中或未選中)。
Color buttonColor, // RaisedButton按鈕中使用的Material 的預設填充顏色。
ButtonThemeData buttonTheme, // 定義按鈕部件的預設配置,如RaisedButton和FlatButton。
Color secondaryHeaderColor, // 選定行時PaginatedDataTable標題的顏色。
Color textSelectionColor, // 文字框中文字選擇的顏色,如TextField
Color cursorColor, // 文字框中游標的顏色,如TextField
Color textSelectionHandleColor, // 用於調整當前選定的文字部分的控制程式碼的顏色。
Color backgroundColor, // 與主色形成對比的顏色,例如用作進度條的剩餘部分。
Color dialogBackgroundColor, // Dialog 元素的背景顏色
Color indicatorColor, // 選項卡中選定的選項卡指示器的顏色。
Color hintColor, // 用於提示文字或佔位符文字的顏色,例如在TextField中。
Color errorColor, // 用於輸入驗證錯誤的顏色,例如在TextField中
Color toggleableActiveColor, // 用於突出顯示Switch、Radio和Checkbox等可切換小部件的活動狀態的顏色。
String fontFamily, // 文字字型
TextTheme textTheme, // 文字的顏色與卡片和畫布的顏色形成對比。
TextTheme primaryTextTheme, // 與primaryColor形成對比的文字主題
TextTheme accentTextTheme, // 與accentColor形成對比的文字主題。
InputDecorationTheme inputDecorationTheme, // 基於這個主題的 InputDecorator、TextField和TextFormField的預設InputDecoration值。
IconThemeData iconTheme, // 與卡片和畫布顏色形成對比的圖示主題
IconThemeData primaryIconTheme, // 與primaryColor形成對比的圖示主題
IconThemeData accentIconTheme, // 與accentColor形成對比的圖示主題。
SliderThemeData sliderTheme, // 用於呈現Slider的顏色和形狀
TabBarTheme tabBarTheme, // 用於自定義選項卡欄指示器的大小、形狀和顏色的主題。
CardTheme cardTheme, // Card的顏色和樣式
ChipThemeData chipTheme, // Chip的顏色和樣式
TargetPlatform platform,
MaterialTapTargetSize materialTapTargetSize, // 配置某些Material部件的命中測試大小
PageTransitionsTheme pageTransitionsTheme,
AppBarTheme appBarTheme, // 用於自定義Appbar的顏色、高度、亮度、iconTheme和textTheme的主題。
BottomAppBarTheme bottomAppBarTheme, // 自定義BottomAppBar的形狀、高度和顏色的主題。
ColorScheme colorScheme, // 擁有13種顏色,可用於配置大多陣列件的顏色。
DialogTheme dialogTheme, // 自定義Dialog的主題形狀
Typography typography, // 用於配置TextTheme、primaryTextTheme和accentTextTheme的顏色和幾何TextTheme值。
CupertinoThemeData cupertinoOverrideTheme
})
複製程式碼
實戰
引入包
由於用到了 Provider狀態管理 跟 shared_preferences資料持久化 所以先引入一下,開啟Flutter專案跟目錄的pubspec.yaml
shared_preferences: ^0.5.12+4
provider: ^4.3.2+3
複製程式碼
新增全域性主題顏色
要設定主題,首先要知道自己的app需要多少種主題(顏色),這個主題我們要定義一個全域性list出來
在專案跟目錄 lib 下新建 global資料夾 在資料夾下新建 global_theme.dart檔案 用來儲存全域性主題顏色,程式碼如下
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
final List<Color> themeList = [
Colors.blue,
Colors.black,
Colors.red,
Colors.purple,
Colors.indigo,
Colors.yellow,
Colors.green,
];
複製程式碼
我們就定義這幾種顏色,大家如需要更多,可以自己定義
新增全域性主題狀態
由於我們的主題是動態的,所以改變後需要通知介面重新整理,這裡就要用到狀態管理了,如果不熟悉 Provider狀態管理 請先學習後在看
在專案跟目錄 lib 下新建 provider資料夾 在資料夾下新建 theme_provider.dart檔案 用來儲存當前主題狀態值,如果改變後,則會自動重新整理用到此狀態的ui,這就是狀態管理的好處
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:zhong_mei_utils_flutter/global/Global.dart';
//型別隨便取,繼承ChangeNotifier
class ThemeProvider with ChangeNotifier {
Color _color = themeList.first;//預設是我們設定的主題顏色列表第一個
void setTheme(int index) {//給外部提供修改主題的方法
print(index.toString());
_color = themeList[index];
notifyListeners();
}
Color get color => _color;//獲取當前主題
}
複製程式碼
主題狀態類建立好了,新增到專案裡,讓專案管理此狀態 修改main.dart
return runApp(MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => UserProvider()),
ChangeNotifierProvider(create: (context) => ThemeProvider()),
],
child: MyApp(),
));
複製程式碼
主題狀態設定完畢,接下來使用全域性主題狀態
一般來說,一個app只有一個MaterialApp,我們主題設定在MaterialApp,所以找到我們的MaterialApp,新增以下程式碼
return MaterialApp(
theme: ThemeData.light().copyWith(
primaryColor: Provider.of<ThemeProvider>(context).color,
buttonTheme: ButtonThemeData(
buttonColor: Provider.of<ThemeProvider>(context).color,
textTheme: ButtonTextTheme.normal,
),
),
複製程式碼
上段程式碼設定了app主題顏色跟按鈕顏色使用狀態管理裡的顏色,而按鈕文字顏色則是高亮,如果按鈕背景色是黑色,那麼按鈕文字顏色自動白色
主題修改介面
在 lib 下新增 settings_theme.dart.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:weui/icon/index.dart';
import 'package:zhong_mei_utils_flutter/global/Global.dart';
import 'package:zhong_mei_utils_flutter/provider/ThemeProvider.dart';
class SettingsView extends StatefulWidget {
@override
_SettingsViewState createState() => _SettingsViewState();
}
class _SettingsViewState extends State<SettingsView> {
int _index;//我們當前主題設定的是 全域性主題列表中的第幾個?
@override
void initState() {
super.initState();
loadData();//查詢當前持久化資料
}
void loadData() async {
SharedPreferences sp = await SharedPreferences.getInstance();//獲取持久化操作物件
setState(() {
_index = sp.getInt("theme") ?? 0;//查詢持久化框架中儲存的theme欄位,如果是null則預設是0
});
}
Widget _itemBuilder(BuildContext context, int index) {
return GestureDetector(
child: Container(
width: double.infinity,
height: 50,
margin: EdgeInsets.only(top: 10, bottom: 10),
decoration: BoxDecoration(
color: themeList[index],
borderRadius: BorderRadius.all(Radius.circular(20)),
),
child: _index != index
? Text("")//如果沒選中則無東西
: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Icon(
WeIcons.hook,//如果選中了則給一個圖示,這個圖示是一個對勾,大家可以自己找
color: Colors.white,
),
SizedBox(width: 16),
],
),
),
onTap: () async {
SharedPreferences sp = await SharedPreferences.getInstance();
sp.setInt("theme", index);//點選後,修改持久化框架裡的theme資料庫
Provider.of<ThemeProvider>(context, listen: false).setTheme(index);//修改全域性狀態為選中的值
setState(() {
_index = index;//設定當前介面選種值,重新整理對勾
});
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("設定主題"),
centerTitle: true,
elevation: 10,
),
body: Padding(
padding: EdgeInsets.all(20),
child: Scrollbar(
child: ListView.builder(
itemBuilder: _itemBuilder,
itemCount: themeList.length,
),
),
),
);
}
}
複製程式碼
這時候已經完成了,但是有一個問題,就是我們正常使用都沒有問題,但是退出app再進來,我們沒有讀取持久化儲存的主題,所以又變預設第一個顏色
這時候我們想讀取資料看中的主題,先了解一個概念 app啟動時,都有白屏,一般開發者會做一個啟動圖,來掩蓋白屏,我們這裡需要知道,白屏是因為app在載入一些東西,還沒有渲染介面,那麼我們載入主題也應該在app啟動前載入
否則會出現,介面渲染了,但是主題沒讀取出來,造成主題先顯示預設顏色,才顯示我們設定的主題顏色
那麼flutter專案的main.dart中有一段程式碼
return runApp(MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => UserProvider()),
ChangeNotifierProvider(create: (context) => ThemeProvider()),
],
child: MyApp(),
));
複製程式碼
可以看到 child: MyApp() 之前是沒有ui渲染操作的,所以我們要在 return runApp之前讀取到主題,並且生成ThemeProvider物件,程式碼改動如下
int theme;//新增一個變數,接收資料庫讀取返回
void main() async {//由於讀取資料庫需要非同步,所以加上async
await loadData();//讀取資料庫儲存的主題
ThemeProvider themeProvider = ThemeProvider();//new一個主題狀態物件
themeProvider.setTheme(theme ?? 0);//給物件設定成我們讀取的主題
return runFxApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => UserProvider()),
ChangeNotifierProvider(create: (context) => themeProvider),//這裡ThemeProvider() 改成 themeProvider物件
],
child: MyApp(),
),
// onEnsureInitialized: (info) {},
enableLog: false,
uiBlueprints: uiSize,
);
}
void loadData() async {
SharedPreferences sp = await SharedPreferences.getInstance();
theme = sp.getInt("theme") ?? 0;//如果讀取是空則返回0
}
複製程式碼
經過以上改動,我們完成了讀取持久化儲存的主題,並且在app啟動時預先載入了主題
實現效果演示
可以看到,啟動專案到登陸頁面的時候主題也是最新的了,這裡我們用到了持久化操作