Flutter 樣式基礎之 Theme 主題

YYDev發表於2019-09-08

一、介紹

Theme 類將主題應用於子控制元件,Theme(主題)它描述了應用程式的顏色和排版選擇。Theme有兩種:全域性Theme和區域性Theme。全域性Theme是由應用程式根MaterialApp建立的Theme;而區域性Theme是在應用程式某個區域範圍中用於覆蓋全域性主題。

Google官網介紹:api.flutter.dev/flutter/mat…

建構函式

Theme({Key key,
@required ThemeData data,
bool isMaterialAppTheme: false, 
@required Widget child })
複製程式碼
  • ThemeData data: 儲存 Material 主題的顏色和字型等排版樣式。
  • isMaterialAppTheme: 預設值是false ,表示是否為材料設計風格的主題。
  • child: 子控制元件。

繼承關係

Object -> Diagnosticable -> DiagnosticableTree -> Widget -> StatelessWidget -> Theme。

二、ThemeData

從Theme的建構函式我們不難看出,Flutter 中是通過ThemeData去儲存共享應用的主題及樣式等資訊的,因此我們繼續翻一下官方文件,研究研究一下ThemeData包含了哪些知識點。

繼承關係: Object -> Diagnosticable -> ThemeData

建構函式:

ThemeData({Brightness brightness,
MaterialColor primarySwatch, 
Color primaryColor, 
Brightness primaryColorBrightness, 
Color primaryColorLight, 
Color primaryColorDark, 
Color accentColor, 
Brightness accentColorBrightness,
Color canvasColor, 
Color scaffoldBackgroundColor, 
Color bottomAppBarColor, 
Color cardColor, 
Color dividerColor, 
Color focusColor, 
Color hoverColor, 
Color highlightColor, 
Color splashColor, 
InteractiveInkFeatureFactory splashFactory, 
Color selectedRowColor, 
Color unselectedWidgetColor, 
Color disabledColor, 
Color buttonColor, 
ButtonThemeData buttonTheme,
Color secondaryHeaderColor,
Color textSelectionColor,
Color cursorColor, 
Color textSelectionHandleColor, 
Color backgroundColor, 
Color dialogBackgroundColor, 
Color indicatorColor, 
Color hintColor, 
Color errorColor, 
Color toggleableActiveColor, 
String fontFamily, 
TextTheme textTheme, 
TextTheme primaryTextTheme, 
TextTheme accentTextTheme, 
InputDecorationTheme inputDecorationTheme, 
IconThemeData iconTheme, 
IconThemeData primaryIconTheme, 
IconThemeData accentIconTheme, 
SliderThemeData sliderTheme, 
TabBarTheme tabBarTheme,
CardTheme cardTheme, 
ChipThemeData chipTheme, 
TargetPlatform platform, 
MaterialTapTargetSize materialTapTargetSize, 
PageTransitionsTheme pageTransitionsTheme,
AppBarTheme appBarTheme,
BottomAppBarTheme bottomAppBarTheme, 
ColorScheme colorScheme, 
DialogTheme dialogTheme, 
FloatingActionButtonThemeData floatingActionButtonTheme, 
Typography typography, 
CupertinoThemeData cupertinoOverrideTheme, 
SnackBarThemeData snackBarTheme, 
BottomSheetThemeData bottomSheetTheme })
複製程式碼

