主題 Theme:黑暗、光明與 UI 設計更自由

紅寶鐵軌客
Join to follow...
Follow/Unfollow Writer: 紅寶鐵軌客
By following, you’ll receive notifications when this author publishes new articles.
Don't wait! Sign up to follow this writer.
WriterShelf is a privacy-oriented writing platform. Unleash the power of your voice. It's free!
Sign up. Join WriterShelf now! Already a member. Login to WriterShelf.
寫程式中、折磨中、享受中 ......
897   0  
·
2021/07/11
·
22 mins read


這一篇希望可以解救工程師的靈魂,因為有太多的工程師因為 UX/UI 的改來改去而氣到翻臉,哎,何必呢,除了要訓練自己的時時保持出世與正向外,儘早將 UX/UI 的控制獨立,會是最好的解決方式。相信我,老闆跟 designer 也不是故意玩你,他們只是天生善變而已。

Flutter 有非常強大的 UX/UI 管理能力,在我們深入暸解它之前,讓我們先來聞香一下,先有個概念。

延續我們的程式碼,如果有人現在才加入,目前階段的程式碼請自行參考本書:程式碼備份中的 Code milestone 2

支援黑暗模式

話說蘋果在 2019 年底發佈的 iOS 13 時,加入了黑暗模式,之後,很多的網站及 App 就開始將黑暗模式列為必須,想像一下各位就是負責黑暗模式的開發人員,先請想想看:開發這項功能難不難?會需要改變些什麼?⋯⋯ 我想大家一定馬上就會想到:文字的顏色要反轉,背景、按鈕、Title 等的顏色都要改變,嗯,要改的東西可真多啊,黑暗模式並不是一件簡單的事。

但是,在 Flutter,黑暗模式卻相對簡單多了,讓我們先執行目前的程式,看看目前的,也就是預設的,淺色 Light 主題,然後,請更改:

lib/main.dart:

return MaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData.dark(
    //primarySwatch: Colors.blue,
  ),
  • 第 3 行:在程式中找到 theme,將 ThemeData 改成 ThemeData.dark,
  • 第 4 行:將 primarySwatch 加兩撇,變成註解。

再執行一次,哇,黑暗模式出現了!

左邊就是改變前預設的淺色 Light 模式,右邊就是改成 ThemeData.dark 的黑暗模式,Flutter 很厲害吧,只要簡單幾個字,就把黑暗模式做出來了。

等等,你現在可能馬上就有第一個問題了:「我怎麼知道可以用 ThemeData.dark?文件在哪裏?」來,分享你一個秘笈,很簡單,請將程式中的 ThemeData 選起來,也就是點兩下將它 hightlight ,然後按 Command-b,鐺鐺,Flutter 的 theme 原始碼 theme_data.dart 就跳出來了,這其實就是 flutter 的一種文件,Flutter 的原始碼都有很完整的註解,你所要找的設定與使用方法,都寫在上面,這原始碼上有很清楚的註解:

/// * [ThemeData.from], which creates a ThemeData from a [ColorScheme].
/// * [ThemeData.light], which creates a light blue theme.
/// * [ThemeData.dark], which creates dark theme with a teal secondary [ColorScheme] color.

啊,原來 Flutter 除了 dark 外,還有 light 跟 from,也就是「黑暗」、「光明」與「自由配」。


Android Studio on Mac 常用的快捷鍵

用 Android Studio 寫 Flutter 有一些你一定要知道的超好用快捷鍵,以下是我最常用的: 

  • Control-J 或游標指著不動:可以顯示選取的說明文件。
  • Option-space:查看 Widget 的 Properties,這超好用,也比看原始碼快也安全。
  • Option-Return:Widget 的再打包或移除,我比較喜歡用 flutter outline,之前有介紹過。
  • Option-↑:先選一個 Widget 然後這樣按,就可以選整個 Widget,繼續按,可以繼續往上層選,很好用,按 ↓ 就會縮小。
  • Option-Command-L:重新排列整理原始碼,基本上有逗點的都會分行,很酷,所以可以先亂打,哈哈哈哈哈。
  • Command-B  - 看 source code 原始碼,但是要小心使用,不要把原始碼改了。


