為了讓你的 App 更美觀,主題切換已經是一個必不可少的功能了,但如果想在傳統的 Android 和 iOS 上分別適配不同的主題相當繁瑣。但這一切,在 Flutter 中都非常容易實現。今天我們就來看看,如何在 Flutter 中給你的 App 新增換膚功能。我們要實現的效果如下:
新增依賴
在該案例中,我使用到了 provider
和 flustars
兩個庫,簡單介紹一下這兩個庫:
provider
官方推薦的狀態管理庫,相比其他狀態管理庫使用起來比較方便。
狀態管理:通俗的講,當我們想在多個頁面(元件/Widget)之間共享狀態(資料),或者一個頁面(元件/Widget)中的多個子元件之間共享狀態(資料),這個時候我們就可以用 Flutter 中的狀態管理來管理統一的狀態(資料),實現不同元件直接的傳值和資料共享。
flustars
號稱“Flutter 全網最全常用工具類”,其中包括了SpUtil
、ScreenUtil
、TimelineUtil
等常見工具類,這裡我們要使用的是SpUtil
這個部分,用於儲存使用者所選擇的主題資訊。
以上就是關於我們使用的兩個第三方庫的介紹,如果想要使用,我們需要在pubspec.yaml
檔案中新增如下內容:
provider: ^4.0.5
flustars: ^0.2.6+1
複製程式碼
準備工作做好了,接下來我們就開始編碼吧。
新增主題樣式
我們需要先想好自己所需要切換的主題樣式列表,如果覺得麻煩的可以直接用下面的內容:
Map<String, Color> themeColorMap = {
'gray': Colors.grey,
'blue': Colors.blue,
'blueAccent': Colors.blueAccent,
'cyan': Colors.cyan,
'deepPurple': Colors.purple,
'deepPurpleAccent': Colors.deepPurpleAccent,
'deepOrange': Colors.orange,
'green': Colors.green,
'indigo': Colors.indigo,
'indigoAccent': Colors.indigoAccent,
'orange': Colors.orange,
'purple': Colors.purple,
'pink': Colors.pink,
'red': Colors.red,
'teal': Colors.teal,
'black': Colors.black,
};
複製程式碼
使用 Provider 進行全域性狀態管理
然後我們就需要使用 Provider 來進行全域性的狀態管理了。首先先建立一個app_provider.dart
檔案,然後新增如下程式碼:
class AppInfoProvider with ChangeNotifier {
String _themeColor = '';
String get themeColor => _themeColor;
setTheme(String themeColor) {
_themeColor = themeColor;
notifyListeners();
}
}
複製程式碼
因為是全域性的狀態管理,接下來我們需要在main.dart
檔案中配置一下剛才建立的 provider,有多個狀態管理就使用 MultiProvider,單個的使用 Provider.value 就行了。(考慮到未來專案的擴充套件,這裡我就直接使用 MultiProvider)了
class MyApp extends StatelessWidget {
Color _themeColor;
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [ChangeNotifierProvider.value(value: AppInfoProvider())],
child: Consumer<AppInfoProvider>(
builder: (context, appInfo, _) {
String colorKey = appInfo.themeColor;
if (themeColorMap[colorKey] != null) {
_themeColor = themeColorMap[colorKey];
}
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primaryColor: _themeColor,
floatingActionButtonTheme:
FloatingActionButtonThemeData(backgroundColor: _themeColor),
),
home: MyHomePage(title: 'Flutter Theme Change demo'),
);
},
),
);
}
}
複製程式碼
如果想要在某個地方改變主題,我們只需要執行下面這行程式碼即可。
Provider.of<AppInfoProvider>(context).setTheme(colorKey);
複製程式碼
我們先來說說上面這段程式碼,重點就在於 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。
之所以使用
floatingActionButtonTheme
單獨設定floatingActionButton
而不是使用accentTextTheme
,是因為會有警告 ⚠️The support for configuring the foreground color of FloatingActionButtons using ThemeData.accentIconTheme has been deprecated. Please use ThemeData.floatingActionButtonTheme instead. See https://flutter.dev/go/remove-fab-accent-theme-dependency. This feature was deprecated after v1.13.2.
意思就是這個屬性將會在1.13.2
中被廢棄。不過並不影響我們現在的使用。
更多關於主題的內容可以參考 ?顏色和主題
持久化選擇的主題
這裡就需要使用到一開始提到的flustars
中的SpUtil
了,我們一般會在頁面初始化載入的時候讀取儲存的顏色資訊,所以我們需要在初始化頁面配置如下程式碼:
String _colorKey;
@override
void initState() {
super.initState();
_initAsync();
}
Future<void> _initAsync() async {
await SpUtil.getInstance();
_colorKey = SpUtil.getString('key_theme_color', defValue: 'blue');
// 設定初始化主題顏色
Provider.of<AppInfoProvider>(context, listen: false).setTheme(_colorKey);
}
複製程式碼
await SpUtil.getInstance();
這段程式碼用於載入SpUtil
庫,通過看原始碼我們知道,這個庫採用了單例模式,當然,這不是這篇文章的重點。
上面這段程式碼用於初始化主題,我們通過SpUtil.getString('key_theme_color', defValue: 'blue');
獲取儲存的主題資訊,然後再使用Provider.of<AppInfoProvider>(context, listen: false).setTheme(_colorKey);
設定主題即可。
初始化主題弄好了,那選擇的程式碼又如何編寫呢?
很簡單,只需要才合適的地方呼叫下面的程式碼就可以了。
setState(() {
_colorKey = key;
});
SpHelper.putString('key_theme_color', key);
Provider.of<AppInfoProvider>(context).setTheme(key);
複製程式碼
思路和上面大同小異,無非是將getString
換成了putString
。
切換主題控制元件的編寫
上面的程式碼提供了切換主題的思路,但是對於使用者來說,他們所要做的是有一個介面可以讓他們直接切換主題,因此,下面我們來編寫切換主題的控制元件。
因為切換主題通常會在設定介面中出現,所以這裡我用了一個ExpansionTile
,這是一個可以展開的ListTile
,程式碼如下:
…………
ExpansionTile(
leading: Icon(Icons.color_lens),
title: Text('顏色主題'),
initiallyExpanded: false,
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 10, right: 10, bottom: 10),
child: Wrap(
spacing: 8,
runSpacing: 8,
children: themeColorMap.keys.map((key) {
Color value = themeColorMap[key];
return InkWell(
onTap: () {
setState(() {
_colorKey = key;
});
SpUtil.putString('key_theme_color', key);
Provider.of<AppInfoProvider>(context, listen: false)
.setTheme(key);
},
child: Container(
width: 40,
height: 40,
color: value,
child: _colorKey == key
? Icon(
Icons.done,
color: Colors.white,
)
: null,
),
);
}).toList(),
),
)
],
),
…………
複製程式碼
效果如下:
上面這段程式碼就是將我們最開始選定的一些主題themeColorMap
展示出來,告訴使用者可以切換哪些主題。其中onTap
內的程式碼就是上一節中提到的設定顏色主題的方法,InkWell
主要用於提供主題色的點選效果,換成GestureDetector
也是可以的。
至此我們的換膚功能也就完成了,想要獲取完整程式碼的可以關注公眾號「01 二進位制」,後臺回覆「Flutter 主題切換」。
最後
以上就是關於如何在 Flutter 中切換主題的詳細內容了。可以看出,相較於原生應用主題的適配,在 Flutter 中實現換膚的功能簡單很多了。
最後來發布一篇預告,因為 iOS 13 和 Android 10 系統上都新增了「深色模式」,在文中我也提到了ThemeData
的Brightness brightness
屬性用於表示深色還是淺色。下一篇文章我就來聊一聊深色模式的適配。
如果你覺得我的文章對你有所幫助,不妨給個贊 ? 或者關注支援一下。