從其建構函式不難看出,它主要儲存了各種顏色及字型、Icon樣式等設定。由於屬性過多,我們挑一些常用的講一講:

  • brightness - Brightness型別,應用程式的整體主題亮度。用於按鈕等小部件,以確定在不使用主色(primaryColor)或強調色(accentColor)時選擇什麼顏色。當亮度較暗時,畫布、卡片和原色都較暗。當亮度為光時,畫布和卡片的顏色是明亮的,原色的暗度根據原色亮度變化。當亮度較暗時,原色(primaryColor)與卡片和畫布顏色的對比度較差;當亮度較暗時,用白色或亮藍色來對比。

  • primarySwatch - MaterialColor 型別,Material 主題中定義一種顏色,它具有十種顏色陰影的顏色樣本。值越大顏色越深,10個有效的index分別為:50,100,200,…,900。預設是取中間值500。

  • primaryColor - Color型別,App主要部分的背景色(ToolBar,Tabbar等)

  • primaryColorBrightness - Brightness型別,primaryColor的亮度,用於確定設定在primaryColor上部的文字和圖示顏色(如:工具欄文字(toolbar text))。

  • primaryColorLight - Color型別,primaryColor的較淺版本

  • primaryColorDark - Color型別,primaryColor的較深版本

  • accentColor - Color型別,前景色(按鈕、文字、覆蓋邊緣效果等)

  • accentColorBrightness - Brightness型別,accentColor的亮度。用於確定位於accentColor上部的文字和圖示顏色(例如,浮動操作按鈕(FloatingButton)上的圖示)

  • canvasColor - Color型別,MaterialType.canvas Material的預設顏色。

  • scaffoldBackgroundColor - Color型別,作為Scaffold下的Material預設顏色,用於materia應用程式或app內頁面的背景色。

  • bottomAppBarColor - Color型別,bottomAppBarColor的預設顏色。這可以通過指定BottomAppBar.color來覆蓋。

  • cardColor - Color型別,用在卡片(Card)上的Material的顏色。

  • dividerColor - Color型別,分隔符(Dividers)和彈窗分隔符(PopupMenuDividers)的顏色,也用於ListTiles和DataTables的行之間。要建立使用這種顏色的合適的邊界,請考慮Divider.createBorderSide。

  • highlightColor - Color型別,用於墨水噴濺動畫或指示選單被選中時的高亮顏色

  • splashColor - Color型別,墨水濺出的顏色

  • splashFactory - InteractiveInkFeatureFactory型別,定義InkWall和InkResponse產成的墨水噴濺時的外觀。

  • selectedRowColor - Color型別,用於高亮選定行的顏色。

  • unselectedWidgetColor - Color型別,小部件處於非活動(但啟用)狀態時使用的顏色。例如,未選中的核取方塊。通常與accentColor形成對比。

  • disabledColor - Color型別,無效的部件(widget)的顏色,不管它們的狀態如何。例如,一個禁用的核取方塊(可以選中或不選中)。

  • buttonColor - Color型別,Material中RaisedButtons使用的預設填充色。

  • buttonTheme - ButtonThemeData型別,定義按鈕小部件的預設配置,如RaisedButton和FlatButton。

  • secondaryHeaderColor - Color型別,有選定行時PaginatedDataTable標題的顏色

  • textSelectionColor - Color型別,文字欄位(如TextField)中文字被選中的顏色。

  • cursorColor - Color型別,在 Material-style 文字欄位(如TextField)中游標的顏色。

  • textSelectionHandleColor - Color型別,用於調整當前選定文字部分的控制程式碼的顏色。

  • backgroundColor - Color型別,與primaryColor對比的顏色(例如 用作進度條的剩餘部分)。

  • dialogBackgroundColor - Color型別,Color型別,Dialog元素的背景色

  • indicatorColor - Color型別,TabBar中選項選中的指示器顏色。

  • hintColor - Color型別,用於提示文字或佔位符文字的顏色,例如在TextField中。

  • errorColor - Color型別,用於輸入驗證錯誤的顏色,例如在TextField中。

  • toggleableActiveColor - Color型別,用於突出顯示切換Widget(如Switch,Radio和Checkbox)的活動狀態的顏色。

  • fontFamily - String型別,字型型別

  • textTheme - TextTheme型別,與卡片和畫布對比的文字顏色

  • primaryTextTheme - TextTheme型別,與primary color形成對比的文字主題。

  • accentTextTheme - TextTheme型別,與accent color形成對比的文字主題。

  • inputDecorationTheme - InputDecorationTheme型別,InputDecorator、TextField和TextFormField的預設InputDecoration值基於此主題。

  • iconTheme - IconThemeData型別,與卡片和畫布顏色形成對比的圖示主題。

  • primaryIconTheme - IconThemeData型別,與原色(primary color)形成對比的圖示主題。

  • accentIconTheme - IconThemeData型別,與前景色(accent color)形成對比的圖示主題。

  • sliderTheme - SliderThemeData型別,SliderThemeData型別,用於渲染Slider的顏色和形狀。

  • tabBarTheme - TabBarTheme型別, 一個主題,用於自定義選項卡欄指示器的尺寸、形狀和顏色。

  • chipTheme - ChipThemeData型別,用於Chip的顏色和樣式

  • platform - TargetPlatform型別,widget應該適應目標的平臺。

  • materialTapTargetSize - MaterialTapTargetSize型別,配置特定材料部件的hit測試大小。

  • pageTransitionsTheme - PageTransitionsTheme型別,每個目標平臺的預設MaterialPageRoute轉換。

  • colorScheme ColorScheme型別,一組13種顏色,可用於配置大多陣列件的顏色屬性。

  • typography - Typography型別,用於配置TextTheme、primaryTextTheme和accentTextTheme的顏色和幾何文字主題值。