設計語言

Flutter 內建支援兩個設計語言,也就是近年來被當神來拜的 UX,其實,這就是設計的一慣性。在 Flutter 中,一般都會在 main.dart 的程式碼中選定用那一個:

  • MaterialApp:Flutter 是 Google 的,當然要全力支援 Google 的 Material design。  
  • CupertinoApp:就是 App 的 iOS 風格,也就是蘋果的 Human Interface Guidelines,可能是嫌這個字太長,或是有版權問題,Google 在 Flutter 中就用 Cupertino 來稱呼,Cupertino 就是蘋果總部所在地啦,也算工程師的小樂趣吧。

這兩個設計語言的風格當然不同,一般會希望跑在 iPhone 上的就用 CupertinoApp,在 Android 上,就用 MaterialApp,當然也可以都不用,混用,甚至依使用平台自動切換,不過,我們剛學,就先以 MaterialApp 為基礎,先學走再學跑。

話說,怎麼沒人理微軟的 Fluent 呢?Flutter 的產品經理說:「沒有開發計畫,但是如果社群有人想開發,可以幫忙。」


Material Design 

Google 的 Material Design 是一個大傢伙,各位一定到這個官網看看,這個設計語言包含了 Icon、字體、元件、顏色等等,要完全搞懂還真不容易,不過,Flutter 已經都幫你準備好了,所以我們只要負責用,真的容易太多了,我也建議在 Flutter 中,就以 Material deisgn 為主,畢竟,這是 Google 的主場,何必強人所難。

我不是設計師,但是我覺得下面的這個幕後花絮影片很棒,可以快速了解 Material design 的起點。

Material design Google Developers — YouTube


依照系統的黑暗模式 ~ 超簡單

現在讓我們來讓這個 App 支援系統的黑暗模式,也就是當系統設定為黑暗模式時,App 也會自動轉成黑暗模式。

如同我們之前強調的寫作風格與管理,我們應該要將 UI 的設定獨立出來,並獨立放在 theme 目錄內。UI 是個人人有意見,天天都會變的惡魔,一個整合的 UI 設定檔會讓寫程式的人生由黑白變彩色,所以一開始就應該將它分開,我們現在就將它分開,新增一個 style.dart 程式檔,未來所有 theme 的設定都會放在這裡。

lib/theme/style.dart:

import 'package:flutter/material.dart';

final themeLight = ThemeData.light().copyWith(
  primaryColor: Colors.green,
);
final themeDark = ThemeData.dark().copyWith(
  primaryColor: Colors.blueGrey,
);
  • 第 3~4 行:我們把淺色模式的 theme 就取名叫 themeLight,它基本上就是複製 ThemeData.light() 的內容,但是我們透過 copyWith() 保留所有的設定只將指定的項目改變,在這裏,我們將 primaryColor 的顏色改變成綠色。各位一定要試試將 copyWith 選定後按 control-J,就可以把 API 文件叫出來,各位應該會心裡叫聲「哇」,原來有那麼多東西可以改。
  • 第 6~7 行:黑暗模式的 theme 就叫 themeDart,其他就跟 themeLight 一樣。

再來就是將 main 中的 MaterialApp 設定為依照系統設定。

lib/main.dart:

