Flutter深色模式適配

張欽發表於2021-04-03

一、簡介

Flutter的深色模式以及跟隨系統設定比較簡單,我感覺需要注意的是開發過程中儘量使用Theme中的顏色與樣式,開發過程中遇到的比較大的坑就是provider的一些問題,可能是因為我用的版本新一些,網上找了很多文章,總會遇到一些問題。本文的深色模式適配是通過修改themeMode來實現的,供諸位有緣人蔘考。

image.png

二、環境介紹

1. Flutter: 2.0.3

2. Dart: 2.12.0

3. provider: 5.0.0

狀態管理,用於執行時切換主題

4. shared_preferences: 2.0.5

資料持久化,用於儲存當前選中的主題,以便下次啟動時讀取使用使用者選擇的主題

environment:
  sdk: ">=2.12.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

	# 忽略了一些依賴...

  # shared_preferences https://pub.flutter-io.cn/packages/shared_preferences
  shared_preferences: ^2.0.5
  # 全域性狀態管理 https://github.com/rrousselGit/provider/blob/master/resources/translations/zh-CN/README.md
  provider: ^5.0.0

複製程式碼

三、主題

1. ThemeData

factory ThemeData({
    Brightness brightness, // 應用主題亮度,可選(dark、light)
    VisualDensity visualDensity, // 視覺密度
    MaterialColor primarySwatch, // 主要樣式,設定primaryColor後該背景色會被覆蓋
    Color primaryColor, // 主要部分背景顏色(導航和tabBar等)
    Brightness primaryColorBrightness, // primaryColor的亮度
    Color primaryColorLight, // primaryColor的淺色版
    Color primaryColorDark, // primaryColor的深色版
    Color accentColor, // 前景色(文字,按鈕等)
    Brightness accentColorBrightness, // accentColor的亮度
    Color canvasColor, // MaterialType.canvas 的預設顏色
    Color shadowColor, // 陰影顏色
    Color scaffoldBackgroundColor, // Scaffold的背景顏色。典型Material應用程式或應用程式內頁面的背景顏色
    Color bottomAppBarColor, // BottomAppBar的預設顏色
    Color cardColor, // Card的顏色
    Color dividerColor, // Divider和PopupMenuDivider的顏色,也用於ListTile之間、DataTable的行之間等。
    Color focusColor, // 突出顏色
    Color hoverColor, //  hoverColor
    Color highlightColor, // 高亮顏色,選中在潑墨動畫期間使用的突出顯示顏色,或用於指示選單中的項。
    Color splashColor, // 墨水飛濺的顏色。InkWell
    InteractiveInkFeatureFactory splashFactory, // 定義由InkWell和InkResponse反應產生的墨濺的外觀。
    Color selectedRowColor, // 用於突出顯示選定行的顏色。
    Color unselectedWidgetColor, // 用於處於非活動(但已啟用)狀態的小部件的顏色。例如,未選中的核取方塊。通常與accentColor形成對比。也看到disabledColor。
    Color disabledColor, // 禁用狀態下部件的顏色,無論其當前狀態如何。例如,一個禁用的核取方塊(可以選中或未選中)。
    Color buttonColor, // RaisedButton按鈕中使用的Material 的預設填充顏色。
    ButtonThemeData buttonTheme, // 定義按鈕部件的預設配置,
    ToggleButtonsThemeData toggleButtonsTheme, // 切換按鈕的主題
    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值。
    TabBarTheme tabBarTheme, // 用於自定義選項卡欄指示器的大小、形狀和顏色的主題。
    TooltipThemeData tooltipTheme, // tooltip主題
    CardTheme cardTheme, // Card的顏色和樣式
    AppBarTheme appBarTheme, // appBar主題
    ColorScheme colorScheme, // 擁有13種顏色,可用於配置大多陣列件的顏色。
    NavigationRailThemeData navigationRailTheme, // 導航邊欄主題
  	// ...
  })
複製程式碼

2. main.dart or MaterialApp

theme為預設主題,darkTheme為深色主題,themeMode為當前使用哪個主題,可選值systemlightdark,只有在th``emedarkTheme都設定的時候才會生效,我們的themedarkTheme都直接使用ThemeData物件,給他指定了brightness,而不是使用這樣感覺可以方便修改樣式,當然也可以抽出來封裝一下,我這沒有去處理。

