flutter使用Provider完成動態主題功能

wz_app發表於2021-03-26

介紹

動態切換主題功能,使用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啟動時預先載入了主題

實現效果演示

s103xavuim.gif 可以看到,啟動專案到登陸頁面的時候主題也是最新的了,這裡我們用到了持久化操作

相關文章