return MaterialApp(
  title: 'Flutter Demo',
  theme: themeLight,
  darkTheme: themeDark, // Provide dark theme.
  themeMode: ThemeMode.system,
  • 第 3 行:將 theme 設為淺色模式的 themeLight 變數,
  • 第 4 行:將黑暗模式設為 themeDark,怕以後忘記?將 MaterialApp 選定後按 control-J,就可以在文件中看到黑暗模式設定就是用 darkTheme,除此之外,還有 highContrastTheme 跟highContrastDarkTheme 等等。
  • 第 5 行:透過 themeMode,我們可以指定使用哪一個 theme,設定為 ThemeMode.system 時就會依照系統的設定,淺色時就用 theme 設定,黑暗模式時就選用 darkTheme。

當然不要忘了在 main.dart 的前面 import style.dart:

import 'package:happy_recorder/theme/style.dart'; 

趕快再來執行一次,如果你的模擬器沒改變的話,綠色頭的淺色模式就出現了,現在請將模擬器設定到暗黑模式,在 iOS 上就是:Home > Settings > 最下面的 developer > Dark Appearance,如下圖:

把 Dark Appearance 黑暗模式打開,切回到 App,哇,黑暗模式出現了!這未免也太簡單了吧!沒錯,因為 Flutter 已經把設計語言整合進來了,而且這也是一條正確的路,不要再想自己搞一套設計語言或是色系了,不是說你不行,Flutter 也沒有限制,但是絕不是一件簡單的事。


Material design 的顏色系統

Material Design 有一套很完整的顏色系統,顏色理論 (Color theory) 雖然是個從牛頓就已經有的老東西,但是要搞懂還真不容易,我也不懂,所以我們不會深入討論,如果對 Material design 的色系有興趣,可以點這裡進去看看。

Material Design 內定了一些色系,當然也允許自定色系,事實上,我們上面所使用的 dark 及light 就等於:

  • ThemeData.light:使用淺藍 light blue 主題色系,加上設定 theme 的亮度為明亮。
  • ThemeData.dark:使用藍綠色 teal 為第二主題色系,加上設定 theme 的亮度為黑暗。

使用 Flutter 內定的顏色是最簡單的路,但是如果一定要自訂顏色,也一點都不難,只是好不好看而已,我們可以很簡單的增加一的自訂的 theme,如下:

我們在 lib/theme/style.dart 下面增加一個 themeCustom:

final themeCustom = ThemeData(
  brightness: Brightness.light,
  primarySwatch: Colors.yellow,
  primaryColor: Colors.deepOrange,
  accentColor: Colors.pinkAccent[700],
  backgroundColor: Colors.teal[200],
  scaffoldBackgroundColor: Color.fromRGBO(0xCC, 26, 255, 50.0),
);
  • 第 1 行:這個 theme 就取名叫 themeCustom,這次不是複製一個既有的色系了,我們重新開始,全部自己設定。
  • 第 2 行:設定亮度為明亮。
  • 第 3 行:主要的色盤顏色。
  • 第 4~6 行:主要顏色為 deep orange、次要色是淺 pink,我們在這裏是用 Colors 來選顏色,它是 material 設定顏色的 class,所以自然是使用 material 中已經定好的顏色,先記住 Colors 與 Color 是不同的,我們等一下會有更進一步的說明 。
  • 第 7 行:這是定 scaffold 的背景色,這次我們用的是 Color 這個 dart 的 UI class,所以是完全自由的選色,愛選什麼色就什麼色,fromRGBO 就是 RGB 三色加上透明度。

Flutter 中 ThemeData 可以設定的東西可多了,還記得我們的祕技 Control-J 嗎?打開它,哇,有那麼多可以設定,是要花一些時間才會摸熟 Widget 與顏色的對應關係。

好啦,我們再來就把 main.dart 中的 theme 改成用這個:

theme: themeCustom,

在模擬器上執行它,別忘了要切回系統預設的明亮模式,鐺鐺,我的驚世駭俗 UI 色系就出現了:

這真是一個難以形容的怪色調,不過也許有人就是喜歡,UI 是個永遠的爭論,自訂顏色在 Flutter 上真的一點都不難,但是要好看,就真的很難,所以關於選顏色這件事,我們就可以把 style.dart 交給設計,讓專業的來吧,現在知道爲什麼一定要將 style 獨立出來了吧。


material Colors vs 絕對顏色 Color

上面我們已經看到 Colors 跟 Color 這兩個 class 是完全不同的應用,沒有「s」的 Color 是 dart 的內建 class,可以用來自由的設定任何顏色。有「s」的 Colors 就不一樣了,它是 Flutter 中的 material class,所以它就只能選已經預先設定好的 material color,詳細的說明可以看文件,但是很難懂,所以我們來做個簡介:

  • Flutter 中,預先設定好的 material color 只有幾個顏色,如:pink、red、amber、yellow 等等 ⋯⋯,不用忙著寫筆記,在 IDE 中輸入 Colors 加上 . 時,IDE 就會跳出內建顏色讓你選了。
  • Material design 有分兩個顏色:主色 primary color 跟強調色 accent color,字尾是 accent 的就是強調色。
  • 主色都有十個色階:[50]+[100], [200], [300]~[900],由淺到深,使用時如上面的第 2~6 行,可以指定,沒指定就是[500]。
  • Colors.grey 沒有 accent 在尾巴,所以自然是主色,但是它太常用了,所以多加了 [350], [850] 兩個色階,共 12 個色階。 
  • 強調色只有:[100], [200], [400], [700] 四個色階,沒指定就是[200]。
  • 黑白色獨立,以透明度區分,如:black54 = 黑色,54% 透明。 

好啦,這樣就很清楚知道什麼是有「s」的 Colors 了。

黑白色很特別,嚴格來說,它們並不是 material color,各位可以試試把 style.dart 中的 primarySwatch 改成黑色,如:

primarySwatch: Colors.black38, 

Flutter 很快就告訴你說,不行,Colors.black38 不是 material color,不能用來指定 primarySwatch 色盤顏色,色盤顏色必須要是 Material design 的主色,它要有十個色階,其實 primarySwatch 就是用來快速一次設好整個顏色主題 theme 用的,它會將不同的色階用在不同的地方。

要自訂 material color 其實也不難,如下:

// from: https://stackoverflow.com/a/47740454/4080636
MaterialColor myGreen = const MaterialColor(0xFFAAD400,
  const {
    50 : const Color(hex_value1),
    100 : const Color(hex_value2),
    200 : const Color(hex_value3),
    300 : const Color(hex_value4),
    400 : const Color(hex_value5),
    500 : const Color(hex_value6),
    600 : const Color(hex_value7),
    700 : const Color(hex_value8),
    800 : const Color(hex_value9),
    900 : const Color(hex_value10)
  }
);

所以你可以將任何顏色設定為 material color,再套入 primarySwatch 中,就是你的自訂色盤了。

除了 primarySwatch 色盤外,你可以在 theme 中用任何顏色,包含黑白,只是還是我那句老話:自訂顏色在 Flutter 上真的一點都不難,但是要好看,就真的很難。


ThemeData.from 

所以如果你的需求又要顏色來自別人,又想要可以重新設定,就可以用 ThemeData.from,範例如下:

final themeFrom = ThemeData.from(
  colorScheme: const ColorScheme.highContrastLight(
    secondary: Colors.red,
  ).copyWith(
    primary: Colors.pink,
  ),
);

只是,這樣的使用情境好像很少,有用到的留個言吧,我還真想不到。


不要個別的設定 widget 顏色

將模擬器切到黑暗模式,執行 App,我們看到 ListView > Card 裡面的箭頭是藍色的:

說實話,好看嗎?見仁見智了,不過我們知道,這個顏色是寫死在 my_home_page.dart 裡的:

trailing: Icon(
  Icons.play_arrow,
  color: Colors.blueAccent,

這就一定是個「笨」寫法,想想看,當你的程式寫到幾千行時,有一天你的老闆或業主走過來說:所有的 Play 箭頭顏色都要改成紅的,你可能就要加班了,所以,我的建議就是:絕對不要將顏色寫死在個別的 widget 或 screen 中,將他們統一寫到 style.dart 裡面!

建議使用 theme

顏色的協調一直都是 UI 設計的重點,也是每一個設計語言的重點,既然我們都已經定好了 theme 的顏色,就應該使用它,讓這個箭頭的顏色跟整個 theme 一致,使用 theme 顏色也非常簡單,就是選一個 theme 的顏色,如下:

trailing: Icon(
  Icons.play_arrow,
  color: Theme.of(context).primaryColor,

在這個例子裡,我們是使用 theme 中的 primaryColor 來當作這個箭頭的顏色,UI 變成:

不知道各位讀者覺得如何,我是覺得這樣好看也一致多了,但是不要忘記了,這可只是黑暗模式,另一還有個淺色模式,使用 Theme.of(content) 的好處是一次就可以將兩個模式都整合設定好,要調整也是整個 App 一起調,協調性與方便性都最好!

不管,我就是要一個特別色!

也許,不知道什麼理由,你的這個顏色就是要不同於 Theme 的顏色,強烈建議將這個顏色寫在 style.dart 中,如:

const cardListArrow = Color.fromRGBO(0xCC, 26, 255, 50.0);

然後就可以到處用,如:

color: cardListArrow,

就這樣,不管是用不用 theme 顏色,所有的顏色就都統一寫到 style.dart 裡面了,再也不怕要改顏色了。

widget 的預設顏色

事實上,所有的 Widget 都有使用 Theme 的預設顏色,我們現在就可以把這個 play arrow 的顏色 remark 起來,再執行一次,來看看 Flutter 的預設顏色是什麼:

嗯,很不錯呢,所以其實大部分的情況下,我們都「應該」使用預設的顏色,因為 Flutter 已經根據 material design 將顏色都預設好了,改一髮動全身,只有必要時,才需要自己設定顏色。

你可能會問,那要怎麼查每一個 widget 預設的 theme 顏色呢?好問題,Flutter 的文件關於這方面寫得很差,也可以說根本就沒寫,如果真的很想要知道,目前大概只能從原始碼中查到,整合的文件只有這個,但也還在開發中。還好,實務上,我們一般的用法都是:

  1. 先使用預設的顏色,
  2. 不行,再將 Widget 換用不同的 Theme.of(content) 的顏色,
  3. 最後都不行,才自行設定 Widget 的顏色。


實際開發的使用建議

建議就是使用 material 的預設,當你能夠接受一切都是使用 Flutter 的 Material design 預設為主時,這是最簡單,也可能是最「好」的方法,直接就延伸 light 或是 dark 的 theme 就好,簡單又美麗,要做一些小幅的改變,再用 ThemeData.light()/dark().copyWith(),是的,這樣用會有很多限制,例如不能用 primarySwatch 改色盤、字體、等等 ⋯⋯,這很自然,你就是使用 Flutter 的 Material design 預設了,自然不能改變。

如果你就是一定要自己來,那你就應該要全面重新自定一個 theme,記住,這絕不是一件簡單的事,但是,這樣做有最大的彈性,如果這是你所要的,那就請繼續看下去:


自定 theme

我很不建議這樣用,但是,這篇文章這本書就是學 Flutter,所以當然也應該要知道如何自定 theme,自定 theme 很麻煩,也很不容易做出一個好看的 UI,但是,卻有巨大的彈性,什麼都可以改,不只顏色,包含字體、形狀、等等 ⋯⋯ 都可以改,真的很強。

自定 theme 也一樣要寫在 lib/theme/style.dart 中,將以下的程式碼加入其中:

//
// 自定 Theme
//
final themeCustomLight = ThemeData(
  brightness: Brightness.light,
  colorScheme: ColorScheme.light().copyWith(
      primary: Colors.pink,
      secondary: Colors.pinkAccent,
      background: Colors.blueGrey[100]),
  //accentColor no longer work, https://flutter.dev/docs/release/breaking-changes/theme-data-accent-properties
  textTheme: myBaseTextTheme,
  // fontFamily: 'Georgia', // 還是 light, dark 都要寫一次
  appBarTheme: AppBarTheme(
    backgroundColor: Colors.pink,
  ),
  bottomAppBarTheme: BottomAppBarTheme(
    color: Colors.pink,
  ),
  floatingActionButtonTheme: myFabTheme,
);

final themeCustomDark = ThemeData(
  brightness: Brightness.dark,
  colorScheme: ColorScheme.dark().copyWith(primary: Colors.blue),
  textTheme: myBaseTextTheme,
  floatingActionButtonTheme: myFabTheme,
);

const myFabTheme = FloatingActionButtonThemeData(
  foregroundColor: Colors.yellow,
  backgroundColor: Colors.pink,
  shape: CircleBorder(
    side: BorderSide(
      width: 3,
      color: Colors.red,
      style: BorderStyle.solid,
    ),
  ),
);

const myBaseTextTheme = TextTheme(
  headline5: TextStyle(
      fontSize: 20.0, fontWeight: FontWeight.w800, fontFamily: 'JBMono'),
  headline6: TextStyle(
      fontSize: 18.0, fontWeight: FontWeight.w600, fontFamily: 'JBMono'),
  subtitle1:
      TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold), // ListTile title
  subtitle2: TextStyle(
      fontSize: 18.0, fontWeight: FontWeight.normal, fontFamily: 'JBMono'),
  bodyText2: TextStyle(
      fontSize: 16.0, fontStyle: FontStyle.italic), // ListTile subtitle
);
  • 第 3~20 行:是設定淺色模式的 theme:
    • 第 5 行:用 brightness 將此模式設為預設的淺色模式;
    • 第 6~8 行:將淺色模式預設的淺藍色主色及副色改成粉紅,背景從白色改成淺灰;
      • 請注意,我們在這裡是使用 colorScheme 來設定顏色,並沒有使用 primaryColor 等傳統用法,這是因為 Flutter 未來也將改用 colorScheme 來設定顏色;
      • 寫在這裡,只有淺色模式有被改顏色,黑暗模式並沒有改,事實上,這是正確的,黑暗模式的顏色本來就與淺色模式完全不同。
    • 第 11 行:文字樣式就用另外設定通用的 myBaseTextTheme:
      • 這樣分開寫就不用寫兩遍,才會 DRY(Don't repeat yourself,不要重複你自己),這個觀念很重要,不只是省打幾個字,最主要是很容易忘了改,只改一部分程式碼。
    • 第 12 行:設定字體必須在各別的 theme 中設定,關於字體我們會在下面另闢章節說明,在這裏,我們還是用預設字體,所以就先 remark 起來,留作為來參考。
    • 第 13~18 行:在淺色模式中,我們的主色及副色改成粉紅,所以我把 AppBar 及 BottomAppBar 的相對顏色也改成粉紅。
    • 第 19 行:FloatingActionButton 的樣式也是淺深色模式通用,所以當然也要 DRY,就另外寫在 myFabTheme。
  • 第 22~27 行:是設定黑暗模式的 theme:
    • 基本上等於沒改,這在黑暗模式中很常見,黑暗模式很單純的難搞,就是一堆黑色階,比較特別的是 FloatingActionButton 現在與淺色模式共用了,所以兩個會長的一模一樣,顏色也一樣了。
  • 第 29~39 行:是設定淺色與黑暗模式通用的 FloatingActionButton theme:
    • 按鈕的文字設成黃色,背景改成粉紅色,形狀還是圓的但是有一個紅色的外圈,就這樣,以後如果要改設計,只要這裡就好了。
  • 第 41~52 行:是設定淺色與黑暗模式通用的 Text theme 文字樣式: 
    • TextStyle 可以設定的項目繁多,字型、大小、顏色、底線、等等⋯⋯,我們在這裏主要是改變了大小與粗細;
    • Flutter 目前有 13 個預設文字樣式,我們只改了其中的幾個,完整的名稱與相對的設定如下表,這是從官方網站中擷取出來的,請注意,名稱在 2018 年改過,所以現在請用最右邊的 2018 NAME。
NAME       SIZE   WEIGHT   SPACING  2018 NAME
display4   112.0  thin     0.0      headline1
display3   56.0   normal   0.0      headline2
display2   45.0   normal   0.0      headline3
display1   34.0   normal   0.0      headline4
headline   24.0   normal   0.0      headline5
title      20.0   medium   0.0      headline6
subhead    16.0   normal   0.0      subtitle1
body2      14.0   medium   0.0      body1 (bodyText1)
body1      14.0   normal   0.0      body2 (bodyText2)
caption    12.0   normal   0.0      caption
button     14.0   medium   0.0      button
subtitle   14.0   medium   0.0      subtitle2
overline   10.0   normal   0.0      overline
自選字體

在 style.dart 的第 41~52 行中,我們的 fontFamily 指定了一個 'JBMono',這就是自選字體,在 Flutter 中自選字體很簡單,特別是字體來自 Google Font,程序如下:

  1. Google Font 中選一個你的最愛字體,我建議選 variable fonts,簡單好用又輕巧,記住,一個 font 通常上百 k,不是 variable fonts 的話,大小粗細斜體都算一個 font,隨便選一選也不小。
  2. 下載字體,解壓縮,將 ttf 及 otf 的檔案移到你喜歡的 font 目錄,目前大家的習慣是放在 app/assets/fonts 裡面,我的建議是跟著大家走。我們是下載 JetBrains Mono,它是 variable fonts,所以只有兩個 ttf 檔案:JetBrainsMono-VariableFont_wght.ttf 及JetBrainsMono-Italic-VariableFont_wght.ttf,新增一個 happy_record/assets/fonts 目錄,將這兩個檔案搬過去。
  3. 修改 pubspec.yaml,將新增的字體及位置告訴 Flutter,原來的 pubspec.yaml 在最下面就有字體的註記,改一改就好,別搞錯正體跟斜體了:
fonts:
  - family: JBMono
    fonts:
      - asset: assets/fonts/JetBrainsMono-VariableFont_wght.ttf
      - asset: assets/fonts/JetBrainsMono-Italic-VariableFont_wght.ttf
        style: italic

這樣就將新字體加好了,可以用 fontFamily 來指定,沒指定就是用預設字體,想知道 Flutter 的預設字體嗎?不要太好奇,這其實並不簡單,想想看,在每一個平台上的預載字體都不一樣,Flutter 的預設字體自然也要不同,真想知道可以看 sdk 中的:

/flutter/packages/flutter/lib/src/material/typography.dart 原始碼

使用自定的 theme

自定 theme 的名稱變了,當然在 main.dart 中要記得改,如下: 

return MaterialApp(
  title: 'Flutter Demo',
  theme: themeCustomLight,
  darkTheme: themeCustomDark, // Provide dark theme.
  themeMode: ThemeMode.system,

好啦,那我們要怎麼使用自定的 theme 呢,我先把改好的圖讓大家看,再來看怎麼寫的:

淺色模式改粉紅
黑暗模式的 FAB 與淺色長的一樣了

好不好看不討論,見仁見智,我們是以學習分享為主,現在我們就來看看在 widget 中是怎麼用的:

  • MyHomePage 及 AudioSession 中都是使用 Scaffold,我們將 Scaffold 的背景顏色改了:
    • 加上:backgroundColor: Theme.of(context).colorScheme.background
    • 注意:我們用的是 colorScheme.background,如同之前所說,Flutter 未來都會以 colorScheme 為主,所以我們也應該都改用 colorScheme。
  • MyHomePage 中:
    • 有注意到 MyHomePage 中的 ListTile 字體變了嗎?它就是使用 theme 中 myBaseTextTheme 所設定的字體,以後要改變就改 style.dart 就好:
      • ListTile 的 title 預設就是使用 subtitle1
      • ListTile 的 subtitle 預設就是使用 bodyText2
    • 有注意到 ListView 的箭頭兩個模式顏色不一樣嗎?我們將它的顏色改成用 colorScheme.secondary,這在淺色 theme 中已經改成 Colors.pinkAccent,但是黑暗模式沒變,所以在兩個模式的顏色就不同了,也更一致,碼如下:
      • color: Theme.of(context).colorScheme.secondary
    • MyHomePage 中的 FloatingActionButton 是使用 theme 中的 myFabTheme 設定,淺色與黑暗模式都用同一個設定,所以顏色與造型都一樣了,以後變動也只要改 style.dart 就好了。
  • AudioSession 中:
    • 也用 ListTile widget,所以它的字體自然也與 MyHomePage 一樣,你看,這就是在 theme 設定的好處,方便又一致。
    • 中間的文字我改變了,用 theme 的方式改的,改這個只是要說明而已,如下:
      • style: Theme.of(context).textTheme.subtitle2
    • 下面的兩個按鈕,背景與文字都是用 colorScheme.secondary,所以兩個模式的顏色不同,暗黑變成綠色的,不是很好看,未來可能在暗黑的 theme 中,加上 secondary。
  • Page404 的哭臉 icon 原來是紅色,改成用 theme 的 error color,如下:
    • color: Theme.of(context).colorScheme.error
    • 傳統的寫法是:Theme.of(context).errorColor

你看看,自定 theme 真是麻煩啊,光改這麼小的一個 App,就要寫這麼多行程式碼,而且還要事先做好規劃,更不要忘了,這還是我們將 App 中的 theme 設定完全分離後「乾淨版」!

在這篇文章中,我們全部都是使用 theme,也證明 theme 中可以使用任何顏色,我們盡量不使用自訂的顏色,這點很重要,要知道,App 並不是單獨的存在與執行,它是跑在 Andriod、iOS 或是 Windows 上的,一個與作業系統不同的主題色系的 App 真的會很怪異的存在,除非設計功力超強,不然真的不建議。

好啦,基本上到此,我們就已經對 material color 在 Flutter 中有足夠的了解了。

最後的程式碼,請自行參考本書:程式碼備份中的 Code milestone 3


進階討論

如果你剛剛一步一步的看到這裡,那你還是菜鳥,可以跳過這段,下面是一些進階的 Theme 使用方法,放在這裡方便未來查詢。

我們要怎麼知道目前 App 是在黑暗還是明亮模式?

有一個觀念很重要,App 的 Theme 模式與電腦系統的 Theme 模式是獨立的兩個系統,舉個例子,你的電腦系統可以設定為黑暗模式,但是在 App 中,你可設定為明亮模式,所以,上面這個問題就要問:你要知道的是 App 目前的 Theme 還是電腦系統目前的 Theme,兩個的讀取方法不同:

App 目前的 Theme,可以用下面的方法知道:

(Theme.of(context).brightness == Brightness.dark)
? #暗黑 : #明亮

電腦系統目前的 Theme:

(MediaQuery.of(context).platformBrightness == Brightness.dark) 
? # 暗黑 : # 明亮
# or use: SchedulerBinding.instance!.window.platformBrightness

 我是不大喜歡第 3 行的用法,不過它不用依靠 context,有其市場。

 

 



WriterShelf™ is a unique multiple pen name blogging and forum platform. Protect relationships and your privacy. Take your writing in new directions. ** Join WriterShelf**
WriterShelf™ is an open writing platform. The views, information and opinions in this article are those of the author.


Article info

This article is part of:
Categories:
Tags:
Date:
Published: 2021/07/11 - Updated: 2022/02/10
Total: 6456 words


Share this article:
About the Author

很久以前就是個「寫程式的」,其實,什麼程式都不熟⋯⋯
就,這會一點點,那會一點點⋯⋯




Join the discussion now!
Don't wait! Sign up to join the discussion.
WriterShelf is a privacy-oriented writing platform. Unleash the power of your voice. It's free!
Sign up. Join WriterShelf now! Already a member. Login to WriterShelf.