三、Theme.of 方法

它是一個 static 方法。子控制元件通過使用Theme.of來獲取當前主題的ThemeData物件,當控制元件使用Theme.of時,如果主題稍後更改,則會自動重建以便可以應用更改。我們可以通過Theme.of檢視當前應用程式的配色方案。

ThemeData.of 方法程式碼:

ThemeData of (
BuildContext context, {
bool shadowThemeOnly: false
})
複製程式碼

ThemeData資料來自最近的給定上下文的Theme例項。如果給定的 context 包含在提供MaterialLocalizations的Localizations部件中,則返回的資料將根據最近的可用MaterialLocalizations進行本地化。如果給定的構建上下文中沒有主題,則預設為新的ThemeData.fallback。

示例用法如下:

@override
Widget build(BuildContext context) {
  return Text(
    'Theme Example',
    style: Theme.of(context).textTheme.title,
  );
}
複製程式碼

到這裡我們已經知道如何去設定flutter主題了,在flutter中,主題又分全域性主題和區域性主題。接下來我們講解一下這兩種方式的適用場景及區別。

四、全域性主題

在建立應用全域性主題時,我們提供一個ThemeData物件給MaterialApp即可,如果建構函式沒有設定ThemeData,則Flutter會建立一個預設的主題。下面我們實踐一下,寫個自定義全域性主題的簡單示例:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
          brightness: Brightness.light, //指定亮度主題,有白色/黑色兩種可選。
          primaryColor: Colors.blue[800], //這裡我們選藍色為基準色值。
          accentColor: Colors.lightBlue[100]), //這裡我們選淺藍色為強調色值。
      home: ThemeTest(),
    );
  }
}

class ThemeTest extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("ThemeTest"), //頁面標題
      ),
      body: Container(
        color: Theme.of(context).primaryColor, //內容背景顏色
        margin: EdgeInsets.all(100.0),
        padding: EdgeInsets.all(10.0),
        child: Text(
          "MaterialApp Theme Color", //內容文字
          style: TextStyle(
              fontSize: 20, color: Theme.of(context).accentColor), //內容文字顏色,引用的是accentColor。
          textAlign: TextAlign.center,
        ),
      ),
    );
  }
}
複製程式碼

效果圖如下:

theme.jpg

五、區域性主題

區域性主題是在應用程式某些區域中,用於覆蓋全域性主題。我們定義好一個主題後,就可以在Widget的build方法中通過Theme.of(context)方法來使用它。Theme.of(context)將查詢Widget樹並返回樹中最近的Theme。如果Widget之上有一個單獨的區域性Theme定義,則返回該主題的值;如果不是,則返回App全域性主題。我們講一下主要的幾種建立方法:

5.1 重新建立Theme物件

通過重新建立Theme的方式,建立一個例項, 並重新建立ThemeData,賦值給data。然後指定child Widget,將ThemeData傳遞給Theme Widget,程式碼如下:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
            brightness: Brightness.light, //指定亮度主題,有白色/黑色兩種可選。
            primaryColor: Colors.blue[800], //這裡我們選藍色為基準色值。
            accentColor: Colors.lightBlue[100]), //這裡我們選淺藍色為強調色值。
        home: Theme(
            data: ThemeData(accentColor: Colors.yellow),
            child: ThemeTest())); //建立區域性主題,將accentColor設定為黃色
  }
}

