如何在 Flutter 中簡單實現滑順的 Light/Dark 模式切換

閱讀時間約 9 分鐘


raw-image

許多 App 都會支持 Light 模式與 Dark 模式,增加使用者體驗,為了做到這個功能,我們可以使用內建 Theme 加上 StatefulWidget 或其他狀態管理套件,就可以輕鬆完成 Light 模式與 Dark 模式。讓我們看看一個簡單的例子。

簡單的切換 Theme

main() {
runApp(const MainApp());
}

class MainApp extends StatefulWidget {
const MainApp({super.key});
@override
State<MainApp> createState() => _MainAppState();
}

class _MainAppState extends State<MainApp> {
bool isDark = false;
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: isDark ? ThemeData.dark() : ThemeData.light(),
home: Scaffold(
body: const Center(
child: Text("Hello World"),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.refresh),
onPressed: () => setState(() => isDark = !isDark),
),
),
);
}
}

我們在 StatefulWidget 中維護了 isDark 變數,當使用者按下左下角按鈕時,就會更新 isDark,並設定不同模式的 ThemeData 給 MaterialApp,藉此達到切換 Light / Dark 模式的效果,而在切換過程中,Flutter 也會幫我們用漸變的方式切換主題,而不是一瞬間就切換完成,增加視覺效果。

raw-image

自定義的 ThemeExtension

有些時候,當我們的設計不是遵循 Material Design 的話,Flutter 提供的 Theme 就會不足以完成我們的需求,此時我們就會使用 ThemeExtension 的功能來擴充 Theme,讓整個 App 都可以使用一致的設計。下面是一段我們自定義的 ThemeExtension,其中除了自定義的顏色設計之外,我們還需要實作 copyWith 與 lerp 方法。

class MyThemeExtension extends ThemeExtension<MyThemeExtension> {
final Color backgroundColor;

MyThemeExtension(this.backgroundColor);

@override
ThemeExtension<MyThemeExtension> copyWith() {
// TODO: implement copyWith
throw UnimplementedError();
}

@override
ThemeExtension<MyThemeExtension> lerp(covariant MyThemeExtension other, double t) {
// TODO: implement lerp
throw UnimplementedError();
}
}

還記得剛剛提到的,切換 Light / Dark 模式時,Flutter 會用漸變的方式,讓畫面漸漸的從 Light 模式轉變為 Dark 模式 (其實動畫時間很短,一下次就轉換完成 XD),為了讓自定義的 ThemeExtension 也能享受到這個效果,實作 lerp 方法就很重要了。lerp 方法會傳入要轉換的 Theme 與動畫時間,讓我們可以自行決定顏色在轉換過程中如何變化,在這邊我們簡單地使用 Color.lerp 來協助做線性轉換即可。

@override
ThemeExtension<MyThemeExtension> lerp(covariant MyThemeExtension other, double t) {
return MyThemeExtension(
Color.lerp(backgroundColor, other.backgroundColor, t)!,
);
}

當我們實作了 lerp 方法之後,再次打開 App 切換 Light / Dark 模式,會發現我們自定義的顏色也能在切換過程中有平滑的視覺效果。

raw-image

完整效果請參考這邊

更酷炫的切換動畫

在研究 Light / Dark 模式切換的過程中,發現了很特別切換效果,詳細可以參考這邊。當使用者切換模式時,畫面會由左上角開始轉換,並往右下角輻射,直到所有畫面都轉換完成,讓我們直接看看下面圖片。

raw-image

與 Flutter 預設的切換方式不同,這種切換方式更令人眼睛一亮,讓我們來看看這是如何做到的。其實要做到這個效果也並不複雜,主要原理是使用 Stack + ClipPath 來完成,簡單來說就是,先在 Stack 中疊上 Light 模式 Widget,然後再疊上被 ClipPath 裁切過的 Dark 模式 Widget,最後透過動畫來慢慢放大被裁切的 Dark 模式 Widget,最後填滿的畫面。如此一來,就能完成這個酷炫的 Light / Dark 模式切換效果。

@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animationController,
builder: (BuildContext context, Widget? child) {
return Stack(
children: [
Theme(
data: _getTheme(!widget.isDark),
child: widget.builder(context),
),
ClipPath(
clipper: CircularClipper(
1.5 * MediaQuery.of(context).size.height * _animationController.value,
Offset.zero,
),
child: Theme(
data: _getTheme(widget.isDark),
child: widget.builder(context),
),
),
],
);
},
);
}

完整效果請參考這邊

小結

為 App 增加一些轉場特效,像是我們今天分享的 Light / Dark 模式切換,或者是 Routing 時的 Transition 效果,都能有效增加使用者體驗,讓 App 看起來更加精緻。