MaterialApp(
  theme: ThemeData(
    brightness: Brightness.light,
    // scaffoldBackgroundColor: Color(0xFFF5F5F9),
  ),
  darkTheme: ThemeData(
    brightness: Brightness.dark,
    // scaffoldBackgroundColor: Color(0xFFF5F5F9),
  ),
  themeMode: context.watch<ThemeModel>().theme
);
複製程式碼

四、全域性配置

全域性配置是在MaterialApp載入之前進行一寫初始化操作,參考了《Flutter實戰》電子書,Flutter當中SharedPreferences是非同步初始化,還有Dio網路請求的快取也需要提前初始化,我們這裡SharedPreferences載入完之後在進行之後的操作,SpUtils中的SharedPreferences使用的Global全域性配置中的靜態屬性。

1. Global

class Global {
  static late SharedPreferences prefs;

  static ThemeMode theme = ThemeMode.light;

  // 是否為release版
  static bool get isRelease => bool.fromEnvironment("dart.vm.product");

  //初始化全域性資訊,會在APP啟動時執行
  static Future init() async {
    WidgetsFlutterBinding.ensureInitialized();
    prefs = await SharedPreferences.getInstance();

    // 當前本地儲存的主題
    String themeValue = await SpUtils.instance.getStorage(SpConstants.skin);
    theme = themeStringToThemeMode(themeValue);

    //初始化網路請求相關配置
    HttpManager();
  }
}
複製程式碼

2. main.dart

// Global載入完成後掉用runApp
Global.init().then((e) => runApp());
複製程式碼

3. themeStringToThemeMode()

字串轉ThemeMode

ThemeMode themeStringToThemeMode(String themeValue){
  ThemeMode theme = ThemeMode.light;
  switch (themeValue) {
    case "light":
      theme = ThemeMode.light;
      break;
    case "dark":
      theme = ThemeMode.dark;
      break;
    case "system":
      theme = ThemeMode.system;
      break;
  }
  return theme;
}
複製程式碼

五、使用狀態管理(provider)切換主題

> 此處大坑,處處勸退,感謝Flutter provider勸退經歷這篇文章

1. 構建主題Model

class ThemeModel extends ChangeNotifier {

  // 獲取當前主題,如果為設定主題,則預設使用淺色模式
  ThemeMode get theme => Global.theme;

  // 主題改變後,通知其依賴項,新主題會立即生效
  set theme(ThemeMode themeMode) {
    if (themeMode != theme) {
      Global.theme = themeMode;
      notifyListeners();
    }
  }
}
複製程式碼

2. main.dart(監聽值變化)

此處為main.dart檔案的完整程式碼,下面有關provider的一些使用方式可能與網上很多文章不一樣的,但是這都是官網文件的最新推薦使用方式。讀取當前provider中儲存的主題context.watch<ThemeModel>().theme

void main() {
  //頂部狀態列透明
  SystemChrome.setSystemUIOverlayStyle(
      SystemUiOverlayStyle(statusBarColor: Colors.transparent));
  Global.init().then((e) => runApp(
        MultiProvider(
            providers: [ListenableProvider<ThemeModel>(create: (_) => ThemeModel())],
            builder: (context, child) {
              return WanAndroid();
            }),
      ));
}

class WanAndroid extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: '/',
      theme: ThemeData(
        brightness: Brightness.light,
        // scaffoldBackgroundColor: Color(0xFFF5F5F9),
      ),
      darkTheme: ThemeData(
        brightness: Brightness.dark,
        // scaffoldBackgroundColor: Color(0xFFF5F5F9),
      ),
      themeMode: context.watch<ThemeModel>().theme,
      routes: {
        '/': (context) => SplashPage(),
        '/index': (context) => IndexPage(),
        '/login': (context) => LoginPage(),
        '/setting': (context) => SettingPage(),
      },
      title: '玩Android-Flutter版',
    );
  }
}
複製程式碼

3. 切換主題

修改provider中儲存的值即可。

// themeStringToThemeMode方法程式碼在上面有寫
context.read<ThemeModel>().theme = themeStringToThemeMode(value);
複製程式碼

六、原始碼

- 原始碼:github.com/sdwfqin/flu…

相關文章