class ThemeTest extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("ThemeTest"),
      ),
      body: Container(
          color: Theme.of(context).primaryColor,
          margin: EdgeInsets.all(100.0),
          padding: EdgeInsets.all(10.0),
          child: Text(
            "MaterialApp Theme Color",
            style: TextStyle(
                fontSize: 20, color: Theme.of(context).accentColor), //應用區域性主題顏色
            textAlign: TextAlign.center,
          )),
    );
  }
}
複製程式碼

效果圖如下:

區域性主題-黃色.jpg

區域性主題不生效問題

這裡有個小坑點需要注意,我們需要在Widget的父層Widget去設定區域性Theme,才會生效;如果我們直接在區域性Theme的child 屬性指定的Widget中去使用的話,會拿到系統的全域性Theme.

如下示例:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
          brightness: Brightness.light, //指定亮度主題,有白色/黑色兩種可選。
          primaryColor: Colors.blue[800], //這裡我們選藍色為基準色值。
          accentColor: Colors.white), //設定為白色。
      home: ThemeTest(),
    );
  }
}

class ThemeTest extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("ThemeTest"),//頁面標題
      ),
      body: Container(
          color: Theme.of(context).primaryColor,
          margin: EdgeInsets.all(100.0),
          padding: EdgeInsets.all(10.0),
          child: Theme(
              data: ThemeData(accentColor: Colors.red), //設定紅色
              //建立accentColor 為綠色的區域性主題,若區域性主題Theme 的Child 是 Text,則Text引用區域性主題色不會生效,拿到的還是區域性Themes上層的值,即全域性主題的值。
              child: Text(
                "MaterialApp Theme Color",
                style: TextStyle(
                    fontSize: 20, color: Theme.of(context).accentColor), //應用主題色
                textAlign: TextAlign.center,
              ))),
    );
  }
}
複製程式碼

效果圖如下:

區域性主題不生效.jpg

可以從截圖看到,Text顯示的顏色依舊是白色,並未變成我們所期望的紅色,因此我們使用的時候需要額外注意這點。

5.2 擴充套件父主題

除了 建立Theme 物件的方式去使用區域性主題,我們可以通過使用copyWith方法來實現,這樣的話,我們去修改父主題時,無需覆蓋所有的主題屬性,只需要將我們想改動的屬性指定即可。

將原先的:

data: ThemeData(accentColor: Colors.yellow)
複製程式碼

改成:

Theme.of(context).copyWith(accentColor: Colors.yellow)
複製程式碼

這樣一來,我們的區域性主題除了accentColor改成了指定值,其他屬性依然可以使用它上一層 Theme的屬性值,而不是變成系統預設的。

5.3 根據裝置平臺設定不同主題

除了上述兩種方法之外,我們還可以根據裝置的平臺型別(iOS,Android和Fuchsia)去提供不同的主題:

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
 
final ThemeData iOSTheme = ThemeData(
    brightness: Brightness.light,
    primaryColor: Colors.grey[600],
    accentColor: Colors.white,
  );

final ThemeData androidTheme = ThemeData(
    brightness: Brightness.dark,
    primaryColor: Colors.yellow[300],
    accentColor: Colors.deepPurple[800],
  );
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        brightness: Brightness.light,
        primaryColor: Colors.grey[100],
        accentColor: Colors.white,
      ),
      home: Theme(
          data: defaultTargetPlatform == TargetPlatform.android
              ? androidTheme
              : iOSTheme,
          child: ThemeTest()),
    );
  }
}

class ThemeTest extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("ThemeTest"),
      ),
      body: Container(
          color: Theme.of(context).primaryColor,
          margin: EdgeInsets.all(100.0),
          padding: EdgeInsets.all(10.0),
          child: Text(
            "MaterialApp Theme Color",
            style:
                TextStyle(fontSize: 20, color: Theme.of(context).accentColor),
            textAlign: TextAlign.center,
          )),
    );
  }
}


複製程式碼

Android 端效果圖:

Android 端區域性主題

iOS 端效果圖:

iOS 端區域性主題

六、總結

本篇文章主要講述了Theme 的一些常用方法及屬性。同時對全域性主題、區域性主題、不同平臺主題的設定方法也做了較詳細的實踐示例。flutter 中對主題的設定相對而言比較靈活,大家可以多多實踐操作一波。

作者

Flutter 樣式基礎之 Theme 主題
xiaosongzeem

相關文章