Flutter主題切換——讓你的APP也能一鍵換膚

巴格梅克發表於2020-04-24

為了讓你的 App 更美觀,主題切換已經是一個必不可少的功能了,但如果想在傳統的 Android 和 iOS 上分別適配不同的主題相當繁瑣。但這一切,在 Flutter 中都非常容易實現。今天我們就來看看,如果在 Flutter 中給你的 App 新增換膚功能。我們要實現的效果如下:

Flutter主題切換——讓你的APP也能一鍵換膚

新增依賴

在該案例中,我使用到了 providerflustars 兩個庫,簡單介紹一下這兩個庫:

provider

官方推薦的狀態管理庫,相比其他狀態管理庫使用起來比較方便。

狀態管理:通俗的講,當我們想在多個頁面(元件/Widget)之間共享狀態(資料),或者一個頁面(元件/Widget)中的多個子元件之間共享狀態(資料),這個時候我們就可以用 Flutter 中的狀態管理來管理統一的狀態(資料),實現不同元件直接的傳值和資料共享。

flustars

號稱“Flutter 全網最全常用工具類”,其中包括了SpUtilScreenUtilTimelineUtil等常見工具類,這裡我們要使用的是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 的設定:

Flutter主題切換——讓你的APP也能一鍵換膚

我們看看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(),
      ),
    )
  ],
),
…………
複製程式碼

效果如下:

Flutter主題切換——讓你的APP也能一鍵換膚

上面這段程式碼就是將我們最開始選定的一些主題themeColorMap展示出來,告訴使用者可以切換哪些主題。其中onTap內的程式碼就是上一節中提到的設定顏色主題的方法,InkWell主要用於提供主題色的點選效果,換成GestureDetector也是可以的。

至此我們的換膚功能也就完成了,想要獲取完整程式碼的可以關注公眾號「01 二進位制」,後臺回覆「Flutter 主題切換」。

最後

以上就是關於如何在 Flutter 中切換主題的詳細內容了。可以看出,相較於原生應用主題的適配,在 Flutter 中實現換膚的功能簡單很多了。

最後來發布一篇預告,因為 iOS 13 和 Android 10 系統上都新增了「深色模式」,在文中我也提到了ThemeDataBrightness brightness屬性用於表示深色還是淺色。下一篇文章我就來聊一聊深色模式的適配。

如果你覺得我的文章對你有所幫助,不妨給個贊 ? 或者關注支援一下。

黑白大氣神祕簡約微信公眾號二維碼

相關文章