分享各種 Flutter 開發技巧
留言0
查看全部
發表第一個留言支持創作者!
本文探討如何利用 ListView 實現自動對齊的效果。深入說明如何透過覆寫 ScrollPhysics 中的相關方法來達成精確的滾動模擬,讓使用者在滑動列表時獲得更佳的體驗。讀者也能學習到如何調整滑動細節,提供開發上的新思路和技巧。
本文探討如何利用 ListView 實現自動對齊的效果。深入說明如何透過覆寫 ScrollPhysics 中的相關方法來達成精確的滾動模擬,讓使用者在滑動列表時獲得更佳的體驗。讀者也能學習到如何調整滑動細節,提供開發上的新思路和技巧。
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
這篇內容,將會講解什麼是「switch」,以及與「switch」相關的知識。包括switch的簡介、switch、break。
在上篇瞭解完Actions & Blinks的整個工作流程後,我們就來學習如何用程式碼構建屬於自己的Actions & Blinks吧!本篇詳細講解了要自製Actions & Blinks的所有步驟並提供範例程式碼給大家參考,相信能讓大家快速入門!
Thumbnail
不論是 Astra、Blocksy 還是 Kadence 佈景主題,都有內建頁首與頁尾編輯器,你可以在外觀自訂器中以所見即所得的方式新增各種元素,像是選單、按鈕及社群圖示。
Thumbnail
這個系列的教學會列出基本上所有常見的造型和一些基礎 , 讓各位可以自行搭配造型~在這個第四篇呢 , 我們會來一起了解:常見的窗口 / 框架造型定義他們的意思加了之後有什麼效果那就讓我們開始吧!
Thumbnail
# 簡介 身為一位專注於 Vue.js 的前端開發者,這是我第一次嘗試構建 Flutter 網頁應用。讓我們開始吧! ## 第一次嘗試 ### 第一步:創建一個 Flutter 應用 首先,通過運行以下命令來創建一個新的 Flutter 項目: ```sh flutter
setter和getter能把狀態改變時需做的事情包裝起來,讓外部只需簡單修改參數就能達到預想的效果
Thumbnail
如何透過 CSS 來美化和增強文本的可讀性,對於提升用戶體驗至關重要。本文將介紹如何使用 CSS 來處理網頁上的文本,包括字型設定、文本排列、裝飾等多方面。
Thumbnail
有沒有想過,即使沒有任何編程背景,你的創意也能在六個月內轉化成真實的App?我可以以自身經歷跟你說有了 No-Code Tool (無代碼工具) 和 AI 的幫助,這一切都是可能的!你一行 code 都不需要打,甚至也無須學習任何編程語言!沒有什麼比實踐一個自小認為不可能的任務還振奮人心的事了!
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
這篇內容,將會講解什麼是「switch」,以及與「switch」相關的知識。包括switch的簡介、switch、break。
在上篇瞭解完Actions & Blinks的整個工作流程後,我們就來學習如何用程式碼構建屬於自己的Actions & Blinks吧!本篇詳細講解了要自製Actions & Blinks的所有步驟並提供範例程式碼給大家參考,相信能讓大家快速入門!
Thumbnail
不論是 Astra、Blocksy 還是 Kadence 佈景主題,都有內建頁首與頁尾編輯器,你可以在外觀自訂器中以所見即所得的方式新增各種元素,像是選單、按鈕及社群圖示。
Thumbnail
這個系列的教學會列出基本上所有常見的造型和一些基礎 , 讓各位可以自行搭配造型~在這個第四篇呢 , 我們會來一起了解:常見的窗口 / 框架造型定義他們的意思加了之後有什麼效果那就讓我們開始吧!
Thumbnail
# 簡介 身為一位專注於 Vue.js 的前端開發者,這是我第一次嘗試構建 Flutter 網頁應用。讓我們開始吧! ## 第一次嘗試 ### 第一步:創建一個 Flutter 應用 首先,通過運行以下命令來創建一個新的 Flutter 項目: ```sh flutter
setter和getter能把狀態改變時需做的事情包裝起來,讓外部只需簡單修改參數就能達到預想的效果
Thumbnail
如何透過 CSS 來美化和增強文本的可讀性,對於提升用戶體驗至關重要。本文將介紹如何使用 CSS 來處理網頁上的文本,包括字型設定、文本排列、裝飾等多方面。
Thumbnail
有沒有想過,即使沒有任何編程背景,你的創意也能在六個月內轉化成真實的App?我可以以自身經歷跟你說有了 No-Code Tool (無代碼工具) 和 AI 的幫助,這一切都是可能的!你一行 code 都不需要打,甚至也無須學習任何編程語言!沒有什麼比實踐一個自小認為不可能的任務還振奮人心的事了!