0%

【文章內容使用 Gemini 1.5 Pro 自動產生】

來自 Flutter 遊戲開發工具早期採用者的觀點

簡介

在今年的 Google I/O 上,我們推出了 Flutter 休閒遊戲工具包,這個工具包是一套資源,用於支援 Flutter 社群中的遊戲開發者。在此之前,Flutter 並未將遊戲開發作為官方支援的用例,儘管我們對一些成功案例印象深刻,例如流行的文字解謎遊戲 4 圖 1 字 以及 Flame 引擎等社群努力。那麼,為什麼我們決定正式支持遊戲開發,以及我們如何決定工具包的初始版本應該包含哪些內容呢? 在這篇文章中,我們將分享與 Flutter 遊戲開發工具的早期採用者進行的兩項使用者研究,並解釋他們的觀點如何塑造我們的策略和工具包的設計。

我們從一項訪談研究開始,在研究中,我們分別與 6 位成功開發和發佈了 Flutter 遊戲的開發者(稱為 P01 到 P06)進行了會面。我們對這項研究有兩個目標。第一是了解哪些類型的資源對於開始進行 Flutter 遊戲開發最有用。第二是更好地了解 Flutter 開發者在遊戲開發中的動機、需求和痛點,並利用這種理解來戰略性地規劃我們對該領域的長期投資。為了補充訪談的定性研究結果,我們還在廣泛的 Flutter 使用者社群中針對這個主題進行了一項調查。

在本文的其餘部分,我們首先總結了訪談和調查研究的主要發現,然後我們分享關於市場定位、文件和學習資源以及潛在的長期投資以支援 Flutter 遊戲開發者的建議。

開發者訪談的發現

Flutter 遊戲開發者的背景

總體而言,我們發現了當今存在的兩種 Flutter 遊戲開發者原型:

  • 應用程式 - 遊戲混合開發者: 他們是 Flutter 應用程式開發者,但他們也利用 Flutter 開發相對簡單的遊戲,而不需要使用其他工具。(P02、P04 和 P05)
  • 計算機藝術家和圖形愛好者: 他們主要被 Flutter 提供的對圖形的控制程度所吸引,但他們可能不會將 Flutter 用於應用程式開發。(P01 和 P03)

下表提供了有關我們在本次研究中所訪談的開發者背景的更多資訊。有幾位是由 Flame 團隊推薦給我們的。

使用 Flutter 開發遊戲的感知益處

參與者列出了使用 Flutter 開發遊戲的許多益處。我們列出了下面由多位參與者提到的那些益處:

  • 簡單性: 參與者認為,與 Unity 和 Unreal 等大型遊戲引擎相比,使用 Flutter 開發 2D 休閒遊戲更容易上手,也更不複雜。(P03、P04)
  • 控制: 參與者欣賞能夠在 IDE 中看到 Flutter 的原始碼,並完全直接地控制畫布。(P01、P02、P04)
  • 生態系統: 開發者可以利用 Flutter 生態系統中的套件,例如資料持久性和音訊播放。(P01、P02、P03、P04)具體而言,P01 認為 Flutter 的音訊功能優於其他跨平台 UI 工具包。
  • 跨平台: 參與者欣賞輕鬆部署到 iOS 和 Android 的便利性,並認為它提供了「真正的單一程式碼庫」。 (P01) 但是,由於遊戲引擎通常是跨平台的,所以這並不常被提及。
  • 輕鬆建立 UI: Flutter 遊戲可以在其 UI 中輕鬆利用任何 Flutter Widget。(P04、P05)這對於更類似於應用程式的遊戲特別有用,這些遊戲不需要使用遊戲引擎(例如,P05 和 P06 開發的文字解謎遊戲)。
  • 學習機會: Flutter 和 Flame 的開源性質提供了一個機會,透過開發遊戲來深入了解遊戲引擎和圖形管線。(P03)
  • Flame 遊戲引擎: 使用 Flame 的參與者普遍對該專案及其功能以及 Flame 提供的支持表示肯定。P04 特別是在 Flutter 上開發了一款遊戲,因為他發現了 Flame。

使用 Flutter 開發遊戲的感知挑戰

作為一個新興用例,在 Flutter 上開發遊戲並非沒有挑戰。參與者強調了他們在各自專案中遇到的以下問題:

  • 整合和生態系統:

    • 难以與 Play 遊戲服務整合。(P01、P03)
    • 缺少用於離線優先資料庫的良好選項,這些資料庫具有衝突管理功能。(P05)
    • 需要指導以選擇最佳的音訊外掛,該外掛可以同時在前景和背景中播放音訊。(P05)
    • 資產:尋找和建立高品質的遊戲資產非常耗時。(P02、P03、P04)
    • 這可能是最耗時的過程。如果我找不到彼此相符的資產,遊戲看起來就不會完全是我想要的樣子。而且由於我不太擅長製作藝術品,所以找到合適的資產對我來說是最具挑戰性的任務。 (P03) *
  • Flame 特定的挑戰:

    • 參與者指出,Flame 的文件對初學者不友好。特別是,Forge2D(Flame 的一部分)需要更好的文件。(P03、P04)
    • 參與者指出,Flame 中的碰撞檢測過於基礎。在兩個物體碰撞後,它不會處理物理效果,除非您使用 Forge2D。(P03)
  • Flutter SDK 和工具中缺少的功能:

    • 缺乏 3D 支援。(P02、P03)但是,這對這些參與者來說似乎並不重要。
    • 沒有用於濾鏡、效果、層或合成圖的直接著色器訪問權。(P01、P02)這對熱衷於電腦圖形的參與者來說比較重要。
    • 缺少用於控制更新率(例如 120hz)的使用者空間 API。(P01、P02)
    • 不確定 Flutter 是否擁有提供對畫面緩衝區可見性的著色器除錯工具。Xcode 有很棒的 OpenGL 除錯工具。(P01)
  • 效能和套件大小:

    • DevTools 在診斷具有遊戲迴圈的遊戲的效能問題時並不總是很有用。(P02)
    • 在低階 iOS 設備上存在動畫卡頓。著色器預熱解決方案很有用,但非常耗費人力。 (P05) 也沒有關於如何做到的視訊教學。(P03)
    • 缺乏對根據設備的效能特性調整應用程式渲染品質的支持。 (P05)
    • Web 套件大小對於 Facebook Instant Game 等網頁遊戲平台來說太大。 (P05)

將 Flutter 與其他遊戲開發工具進行比較

由於本研究的選擇標準,所有參與者對其他遊戲引擎的經驗都非常有限。但是,他們就入門、控制感和套件生態系統提供了他們的印象。

首先,參與者認為 Flutter/Flame 比 Unity 和 Unreal 等大型遊戲引擎更容易上手和理解。(P01、P02、P03、P04)參與者發現,Unity 或 Unreal 中的許多功能與他們正在開發的遊戲類型無關。

其次,由於我們的參與者是經驗豐富的程式設計師,他們發現很難從 Unity 或 Unreal 的低程式碼開發環境中獲得他們需要的控制權。

我真的很喜歡深入研究並完全控制所有細節。因此,使用 Flutter,Flame 引擎非常開放。我覺得我真的擁有完全的控制權,而在 Unity 中,一切都隱藏起來了。他們試圖為休閒使用者自動化某些操作。 (P01)

最後但並非最不重要的是,Flutter 的生態系統被認為優於其他輕量級遊戲引擎,例如 Godot(P02、P03)。例如,P03 表示,使用 package:hive,在原生平台上進行資料持久化非常容易。他懷疑使用 Godot 可能需要原生程式碼。

對第一次使用 Flutter 開發遊戲的開發者有用的資源

所有參與者都提到,在開始使用 Flutter 開發遊戲時,他們需要更好的文件和學習資源。為了確定優先順序,我們要求參與者透過將不同類型的資源分類到三個桶中來評估它們對遊戲開發的重要性:必須擁有應該擁有可以擁有。以下清單顯示了所得的桶:

「必須擁有」的資源包括:

  • 一個 範例遊戲,其中預先整合了常見的遊戲特定技術(例如音訊支援)和平台遊戲服務(例如 Google Play 服務)。

  • 一個專用的網站,其中包含所有遊戲開發資源以及有關第三方服務(例如套件、資產市場)的推薦。

  • 操作指南視訊,涵蓋基本的遊戲開發概念(例如遊戲迴圈)、與後端的整合、遊戲支援和變現服務。P03 也對像 無聊的表演 等長篇視訊感興趣,這些視訊涵蓋了整個遊戲開發過程,包括疑難排解:

    • 我覺得兩種不同的 [視訊] 內容會更好。一種是類似於「本週 Widget」的非常短的內容,然後是類似於「無聊的 Flutter 表演」的內容,其中您會深入了解整個過程,並逐步完成建立該遊戲的所有步驟。 (P03)*

「應該擁有」的資源包括:

  • 一個專門針對 Flutter 遊戲的 Discord 頻道,這個頻道比 Flame 團隊的 Discord 更通用。

「可以擁有」的資源包括:

  • Codelabs
  • 教師指導的研討會
  • Google 服務的信用額度(例如 GCP、廣告、Firebase)
  • 直接存取專用支援

根據參與者的寶貴意見,我們在休閒遊戲工具包的初始版本中優先考慮了「必須擁有」的資源。

調查的發現

如前所述,我們進行了一項補充調查,以更廣泛地了解 Flutter 社群中的遊戲開發情況。以下圖表顯示了高階結果:

Flutter 使用者有興趣使用 Flutter 開發遊戲

許多 Flutter 使用者(62.4%)不知道他們可以使用 Flutter 開發遊戲,這並不奇怪。然而,39.2% 的人有興趣了解更多。

Results for the survey question “Did you know that you can use Flutter to build games?” 39.3% — “No, and I’m interested in learning more.” 37.6% — “Yes.” 23.2% — “No, I’m not interested in the topic.”

許多 Flutter 使用者對使用 Flutter 開發遊戲的廣泛興趣也反映在許多 Flutter 使用者對遊戲開發並不陌生這一事實中。在 936 位受訪者中,有 500 多人有使用 Unity 的經驗,130 多人有使用 Unreal 的經驗,120 多人有使用 Flame 的經驗,80 多人有使用 Godot 的經驗。看到這麼多開發者已經具備遊戲開發技能,這令人鼓舞。

許多 Flutter 遊戲開發者才剛起步

然後,我們詢問那些了解或對 Flutter 開發遊戲的能力感興趣的人,他們在遊戲開發過程中處於什麼階段。在這個群體中,43.2% 尚未開始具體的遊戲專案,這表明需要入門支持。

Summarized responses to the question: “which of the following statements best describe your experience using Flutter to build games?”

比我們想像的更多 Flutter 遊戲已經出現

我們要求受訪者向我們指出他們使用 Flutter 開發的任何遊戲。令我們驚訝的是,我們收到了 148 個回覆。其中許多遊戲是 2D 解謎遊戲或 平台遊戲,而其中一小部分嘗試使用 Flutter 創造擬 3D 效果。

使用 Flutter 開發非常成功的行動遊戲似乎是完全可能的。例如,我們透過這項調查了解的 Flutter 遊戲 Kelimelik 在撰寫本文時在 Google Play 上獲得了 4.5 星的評分,有 356,000 多條評論和 500 萬次安裝!

The kelimelik game’s listing on the Google Play store.

Flutter 使用者希望更多關於遊戲開發的文件

當我們詢問受訪者什麼因素阻礙了他們在遊戲專案中的進展時,我們收到了 31 個回覆。其中許多與缺乏文件有關:

  • 「沒有足夠的教學,我沒有理解透徹。」
  • 「沒有足夠的資源/教學/文件 (flutter flame)。」
  • 「文件對初學者遊戲開發者並不友好。在教學或案例研究演練中,還有很多工作要做,才能為 Flame 元件建立一個非常清晰的入門介紹,並提供常見的場景,以幫助新入門者。」

這個回饋呼應了我們從訪談研究中了解到的內容。調查受訪者還提到了缺少 3D 支援、使用著色器遇到困難以及特定硬體和視覺效果(例如陰影)的效能問題。

建議

我們的研究結果顯示了在 Flutter 上開發 2D 休閒遊戲的潛力。一些 Flutter 使用者嘗試過並成功發佈了高評分的遊戲,還有更多使用者有興趣在未來的幾個月裡開始嘗試。

為了讓這些新的遊戲開發者取得成功,我們必須適當地定位 Flutter 在市場上其他遊戲開發工具中的位置,並利用 Flutter 開放和參與的性質來完善其對遊戲開發的支持。

基於此,我們想提出一些建議,這些建議不僅針對 Google 的 Flutter 團隊,也針對 Flutter 社群的套件作者、工具開發者和內容創作者。

市場定位

研究結果表明了我們在遊戲開發工具市場中定位 Flutter 時需要考慮的三个维度:

遊戲類型:

  • 以 2D 休閒遊戲為目標是一個良好的起點。
  • 解謎遊戲和平台遊戲是在 Flutter 中開發的成功子類別。

開發者類型:

  • 希望利用 Flutter 的休閒遊戲開發能力的 Flutter 應用程式開發者(例如 P02、P04 和 P05)。
  • 被 Flutter 提供的高度控制和開放性所吸引的計算機藝術家和圖形愛好者(例如 P01 和 P03)。

遊戲開發團隊類型:

  • 我們可以專注於獨立遊戲開發者和混合遊戲/應用程式代理商,這些代理商的專業角色較少,可以從 Flutter 的跨平台功能中獲益更多。

文件和學習資源

根據訪談研究的使用者意見,我們認為 Flutter 社群中的創作者和教育工作者可以透過專注於建立開源範例遊戲、操作指南視訊和為遊戲開發策劃資源列表來發揮最大作用。我們在 I/O 上發佈的 Flutter 休閒遊戲工具包是我們沿著這些方向組織資源並提高它們在 Flutter 使用者中的可見度的第一次嘗試。

對 Flutter API 和功能的潛在投資

在研究過程中,參與者指出了他們希望 Flutter SDK 在未來更好地支援的一些主要產品功能:

  • 著色器的可用性和支援(GitHub 議題)。或者,如果函式庫維護者(例如 Flame)直接使用著色器 API,可能就足夠了(例如,P03 希望遊戲引擎中有一個更高階的抽象)。
  • Web 應用程式套件大小。一位參與者指出了將遊戲擴展到基於網頁的遊戲平台(例如 Facebook Instant Game)的問題。由於 Web 套件大小過大,他們最終選擇了 HTML 渲染後端,而不是速度更快但更大的 Canvas Kit 後端。(P05)
  • Windows/Steam 支援。多位參與者表達了對嘗試使用他們的遊戲的全新 Windows 支援的濃厚興趣。其中一人提到,Windows 是最需要支持的平台,特別是如果您打算在 Steam 上發佈您的遊戲。(P03)但是,鑑於沒有參與者或我們的團隊成員在 Windows 上測試過遊戲開發或整合 Steam,所以這種情況仍然不明確。

除了這三個改進領域之外,我們最近開始與社群成員合作,對與遊戲相關的議題進行分類,並將它們標記為 a: gamedev

結論

我們從這兩項研究中了解到,包括 Flutter 使用者對遊戲開發的廣泛興趣,以及現有 Flutter 遊戲開發者所欣賞的 Flutter 的簡單性和高度控制,讓團隊對追求遊戲開發作為可行的用例充滿信心。休閒遊戲工具包是我們在這個領域的第一項投資,我們期待在後續研究中了解我們的社群成員如何利用它。

致謝

Zoey FanChristopher FujinoJaYoung Lee 參與了這項研究,Shams Zakhour 編輯了這篇文章。我們要感謝參與這項研究的使用者,以及 Flame 團隊在參與者招募方面的支持。


來自 Flutter 遊戲開發工具早期採用者的觀點 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章內容使用 Gemini 1.5 Pro 自動產生】

邁向多平台 UI 開發的旅程:手機、桌面和網頁

我們很興奮地宣布,作為 Google I/O 主題演講的一部分,Flutter 3 正式推出。Flutter 3 完成了我們從以行動為中心的框架到多平台框架的路線圖,其中包含 macOS 和 Linux 桌面應用程式支援的可用性,以及 Firebase 整合的改進、新的生產力與效能功能,以及對 Apple Silicon 的支援。

Flutter 3 的旅程

我們最初開發 Flutter 的目標是徹底改變應用程式開發:將網頁的迭代開發模式與硬體加速的圖形渲染和像素級別控制(以前是遊戲的專利)結合起來。在 Flutter 1.0 beta 發佈後的過去四年裡,我們在這些基礎之上不斷發展,新增了新的框架功能和 Widget、與底層平台更深層次的整合、豐富的套件函式庫,以及許多效能和工具方面的改進。

隨著產品的成熟,越來越多的人開始使用 Flutter 構建應用程式。如今,使用 Flutter 構建的應用程式超過 500,000 個。來自 data.ai 等研究機構的分析,以及公開的見證表明,Flutter 被許多領域的 廣泛客戶群 使用:從像 微信 這樣的社交應用程式,到像 BettermentNubank 這樣的金融和銀行應用程式;從像 SHEINtrip.com 這樣的電商應用程式,到像 FasticTabcorp 這樣的休閒應用程式;從像 My BMW 這樣的伴侶應用程式,到像 巴西政府 這樣的公共機構。

如今,使用 Flutter 構建的應用程式超過 500,000 個。

開發人員告訴我們,Flutter 有助於更快速地為更多平台構建更精美的應用程式。在我們最近的使用者研究中:

  • 91% 的開發人員同意 Flutter 縮短了構建和發佈應用程式所需的時間。
  • 85% 的開發人員同意 Flutter 使他們的應用程式比以前更精美。
  • 85% 的開發人員同意它使他們能夠為比以前更多的平台發佈應用程式。

Sonos 最近的一篇部落格文章 中,他們討論了他們改進的設定體驗,並強調了其中第二點:

「毫不誇張地說,[Flutter] 解鎖了我們團隊以前從未實現的「*高級*」程度。對我們的設計師來說,最重要的是,可以輕鬆構建新的 UI,意味著我們的團隊花在對規格說「不」上的時間更少,花在迭代上的時間更多。如果這聽起來很划算,我們建議您試試 Flutter - 我們很慶幸我們嘗試了。」

介紹 Flutter 3

今天,我們推出 Flutter 3,這是我們完成 Flutter 支援平台的旅程的成果。使用 Flutter 3,您可以從單個程式碼庫中為六個平台構建精美的體驗,為開發人員提供無與倫比的生產力,並讓新創公司能夠從第一天起就將新想法推向整個可服務市場。

在之前的版本中,我們在 iOS 和 Android 的基礎上補充了 網頁Windows 支援,現在 Flutter 3 新增了對 macOS 和 Linux 應用程式的穩定支援。新增平台支援需要的更多是渲染像素:它還包括新的輸入和互動模型、編譯和構建支援、無障礙性和國際化,以及平台特定的整合。我們的目標是讓您能夠靈活地充分利用底層作業系統,同時根據需要分享盡可能多的 UI 和邏輯。

在 macOS 上,我們在支援 Intel 和 Apple Silicon 上投資了,並提供了 通用二進位檔 支援,允許應用程式封裝在兩種架構上都能原生執行的可執行檔。在 Linux 上,Canonical 和 Google 合作提供了高度整合的、最優的開發選項。

Superlist 是 Flutter 如何實現精美的桌面體驗的一個很好的範例,它今天以 beta 版推出。Superlist 提供超級加強的協作功能,透過一個新的應用程式將清單、任務和自由格式內容結合在一起,重新詮釋待辦事項清單和個人規劃。Superlist 團隊選擇 Flutter 是因為它能夠提供快速且高度品牌化的桌面體驗,我們認為他們迄今為止的進展證明了為什麼這是一個非常棒的選擇。

Flutter 3 還改進了許多基礎功能,包括改進的效能、Material You 支援和生產力更新。

除了上面提到的工作之外,透過這個版本,Flutter 在 開發 上完全原生於 Apple Silicon。雖然 Flutter 自從推出以來就一直與搭載 M1 的 Apple 設備相容,但 Flutter 現在充分利用了 Dart 對 Apple Silicon 的支援,使搭載 M1 的設備上的編譯速度更快,並支援 通用二進位檔 用於 macOS 應用程式。

我們對 Material Design 3 的工作在這個版本中已基本完成,讓開發人員能夠利用自適應的跨平台設計系統,該系統提供動態色彩方案和更新的視覺組件:

我們詳細的技術部落格文章將深入探討這些功能以及 Flutter 3 中的許多其他新功能。

Flutter 由 Dart 提供支援,Dart 是一種高生產力、可移植的語言,適用於多平台開發。我們在此週期的 Dart 工作包括新的語言功能,這些功能可以減少樣板程式碼並幫助提高可讀性、實驗性的 RISC-V 支援、升級的 linter 和新的文件。若要進一步了解 Dart 2.17 中的所有新改進,請查看 專門的部落格文章

Firebase 和 Flutter

當然,構建應用程式需要的不仅仅是 UI 框架。應用程式發行商需要一套完整的工具來幫助您構建、發佈和運行應用程式,包括身份驗證、資料儲存、雲端函數和裝置測試等服務。有多種服務支援 Flutter,包括 SentryAppWriteAWS Amplify

Google 的應用程式服務產品是 Firebase,SlashData 開發者基准研究 表明 62% 的 Flutter 開發人員在他們的應用程式中使用 Firebase。因此,在過去的幾個版本中,我們一直在與 Firebase 合作,將 Flutter 作為一級整合擴展和改進整合。這包括將 Flutter 的 Firebase Plugin 提升至 1.0 版本,新增更好的文件和工具,以及像 FlutterFire UI 這樣的新的 Widget,為開發人員提供可重複使用的 UI 用於身份驗證和檔案螢幕。

今天,我們宣布將 Flutter/Firebase 整合升級為 Firebase 提供的全功能核心部分。我們將原始碼和文件移至 Firebase 的主要存取庫和網站,您可以信賴我們會讓 Flutter 的 Firebase 支援與 Android 和 iOS 同步發展。

此外,我們對支援使用 Crashlytics(Firebase 受歡迎的即時崩潰報告服務)的 Flutter 應用程式進行了重大改進。透過 Flutter Crashlytics Plugin 的更新,您可以即時追蹤致命錯誤,為您提供與其他 iOS 和 Android 開發人員相同的功能集。這包括重要的警報和指標,例如「無崩潰使用者」,可以幫助您掌握應用程式的穩定性。Crashlytics 分析管道已升級,以改進 Flutter 崩潰的聚類,讓您可以更快地對問題進行分類、優先排序和修復。最後,我們簡化了 Plugin 安裝過程,因此您只需幾個步驟即可從 Dart 程式碼中啟動並運行 Crashlytics。

Flutter 休閒遊戲工具包

對於大多數開發人員來說,Flutter 是一個應用程式框架。但是,圍繞休閒遊戲開發的社群也在不斷增長,他們利用了 Flutter 提供的硬體加速圖形支援,以及像 Flame 這樣的開源遊戲引擎。我們希望讓休閒遊戲開發人員更容易入門,因此,在今天的 I/O 上,我們宣布 休閒遊戲工具包,它提供了一套包含範例和最佳實務的入門套件,以及廣告和雲端服務的信用額度。

雖然 Flutter 不是為高強度 3D 動作遊戲而設計的,但甚至一些這樣的遊戲也開始使用 Flutter 來處理非遊戲 UI,包括像 PUBG Mobile 這樣的熱門遊戲,該遊戲擁有數億使用者。為了讓 I/O 更上一層樓,我們想看看我們可以將技術推到什麼程度,因此我們製作了一個有趣的彈珠台遊戲,該遊戲由 Firebase 和 Flutter 的 Web 支援提供支援。I/O 彈珠台提供了一個自訂的桌子,設計圍繞著 Google 的四個最喜歡的吉祥物:Flutter 的 Dash、Firebase 的 Sparky、Android 機器人和 Chrome 恐龍,讓您可以與其他人競爭高分。我們認為這是一個展示 Flutter 多功能性的有趣方式。

由 Google 贊助,社群提供支援

我們喜歡 Flutter 的一點是,它不仅仅是 Google 的產品,它是「大家的」產品。開源意味著我們都可以參與其中,並在它的成功中發揮作用,無論是貢獻新的程式碼還是文件,建立賦予核心框架新能力的套件,撰寫書籍和培訓課程來教導他人,還是幫助組織活動和使用者群組。

為了展示社群的最佳一面,我們最近與 DevPost 合作,贊助了一項拼圖黑客挑戰賽,讓開發人員有机会透過重新構思經典的滑動拼圖來展示他們的技能。這證明了 Web、桌面和行動如何整合在一起:現在我們都可以透過網路或商店玩這些遊戲。

我們製作了這個影片來展示一些我們最喜歡的投稿和獲獎作品;我們認為您會喜歡的:

感謝您對 Flutter 的支援,歡迎來到 Flutter 3!


Introducing Flutter 3 was originally published in Flutter on Medium, where people are continuing the conversation by highlighting and responding to this story.

【文章內容使用 Gemini 1.5 Pro 自動產生】

深入了解最新版本,包括 macOS 和 Linux 穩定版本、效能提升等等!

是時候再次發佈 Flutter 穩定版本了——我們非常自豪地宣布 Flutter 3!僅僅三個月前,我們宣布了 Flutter 對 Windows 的支援。今天,我們很高興地宣布,除了 Windows 之外,Flutter 現在也穩定支援 macOS 和 Linux!

感謝 Flutter 貢獻者的辛勤工作,我們已合併了 5248 個 Pull Request!

我們在此版本中有一些令人興奮的消息要宣布,包括 Flutter 對 macOS 和 Linux 的支援更新、重大的效能提升、行動和 Web 更新——以及更多!此外,我們還有一些關於減少對舊版 Windows 支援的資訊,以及一小部分重大變更。好了,讓我們開始吧!

隨時準備好在所有桌面平台上投入生產

Linux 和 macOS 已達到穩定狀態,並包含以下功能:

階層式選單和對 macOS 系統選單列的支援

您現在可以使用 PlatformMenuBar Widget 在 macOS 上建立平台渲染的選單列,它支援插入僅平台選單,以及控制 macOS 應用程式選單中顯示的內容。

A user clicks a menu which shows submenus on Mac
階層式選單示範

在所有桌面平台上完全支援國際文字輸入

國際文字輸入,包括對使用文字輸入法編輯器 (IME)(例如中文、日語和韓語)的語言的支援,在所有三個桌面平台上都得到完全支援,包括 Sogou 和 Google 日語輸入等第三方輸入法。

所有桌面平台上的無障礙功能

Windows、macOS 和 Linux 的 Flutter 支援無障礙服務,例如螢幕閱讀器、無障礙導航和反色。

macOS 上預設為通用二進制檔案

從 Flutter 3 開始,Flutter macOS 桌面應用程式被建構為通用二進制檔案,原生支援現有的基於 Intel 的 Mac 和 Apple 最新推出的 Apple Silicon 設備。

棄用 Windows 7/8 開發

在此版本中,我們將推薦的 Windows 開發版本提升到 Windows 10。雖然我們沒有阻止在舊版本(Windows 7、Windows 8、Windows 8.1)上進行開發,但這些版本 不再受 Microsoft 支援,而且我們對這些版本的測試有限。雖然我們將繼續為舊版本提供「盡力而為」的支援,但我們鼓勵您升級。

**注意:**我們繼續提供對在 Windows 7 和 Windows 8 上 *運行* 的 Flutter 應用程式的支援;此更改僅影響推薦的開發環境。

行動更新

我們對行動平台的更新包括以下內容:

可折疊手機支援

Flutter 3 版本支援可折疊行動設備。在由 Microsoft 帶頭合作的過程中,新功能和 Widget 讓您可以在可折疊設備上建立動態且令人愉悅的體驗。

作為此工作的一部分,MediaQuery 現在包含一個 DisplayFeatures 清單,描述設備元素(如鉸鏈、摺疊和切口)的邊界和狀態。此外,DisplayFeatureSubScreen Widget 現在會將其子 Widget 定位在不與 DisplayFeatures 的邊界重疊的位置,並且已經與框架的預設對話方塊和彈出視窗整合在一起,使 Flutter 開箱即用 地感知和對這些元素做出響應。

非常感謝 Microsoft 團隊,尤其是 @andreidiaconu,他們的貢獻!

嘗試 Surface Duo 模擬器範例,其中包括一個帶有 Flutter 圖庫的特殊分支,以查看 Flutter 的雙顯示器在實際情況下的運行情況。

iOS 可變更新率支援

Flutter 現在支援具有 ProMotion 顯示器的 iOS 設備(包括 iPhone 13 Pro 和 iPad Pro)上的可變更新率。在這些設備上,Flutter 應用程式可以以高達 120 赫茲的更新率渲染,而之前限制為 60 赫茲。這在快速動畫(例如捲軸)期間產生更流暢的體驗。查看 flutter.dev/go/variable-refresh-rate 以獲取更多詳細資訊。

簡化的 iOS 發佈

我們已在 flutter build ipa 命令 中加入了新選項,以簡化 iOS 應用程式的發佈。當您準備好發佈到 TestFlight 或 App Store 時,運行 flutter build ipa 以建立 Xcode 檔案 (.xcarchive 檔案) 和應用程式捆綁 (.ipa 檔案)。您可以選擇性地加入 —-export-method ad-hoc、—-export-method development 或 —-export-method enterprise。建立應用程式捆綁後,透過 Apple Transport macOS 應用程式 或在命令列上使用 xcrun altool 將其上傳到 Apple(運行 man altool 以獲取 App Store Connect API 金鑰驗證指示)。上傳後,您的應用程式即可發佈到 TestFlight 或 App Store。在設定初始 Xcode 專案設定(如顯示名稱和應用程式圖標)後,您不再需要打開 Xcode 來發佈應用程式。

Gradle 版本更新

如果您使用 Flutter 工具建立新的專案,您可能會注意到,產生的檔案現在使用 Gradle 和 Android Gradle Plugin 的最新版本。對於現有的專案,您需要手動將 Gradle 的版本提升到 7.4,將 Android Gradle Plugin 的版本提升到 7.1.2。

停止支援 32 位 iOS/iOS 9/iOS 10

正如我們在 2022 年 2 月的 2.10 穩定版本中所宣布的那樣,Flutter 對 32 位 iOS 設備和 iOS 版本 9 和 10 的支援即將結束。此更改影響 iPhone 4S、iPhone 5、iPhone 5C,以及第 2 代、第 3 代和第 4 代 iPad 設備。Flutter 3 是最後一個支援這些 iOS 版本和設備的穩定版本。

若要進一步了解此更改,請查看 RFC:停止支援 32 位 iOS 設備

Web 更新

我們對 Web 應用程式的更新包括以下內容:

圖片解碼

Flutter 網頁現在會自動檢測並在支援它的瀏覽器中使用 ImageDecoder API。截至今天,大多數基於 Chromium 的瀏覽器(Chrome、Edge、Opera、Samsung Browser 等等)都加入了這個 API。

新的 API 使用瀏覽器的內建圖片編解碼器,在主執行緒之外異步地解碼圖片。這將圖片解碼速度提高了 2 倍,而且它永遠不會阻塞主執行緒,消除了之前由圖片引起的任何卡頓。

Web 應用程式生命週期

Flutter Web 應用程式的新生命週期 API 讓您能夠從託管的 HTML 頁面控制 Flutter 應用的引導過程,並幫助 Lighthouse 分析應用的效能。這適用於許多用例,包括以下常用的場景:

  • 啟動畫面。
  • 載入指示器。
  • 在 Flutter 應用之前顯示的純 HTML 互動式登陸頁面。

如需更多資訊,請查看 docs.flutter.dev 上的 自訂 Web 應用程式初始化

工具更新

我們對 Flutter 和 Dart 工具的更新包括:

更新的 lint 套件

lint 套件的 2.0 版本已發佈:

使用 flutter create 在 Flutter 3 中生成的應用程式會自動啟用 v2.0 的 lint 集合。鼓勵現有的應用程式、套件和 Plugin 透過運行 flutter pub upgrade –major-versions flutter_lints 遷移到 v2.0,以遵循 Flutter 世界中的最新最佳實務。

v2 中新增的大多數 lint 警告都帶有自動修復。因此,在將應用程式 pubspec.yaml 檔案中的套件升級到最新版本之後,您可以在程式碼庫上運行 dart fix --apply 以自動修復大多數 lint 警告(一些警告仍然需要手動操作)。尚未使用 package:flutter_lints 的應用程式、套件或 Plugin 可以透過遵循 遷移指南 來遷移。

效能提升

感謝開源貢獻者 @knopp,部分重新繪製已在支援它的 Android 設備上 啟用。在我們的本地測試中,此更改將 Pixel 4 XL 設備上 backdrop_filter_perf 基準測試的平均、第 90 個百分位數和第 99 個百分位數畫面柵格化時間減少了 5 倍。當存在單個矩形髒區域時,部分重新繪製現在已在 iOS 和更新的 Android 設備上啟用。

我們已 進一步改進 了簡單情況下不透明度動畫的效能。特別是,當 Opacity Widget 僅包含單個渲染原語時,Opacity 通常會調用的 saveLayer 方法被省略。在一項用於衡量此優化效益的基準測試中,這種情況下的柵格化時間 提高了一個數量級。在未來的版本中,我們計劃將此優化應用於更多場景。

感謝開源貢獻者 @JsouLiang,引擎的柵格化和 UI 執行緒現在在 Android 和 iOS 上比其他執行緒(例如 Dart VM 後台垃圾回收執行緒)擁有更高的優先級。在我們的基準測試中,這導致 平均畫面構建時間快了約 20%

在 3 版本之前,柵格快取的准入政策只查看圖片中的繪製操作數量,假設任何超過幾個操作的圖片都是快取的良好候選者。不幸的是,這導致引擎浪費記憶體來快取實際上渲染速度非常快的圖片。此版本 引入了一種機制,它根據圖片中包含的繪製操作的成本來估計圖片的渲染複雜度。使用此作為柵格快取准入政策,減少了記憶體使用量,而在我們的基準測試中沒有降低效能。

感謝開源貢獻者 @ColdPaleLight,他 修復了畫面排程中的錯誤,該錯誤導致 iOS 上有少量動畫畫面被丟棄。感謝每一位報告此問題並提供重現步驟和丟棄畫面的影片的人。

Impeller

團隊一直在努力尋找解決 iOS 和其他平台上早期卡頓的方法。在 Flutter 3 版本中,您可以在 iOS 上預覽一個名為 Impeller 的實驗性渲染後端。Impeller 在引擎構建時預編譯了 更小、更簡單的著色器集合,以便它們不會在應用程式運行時編譯;這是 Flutter 中卡頓的主要來源。Impeller 尚未準備好投入生產,而且還遠未完成。目前還未實作所有 Flutter 功能,但我們對其在 flutter/gallery 應用程式中的保真度和效能感到滿意,因此我們分享了我們的進展。特別是,圖庫應用程式轉場動畫中最差的畫面 快了約 20 倍

Impeller 在 iOS 上可以使用標記。您可以將 —-enable-impeller 傳遞給 flutter run,或者將 Info.plist 檔案中的 FLTEnableImpeller 標記設定為 true,以嘗試使用 Impeller。Impeller 的開發工作仍在 Flutter 的 master channel 上繼續進行,我們希望在未來的版本中提供更多更新。

Android 上的內嵌廣告

當您使用 google_mobile_ads 套件 時,您應該會在使用者關鍵互動(例如捲軸和頁面之間的轉場)中看到更好的效能。這在新興市場中流行的設備上尤其明顯。最棒的是,不需要進行任何程式碼更改!

在幕後,Flutter 現在會異步地組合 Android 視圖,通常稱為 平台視圖。這意味著 Flutter 柵格化執行緒不需要等待 Android 視圖渲染。相反,Flutter 引擎會使用它管理的 OpenGL 紋理將視圖放置在螢幕上。

更多令人興奮的更新

Flutter 生態系統的其他更新包括以下內容:

Material 3

Flutter 3 支援 Material Design 3,這是 Material Design 的下一代版本。Flutter 3 為 Material 3 提供了選擇性支援;這包括 Material You 功能,如動態色彩、更新的色彩系統和字體排版、對許多組件的更新,以及 Android 12 中引入的新的視覺效果,如新的觸控漣漪設計和拉伸過捲效果。在 新的 將您的 Flutter 應用程式從無聊變成漂亮 程式碼實驗室中嘗試 Material 3 功能。查看 API 文件 以了解如何選擇性使用這些新功能以及哪些組件支援 Material 3。透過 Material 3 總體議題 關注正在進行的工作。

主題擴展

Flutter 現在可以使用名為 主題擴展 的概念將 任何 內容加入到 material 函式庫的 ThemeData 中。您可以指定 ThemeData.extensions,而不是(在 Dart 中的意義上)擴展 ThemeData 並重新實作其 copyWith、lerp 和其他方法。此外,作為套件開發人員,您可以提供 ThemeExtension。查看 flutter.dev/go/theme-extensions 以獲取更多詳細資訊,並查看 GitHub 上的這個範例

廣告

我們知道,發佈者向使用者要求個人化廣告的同意並處理 Apple 的 App 追蹤透明度 (ATT) 要求非常重要。

為了支援這些要求,Google 提供了使用者訊息平台 (UMP) SDK,它取代了之前的開源 同意 SDK。在即將發佈的 Flutter 版 GMA SDK 中,我們將加入對 UMP SDK 的支援,以允許發佈者獲得使用者同意。如需更多詳細資訊,請查看 pub.dev 上的 google_mobile_ads 頁面。

重大變更

隨著 Flutter 的不斷成長和改進,我們旨在將重大變更的數量降至最低。在 Flutter 3 發佈時,我們有以下重大變更:

如果您正在使用任何這些 API,請參閱 Flutter.dev 上的遷移指南

總結

來自 Google 的 Flutter 團隊向您表示感謝,感謝社群做出的出色工作,幫助 Flutter 保持其作為最受歡迎的跨平台 UI 工具包的地位,正如 Statista 和 SlashData 等分析師所衡量的那樣。我們期待以社群為驅動的工具一起努力,繼續為開發人員和使用者提供令人愉悅的體驗!


Flutter 3 的新功能 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章內容使用 Gemini 1.5 Pro 自動翻譯產生】

Dart 2.17:提升生產力和整合能力

今天在 Google I/O 上,我們宣布推出新的 Dart SDK,版本 2.17。此版本基於我們提升生產力和平台可攜性的核心主題而構建。它提供了新的語言特性:支援成員的列舉、改進了向父類別轉發參數的功能,以及更靈活的命名參數。我們改進了工具,推出了 package:lints 的一個新的主要版本——我們的工具支援根據我們的最佳實務檢查 Dart 程式碼——並廣泛更新了核心函式庫 API 文件,其中包含豐富的程式碼範例。為了改進平台整合,我們提供了在 Flutter 外掛中使用 dart:ffi(原生 C 互通性)的新範本、對 RISC-V 處理器的實驗性支援,以及對簽署 macOS 和 Windows 可執行檔的支援。

Dart 2.17 reease banner

新的語言特性以提高生產力

我們不斷改進 Dart 語言,以提高您的生產力——透過新增新特性和改進現有特性。Dart 2.17 新增了對列舉成員的主要新支援,改進了您在建構函式中使用命名參數的方式,並使將參數轉發給父類別的程式碼更加簡潔,減少了重複。

增強的列舉支援成員

列舉非常適合表示一組離散的狀態。例如,我們可以將水建模為列舉 Water { frozen, lukewarm, boiling }。但是,如果我們想要在列舉上新增一些方法——例如,將每個狀態轉換為溫度,並支援將列舉轉換為字串,該怎麼辦?我們或許可以使用擴充方法來新增 waterToTemp() 方法,但我們必須小心地使其與列舉保持同步。對於字串轉換,我們更希望覆寫 toString(),但這一直不受支援。

在 Dart 2.17 中,我們現在全面支援列舉成員。這意味著我們可以新增儲存狀態的欄位、設定該狀態的建構函式、具有功能的方法,甚至覆寫現有的成員。你們中的許多人一直在要求此功能;這是我們在語言追蹤器中 投票數第三高的議題

對於我們的 water 範例,我們可以新增一個儲存溫度的 int 欄位和一個接受 int 的預設建構函式:

1
2
3
4
5
enum Water {

final int tempInFahrenheit;
const Water(this.tempInFahrenheit);
}

為了確保在建立列舉時調用建構函式,我們需要為每個列舉值調用它:

1
2
3
4
5
6
enum Water {
frozen(32),
lukewarm(100),
boiling(212);

}

為了支援轉換為字串,我們只需覆寫從 Object 繼承的 toString()

1
2
@override
String toString() => "The $name water is $tempInFahrenheit F.";

這樣,您就有一個完整的列舉,可以輕鬆地實例化,並且可以在其上調用方法:

1
2
3
void main() {
print(Water.frozen); // 輸出 “The frozen water is 32 F.”
}

這兩種方法的完整範例如下圖所示;我們發現新的 Dart 2.17 版本更易於閱讀和維護。

父類別初始化器

當您有一個類別繼承階層時,一種常見的模式是將一些建構函式參數傳遞給父類別的建構函式。為此,子類別需要 1) 在其自身的建構函式中列出每個參數,以及 2) 使用這些參數調用父類別建構函式。這會導致程式碼樣板化:大量重複,使程式碼更難閱讀,維護起來也更麻煩。

一些 Dart 社群成員幫助實現了這一點。GitHub 使用者 @roy-sianez 大約半年前提交了一個關於此的 語言議題;建議使用類似於 GitHub 使用者 @apps-transround 先前建議 的內容:我們或許可以透過引入一個新的結構來表示參數是在父類別中指定的來解決這個問題。我們認為這是一個很棒的主意,所以這已經新增到 Dart 2.17 中。從下面的範例中可以看出,這與 Flutter widget 程式碼特別相關。事實上,當我們將新特性應用於 Flutter 架構時,我們發現程式碼總共減少了 近兩千行

任何地方的命名參數

最後,我們改進了當您調用方法時命名參數的工作方式。以前,這些參數必須出現在方法的參數列表的最後。如果您希望將位置參數放在最後以使程式碼更具可讀性,這會很煩人。例如,請參閱以下對 List<T>.generate 建構函式的調用——以前,growable 參數必須放在最後,這使得它很容易在包含產生器本身的大型位置參數下方被遺漏。現在您可以按照自己的喜好排序它們,讓您可以將小型命名參數放在最前面,將產生器放在最後。

欲了解更多關於這三個特性的實際應用範例,請參閱我們更新的 列舉父類別初始化器命名參數 的範例。

生產力工具

繼續生產力主題,我們在核心工具中進行了多項改進。

在 Dart 2.14 中,我們引入了 package:lints,它與 Dart 分析器一起工作,幫助您編寫 Dart 程式碼,以防止錯誤,並使用規範的風格,從而更有效地進行程式碼審查。從那時起,分析器中提供了許多新的 lint,我們仔細地對其進行了分類,並從中為所有 Dart 程式碼選擇了 十個新的 lint,並專門為 Flutter 程式碼選擇了 兩個新的 lint。這些 lint 包括確保您的導入包含在您的 pubspec 檔案中,防止在類型參數上誤用 null 檢查,以及確保子屬性具有一致的風格。您可以使用一個簡單的命令升級到新的 lint:

  • 對於 Dart 套件:
    dart pub upgrade --major-versions lints
  • 對於 Flutter 套件:
    flutter pub upgrade --major-versions flutter_lints

SecureSockets 通常用於啟用透過 TLS 和 SSL 保護的 TCP sockets。在 Dart 2.17 之前,在開發過程中除錯這些 sockets 非常棘手,因為沒有辦法檢查安全資料流量。我們現在新增了對指定 keyLog 檔案的支援。指定後,當與伺服器交換新的 TLS 金鑰時,NSS 金鑰日誌格式 的一行文字會附加到檔案中。這使得網路流量分析器工具(例如 Wireshark)能夠解密透過 socket 傳送的內容。有關詳細資訊,請參閱 SecureSocket.connect() 的 API 文件。

由 dart doc 工具產生的 API 文件是大多數 Dart 開發人員學習新 API 的關鍵資產。雖然我們的 核心函式庫 API 長期以來都有豐富的文字描述,但許多開發人員告訴我們,他們更喜歡透過閱讀使用 API 的範例程式碼來學習 API。在 Dart 2.17 中,我們徹底修改了所有主要的程式碼函式庫,在瀏覽量最高的 200 個頁面中新增了範例程式碼,因此它們現在都有全面的範例程式碼。例如,比較 Dart 2.16 中 dart:convert 的文件 與 Dart 2.17 中更新的 頁面;希望這能使文件更易於使用。

生產力的提高不僅僅來自於我們為平台新增新特性,還來自於我們清理堆疊並移除不再使用的特性。這有助於保持我們平台的精簡,這對於新開發人員尤其重要。為此,我們從 dart:io 函式庫中移除了 231 行棄用的程式碼——如果您仍在使用這些棄用的 API,您可以使用 dart fix 升級到它們的替代品。我們也繼續努力移除 棄用的 Dart 命令列工具,這次移除了 dartdoc 工具(改用 dart doc)和 pub 工具(改用 dart pub 或 flutter pub)。

擴大我們的平台整合和支援

第二個核心主題是平台整合和支援。Dart 是一種真正的跨平台語言。雖然我們已經支援 各種平台,但我們仍在不斷改進,以確保您可以與每個支援的平台深度整合,並支援新興平台。

Dart FFI——我們與 C/原生程式碼互通的核心機制——是一種將 Dart 程式碼與現有原生平台程式碼整合的常用方法。在 Flutter 上,這可能是構建使用主機平台原生 API(例如 Windows win32 API)的 外掛 的好方法。在 Dart 2.17 和 Flutter 3 中,我們在 flutter 工具中新增了範本,因此您現在可以輕鬆建立 FFI 外掛,這些外掛具有由 dart:ffi 調用到原生程式碼的 Dart API。有關詳細資訊,請參閱 flutter.dev 上更新的 開發套件和外掛 頁面。

為了能夠在具有特定於其 ABI(應用程式二進位介面)的類型的平台上使用 FFI,FFI 現在支援特定於 ABI 的類型。例如,您現在可以使用 Long(C 語言中的 long)來正確表示具有特定於 ABI 大小的長整數,根據 CPU 架構,它可能是 32 位或 64 位。有關支援類型的完整列表,請參閱 AbiSpecificInteger API 頁面中的「實作者」列表。

當使用 Dart FFI 與原生平台深度整合時,您有時需要調整由 Dart 和原生程式碼分配的記憶體或其他資源(埠、檔案等)的清理。從歷史上看,這一直非常棘手,因為 Dart 是一種垃圾回收語言,會自動處理清理工作。Dart 2.17 透過引入 Finalizer 的概念來解決這個問題,Finalizer 包括一個 Finalizable 標記介面,用於「標記」不應過早完成或丟棄的物件,以及一個 NativeFinalizer 類別,可以附加到 Dart 物件上,以便在物件即將被垃圾回收時提供一個回調執行。這些一起允許在原生程式碼和 Dart 程式碼中執行清理程式碼。有關詳細資訊,請參閱 NativeFinalizer 的 API 文件中的描述和範例,或 WeakReferencesFinalizer 的文件,以了解在常規 Dart 程式碼中的類似支援。

我們對將 Dart 編譯為原生程式碼的支援是使 Flutter 應用程式具有出色的啟動效能和快速渲染的核心推動因素。第二個用例是使用 dart compile 將 Dart 編譯為可執行檔的能力。這些可執行檔可以在任何機器上獨立執行,而無需安裝 Dart SDK。Dart 2.17 中的另一個新功能是支援 簽署可執行檔,這使得在 Windows 和 macOS 上進行部署成為可能,因為在這些平台上通常需要簽署。

我們也繼續透過保持在新興平台的最前沿來擴大我們支援的平台集。RISC-V 是一種新的創新處理器指令集。RISC-V International 是一個全球非營利組織,擁有 RISC-V 規範,使指令集免費且開源。這仍然是一個新興平台,但我們對其潛力感到興奮,因此我們的 2.17.0-266.1.beta Linux 版本(或我們 beta 頻道 的更高版本)包含對它的實驗性支援。我們很樂意聽到您的回饋,所以請 提交議題發布 關於您的體驗!

開始使用 Dart 2.17!

我們希望今天的 Dart 2.17 版本能讓您興奮,提高您的生產力,並為您的應用程式實現更多的平台整合。要開始使用,您可以直接下載 Dart 2.17 版本,或者將其作為今天 Flutter 3 SDK 版本的一部分嵌入。

我們也邀請您查看我們為 Google I/O 提供的 新內容


Dart 2.17:生產力和整合 最初發佈在 Dart 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章內容使用 Gemini 1.5 Pro 自動產生】

將 Flutter 遊戲開發提升到新的境界

Flutter’s Dash, Android Jetpack, Chrome Dino, and Firebase’s Sparky gathering around a pinball machine.

為了 Google I/O,我們與 Flutter 團隊合作,重新構想了一款經典彈珠台遊戲,該遊戲使用 Flutter 和 Firebase 構建。以下是如何在 Flame 遊戲引擎的幫助下將 [I/O 彈珠台](https://pinball.flutter.dev/) 在網頁上呈現。

遊戲開發基礎

對於構建由使用者互動驅動的遊戲(例如益智遊戲和文字遊戲)來說,Flutter 架構是一個很好的選擇。當談到使用遊戲迴圈的遊戲時,[Flame](https://docs.flame-engine.org/)(一個建立在 Flutter 之上的二維遊戲引擎)可以成為一個有用的工具。I/O 彈珠台使用 Flame 的開箱即用功能,例如動畫、物理、碰撞偵測等等,同時也利用 Flutter 架構的基礎設施。如果您可以使用 Flutter 構建應用程式,那麼您已經擁有了使用 Flame 構建遊戲所需的基礎。

Flame engine logo

遊戲迴圈

在傳統應用程式中,螢幕通常在使用者發生事件或互動之前都是視覺上的靜態。對於遊戲來說,則相反 - UI 是持續渲染的,遊戲的狀態也在不斷變化。Flame 提供了一個遊戲 Widget,它在內部管理遊戲迴圈,以便 UI 能夠以高效能的方式持續渲染。`Game` 類別包含遊戲元件和邏輯的實作,這些實作傳遞給 Widget 樹中的 `GameWidget`。在 I/O 彈珠台中,遊戲迴圈會對彈珠在遊戲場上的位置和狀態做出反應,如果彈珠與物體發生碰撞或掉出了遊戲,則會應用必要的特效。

@override
void update(double dt) {
super.update(dt);
  final direction = -parent.body.linearVelocity.normalized();
angle = math.atan2(direction.x, -direction.y);
size = (_textureSize / 45) *
parent.body.fixtures.first.shape.radius;
}

使用二維元件渲染三維空間

構建 I/O 彈珠台的挑戰之一是弄清楚如何僅使用二維元素來建立三維效果。元件的順序決定了它們在螢幕上的渲染方式。例如,當彈珠向上發射到斜坡時,彈珠的順序會上升,因此它看起來像是位於斜坡的頂部。

I/O Pinball playfield featuring Flutter’s Dash, Android Jetpack, Chrome’s Dino, and Firebase’s Sparky, and other Google-themed elements. Toward the bottom of the board there are two flippers with two bumpers above and to the bottom right is the ball ready to be launched.

彈珠、彈射器、兩個彈射器和 Chrome 恐龍都是具有動態主體的元素,這些元素會受到世界物理的影響。彈珠的大小也會根據它在遊戲場上的位置而變化。當彈珠移動到遊戲場的頂部時,它的尺寸會縮小,從使用者的角度來看,它看起來離使用者更遠。此外,彈珠上的重力會根據彈珠台的角度進行調整,因此彈珠在斜坡上會下降得更快。

/// 根據彈珠在遊戲場上的位置縮放彈珠的主體和 Sprite。
class BallScalingBehavior extends Component with ParentIsA<Ball> {
@override
void update(double dt) {
super.update(dt);
final boardHeight = BoardDimensions.bounds.height;
const maxShrinkValue = BoardDimensions.perspectiveShrinkFactor;
    final standardizedYPosition = parent.body.position.y +   (boardHeight / 2);
final scaleFactor = maxShrinkValue +
((standardizedYPosition / boardHeight) * (1 - maxShrinkValue));
parent.body.fixtures.first.shape.radius = (Ball.size.x / 2) * scaleFactor;
final ballSprite = parent.descendants().whereType<SpriteComponent>();
if (ballSprite.isNotEmpty) {
ballSprite.single.scale.setValues(
scaleFactor,
scaleFactor,
);
}
}
}

使用 Forge 2D 實現物理

I/O 彈珠台很大程度上依賴於由 Flame 團隊維護的 [forge2d](https://pub.dev/packages/forge2d) 套件。此套件將開源 [Box2D 物理引擎](https://box2d.org/) 移植到 Dart 中,以便可以輕鬆地與 Flutter 整合。我們使用 forge2d 來為遊戲的物理提供動力,例如遊戲場上物體(Fixture)之間的碰撞偵測。

forge2D 允許我們監聽 Fixture 之間的碰撞事件。然後,我們為 Fixture 添加 `ContactCallbacks`,以便在兩個元素之間發生接觸時收到通知。例如,當彈珠(具有具有 `CircleShape` 的 Fixture)與彈珠台(具有具有 `EllipseShape` 的 Fixture)發生接觸時,得分會增加。在這些回呼函式中,我們可以精確設定接觸的開始和結束位置,因此當兩個元素與另一個元素發生接觸時,就會發生碰撞。

@override
Body createBody() {
final shape = CircleShape()..radius = size.x / 2;
final bodyDef = BodyDef(
position: initialPosition,
type: BodyType.dynamic,
userData: this,
);
  return world.createBody(bodyDef)
..createFixtureFromShape(shape, 1);
}

Sprite 表格動畫

彈珠台遊戲場上有一些元素,例如 Android、Dash、Sparky 和 Chrome 恐龍,它們是動畫的。對於這些元素,我們使用了 Sprite 表格,這些表格包含在 Flame 引擎中,並帶有 `SpriteAnimationComponent`。對於每個元素,我們都有一個檔案,其中包含圖像的不同方向、檔案中的幀數以及幀之間的時間。使用這些資料,Flame 中的 `SpriteAnimationComponent` 會將所有圖像在迴圈中編譯在一起,使元素看起來像動畫。

Sprite sheet showing the Android in various orientations so that if played on a loop, it will appear to be spinning in a circle.
Sprite sheet 範例
final spriteSheet = gameRef.images.fromCache(
Assets.images.android.spaceship.animatronic.keyName,
);
const amountPerRow = 18;
const amountPerColumn = 4;
final textureSize = Vector2(
spriteSheet.width / amountPerRow,
spriteSheet.height / amountPerColumn,
);
size = textureSize / 10;
animation = SpriteAnimation.fromFrameData(
spriteSheet,
SpriteAnimationData.sequenced(
amount: amountPerRow * amountPerColumn,
amountPerRow: amountPerRow,
stepTime: 1 / 24,
textureSize: textureSize,
),
);

更加深入地了解 I/O 彈珠台程式碼庫

Firebase 實時結果排行榜

I/O 彈珠台排行榜實時顯示世界各地玩家的最高分。使用者還可以將他們的得分分享到 Twitter 和 Facebook。我們使用 Firebase [Cloud Firestore](https://firebase.google.com/docs/firestore) 來追蹤前十名得分,並將其擷取以顯示在排行榜上。當新的得分寫入排行榜時,[Cloud Function](https://firebase.google.com/docs/functions) 會將得分按降序排序,並移除任何不在前十名的得分。

Leaderboard for I/O pinball with 10 top scores displayed.
/// 擷取前 10 個 [LeaderboardEntryData]。
Future<List<LeaderboardEntryData>> fetchTop10Leaderboard() async {
try {
final querySnapshot = await _firebaseFirestore
.collection(_leaderboardCollectionName)
.orderBy(_scoreFieldName, descending: true)
.limit(_leaderboardLimit)
.get();
final documents = querySnapshot.docs;
return documents.toLeaderboard();
} on LeaderboardDeserializationException {
rethrow;
} on Exception catch (error, stackTrace) {
throw FetchTop10LeaderboardException(error, stackTrace);
}
}

為網頁構建

與傳統應用程式相比,構建響應式的遊戲可能會更容易。彈珠台遊戲場只需要縮放到設備的大小即可。對於 I/O 彈珠台,我們根據設備的大小以固定比例進行縮放。這確保了無論顯示大小如何,座標系始終保持一致,這對於確保元件在設備之間一致地顯示和互動非常重要。

I/O 彈珠台還可以適應行動或桌面瀏覽器。在行動瀏覽器中,使用者可以點擊發射按鈕開始遊戲,也可以點擊螢幕的左右兩側來控制對應的彈射器。在桌面瀏覽器中,使用者可以使用鍵盤來發射彈珠和控制彈射器。

程式碼庫架構

彈珠台程式碼庫遵循分層架構,每個功能都在其自己的資料夾中。此專案中,遊戲邏輯也與視覺元件分開。這可以確保我們能夠輕鬆地獨立更新視覺元素,而與遊戲邏輯無關,反之亦然。

彈珠台的主題會根據使用者在開始遊戲之前選擇的角色而有所不同。主題由 `CharacterThemeCubit` 類別控制。根據角色選擇,彈珠顏色、背景和其他元素會更新。

Displays the different I/O Pinball themes. The top left shows Sparky, carpet with prominent flame decorations and neon orange lighting. The top right shows Dash, a carpet with prominent egg decorations and neon blue lighting. The bottom left shows Android, carpet with prominent Android Jetpack decorations and neon green lighting. The bottom right shows Chrome Dino, carpet with prominent cactus decorations, and neon white lighting.
/// {@template character_theme}
/// 用於建立角色主題的基類。
///
/// 角色特定的遊戲元件應在此處指定一個 getter,以
/// 載入其對應的遊戲資產。
/// {@endtemplate}
abstract class CharacterTheme extends Equatable {
/// {@macro character_theme}
const CharacterTheme();
/// 角色名稱。
String get name;
/// 彈珠的資產。
AssetGenImage get ball;
/// 背景的資產。
AssetGenImage get background;
/// 圖示資產。
AssetGenImage get icon;
/// 排行榜的圖示資產。
AssetGenImage get leaderboardIcon;
/// 閒置角色動畫的資產。
AssetGenImage get animation;
@override
List<Object> get props => [
name,
ball,
background,
icon,
leaderboardIcon,
animation,
];
}

I/O 彈珠台的遊戲狀態由 [flame_bloc](https://pub.dev/packages/flame_bloc) 處理,flame_bloc 是一個將 bloc 與 Flame 元件連接起來的套件。例如,我們使用 flame_bloc 來追蹤剩餘的遊戲回合數、透過遊戲獲得的任何獎金以及目前的遊戲得分。此外,Widget 樹的頂部還有一個 Widget,其中包含載入頁面的邏輯,包括如何玩遊戲的說明。我們還遵循 [行為模式](https://en.wikipedia.org/wiki/Behavioral_pattern) 來封裝和隔離遊戲功能的某些元素,這些元素基於其元件。例如,彈珠台在被彈珠擊中時會發出聲音,因此我們實作了 `BumperNoiseBehavior` 類別來處理此問題。

class BumperNoiseBehavior extends ContactBehavior {
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
readProvider<PinballPlayer>().play(PinballAudio.bumper);
}
}

程式碼庫還包含全面的單元、Widget 和黃金測試。有時,由於單個元件可能具有多個責任,這使得它們難以隔離測試,因此測試遊戲會帶來一些挑戰。結果,我們最終定義了一些模式來更好地隔離和測試元件。我們還將改進整合到 [flame_test](https://pub.dev/packages/flame_test) 套件中。

元件沙箱

此專案很大程度上依賴於 Flame 元件,以將彈珠台體驗呈現出來。程式碼庫附帶了一個元件沙箱,它類似於 [UI 元件庫](https://gallery.flutter.dev/#/)。這是在開發遊戲時的一個有用的工具,因為它允許您隔離開發遊戲元件,並確保它們在整合到遊戲中之前,看起來和行為符合預期。

Chrome Dino is animated, moving left to right and opening its mouth to shoot out the pinball ball.

接下來要做什麼

看看您是否可以在 [I/O 彈珠台](https://pinball.flutter.dev/) 中獲得高分!程式碼在 [這個 GitHub 儲存庫](https://github.com/flutter/pinball) 中是開源的。請關注排行榜,並在社交媒體上分享您的得分!


I/O 彈珠台由 Flutter 和 Firebase 提供支援 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章內容使用 Gemini 1.5 Pro 自動翻譯產生】

一步修復多個分析問題

快速修復分析問題中,我們探討了在編輯時自動修復個別診斷的支持。有時即使這樣也太慢了,您真的希望一次修復多個診斷。幸運的是,也有支持這樣做的,我們將在本文中介紹。

修復多個診斷的方法有幾種,取決於您要修復多少診斷,我們將分別研究每一種方法。

修復同一個診斷的多個出現位置

您是否曾經透過將類別移動到不同的檔案來重構程式碼?如果是這樣,很可能某些 import 僅僅是因為移動的類別中的引用才需要的,這會導致您留下未使用的 import:

Visual Studio Code
IntelliJ IDEA

對於任何可以在同一個檔案中多次應用的修復,工具都提供了一個選項,可以在檔案中的任何位置應用它。

這些修復會顯示在與其他修復相同的位置,並且標籤通常與單個位置修復相似,但會添加一些短語,例如「在檔案中」。例如,以下是此修復在 IDE 中的顯示方式:

Visual Studio Code
IntelliJ IDEA

選擇「在檔案中」修復會完全按照您的預期執行 - 它會移除檔案中所有未使用的 import:

Visual Studio Code
IntelliJ IDEA

修復檔案中的所有診斷

這很棒,但它只修復一種診斷,並且需要您找到報告診斷的位置(在編輯器中或在問題視圖中)才能調用修復。看起來工具還可以做得更多。

在 Visual Studio Code 中,它可以。您可以將修復應用於單個檔案中的所有診斷。為了了解如何操作,我們將使用與上面相同的示例:

如果您按一下滑鼠右鍵打開上下文選單,您將看到以下內容:

如果您選擇**原始碼動作…**,它會打開第二個選單:

選擇全部修復會將修復應用於編輯器中的所有診斷,產生以下結果:

您也可以從命令面板調用全部修復命令,您可以透過從檢視選單中選擇**命令面板…**或使用鍵盤快捷鍵(任何平台上的 F1,Windows 和 Linux 上的 Control+Shift+P 或 macOS 上的 Command+Shift+P)來打開命令面板。您可以透過輸入命令的名稱來篩選列表。命令面板看起來像這樣:

修復目錄中的所有診斷

我們用於修復診斷的最完整的工具是命令列工具 dart fix。它可以修復給定目錄中的所有診斷,這意味著您可以使用它來修復套件或應用程式中的所有內容。

當然,有一些注意事項。它並不能修復每個診斷,因為它應用與 IDE 中可用的快速修復相同的快速修復,並且並非每個診斷都有快速修復。當有多個同樣有效的方法來修復問題時,它也不會應用快速修復診斷,因為它不知道您更喜歡應用哪個修復。這些注意事項也適用於上一節中描述的全部修復命令。

雖然不需要經常使用此工具(這也是您可能不知道它的原因之一),但在某些情況下它特別有用。

首先,考慮您想要啟用新的 lint,甚至是 lints 集的情況,例如核心建議flutter規則集之一。您的套件中很可能至少有一些違反 lint 規則的情況。如果存在與 lint 相關聯的修復,那麼一步應用該修復到所有位置會比單獨應用修復到每個違規位置更方便。

其次,考慮您從一個 Flutter 版本更新到另一個版本的情況。如果兩個版本之間有 API 變更,則很有可能存在修復以升級您的程式碼以符合最新版本。同樣,如果所有這些變更都在單個操作中應用,則會很方便。

這就是 dart fix 命令的用武之地。它可以在多個檔案中應用針對多個診斷的快速修復。

該命令有兩種模式。「空運行」模式會告訴您可以應用多少變更以及可以修復哪些診斷,但不會修改任何檔案。「應用」模式會應用變更。

您可以提供包含要修復的檔案的目錄的路徑,但如果您省略目錄,它會遞迴地修復目前目錄及其子目錄中的所有檔案。

假設您已啟用 prefer_single_quotes lint,並且您有一個包含以下內容的檔案:

1
2
3
4
5
6
7
8
import "package:logging/logging.dart";

void main() {
var logger = Logger("myApp");
logger.info("Entering myApp");
// Do stuff.
logger.info("Exiting myApp");
}

從包含此檔案的目錄中使用 --dry-run 選項運行 dart fix 命令會產生以下輸出:

1
2
3
4
5
6
7
$ dart fix --dry-run
Computing fixes in __bin__ (dry run)... 4.3s

3 proposed fixes in 1 file.

demo.dart
prefer_single_quotes • 3 fixes

該工具已找到三個違反 lint 的地方,並報告它能夠修復所有三個,但它不會修改檔案。

再次運行 dart fix 工具,但這次使用 --apply 選項,會產生幾乎相同的輸出:

1
2
3
4
5
6
7
8
9
10
$ dart fix --apply
Computing fixes in __bin__... 5.3s
Applying fixes... 0.0s

3 proposed fixes in 1 file.

demo.dart
prefer_single_quotes • 3 fixes

3 fixes made in 1 file.

但這次檔案會更新為包含以下內容:

1
2
3
4
5
6
7
8
import 'package:logging/logging.dart';

void main() {
var logger = Logger('myApp');
logger.info('Entering myApp');
// Do stuff.
logger.info('Exiting myApp');
}

由於空運行模式不會產生任何允許您查看所建議的實際變更的 diff 輸出,請考慮在使用 --apply 選項運行工具之前提交您的變更。這樣,更容易查看工具實際進行了哪些變更,並還原您不喜歡的任何變更。

如果您不想在運行工具之前提交,並且您正在使用 IntelliJ IDEA,則可以使用檔案->本地歷史記錄功能來比較應用修復前後的狀態。(如需更多資訊,請參閱將標籤添加到特定狀態的本地歷史記錄。)

dart fix 命令無法修復所有問題,但如果可以的話,它比手動應用修復到所有位置要容易得多。

總結

能夠在單個操作中應用多個修復不僅提高了快速修復的功能,還可能更容易進行更大規模的變更,例如啟用新的 lint 規則或升級到最新版本的 Flutter。

與往常一樣,如果您對如何改進這些工具有任何想法,請透過建立 dart-lang/sdk 問題告知我們。

【文章內容使用 Gemini 1.5 Pro 自動產生】

Flutter 是否提升了開發人員的生產力?

Flutter 團隊持續進行每季一次的使用者調查,以收集您,也就是 Flutter 開發人員的意見。2022 年的第一份調查於 2 月 22 日至 3 月 3 日進行,並透過 Flutter IDE 外掛、docs.flutter.dev 網站和 Twitter 進行訪問,超過 8,000 人參與了調查。

Flutter 仍然受到大多數開發人員的喜愛(93% 對 Flutter 感到滿意),甚至創下了有史以來「非常滿意」的最高百分比(60%)!這一點值得注意,因為自上次季度調查(2021 年 11 月至 2022 年 2 月)以來,Flutter 開發人員的數量增加了 10% 以上。我們很高兴看到,即使社群不斷增長,Flutter 仍然能够保持高水準的滿意度。

隨著時間推移,對 Flutter 的滿意度。

在此次調查中,我們重點關注以下四個主題:

  • IDE 外掛:我們可以做些什麼來改進 Flutter 的 IDE 外掛支援?
  • Dart 可擴展性:當 Flutter 專案規模越來越大時,Dart 工具是否會變慢或無回應?
  • 遊戲開發:您是否對遊戲開發感興趣? (更多資訊請參閱即將發布的部落格文章。)
  • Flutter 對開發人員的價值:Flutter 是否實現了對開發人員做出的承諾?

我們不會視您的支持為理所當然,我們會密切关注顯示我們可以進一步改進的指標。我們意识到,改进的空间总是存在的,在本文中,我们将告诉您更多关于我们所学到的知识以及这些信息如何推动我们的路线图。

IDE 外掛

根據此次調查,63.5% 的開發人員對 Flutter 的 IDE 外掛「非常滿意」。那麼其他開發人員呢?為什麼他們沒有完全滿意?

如以下圖所示,沒有任何單一項目能突出顯示為沒有對 IDE 外掛完全滿意的原因。對 Android Studio/IntelliJ 和 VS Code 用戶來說,缺少功能(35%)是最主要的原因,但外掛速度慢(29%)、記憶體消耗(28%)、外掛有 bug(26%)都收到了不少票數。(當受訪者點擊 IDE 中的調查連結時,會記錄 IDE 類型)。

沒有對 Flutter 的 IDE 外掛完全滿意的原因。

「IntelliJ」標籤也包含 Android Studio 用戶。

當我們詢問缺少或支援不完善的功能時,重構和快速修復被選為需要更好地支援的兩個最主要功能。這多少令人驚訝,因為我們沒有預料到重構會排名最高。此外,我們也驚訝地發現 Android Studio/IntelliJ 用戶非常關心導航,儘管我們理解 Android Studio/IntelliJ 用戶比 VS Code 用戶更關心 Android 整合。開發人員對 UI 建立器也有很大興趣。

IDE 中缺少或支援不足的功能。

總之,我們現在對 Flutter 開發人員的需求有了更清晰的理解。但是,我們將透過各種研究繼續了解改進關注領域所需的內容。

Dart 工具

Dart 工具(例如 Dart 分析器、編譯器、DevTools 和 IDE 外掛)使使用這種語言變得更加容易。我們從一些開發人員那裡得知,當 Flutter 專案規模很大時,Dart 工具可能會變慢甚至無回應。我們希望在下次調查中評估這個問題的普遍程度和嚴重程度。

從此次調查中我們發現,大約四分之一的開發人員遇到过這種情況(24.5%),其中近一半人表示,這對開發工作造成了極其負面的影響(16.7%)或中等負面的影響(29.6%)。

開發人員在 Flutter 專案規模越來越大時,遇到 Dart 工具變慢或無回應。
Dart 工具速度慢或無回應對生產力的影響。

我們預計這些數字會更低,我們渴望在這方面有所改進,因此我們計劃與一些對后续对话表示兴趣的開發人員聯繫,以重現問題,並最終提高工具的效能。

Flutter 對開發人員的價值

Flutter 承諾提供一個框架,可以從單個程式碼庫構建美麗、快速、跨平台的應用程式。在本次調查中,我們探討了 Flutter 開發人員是否認為我們實現了這些價值。

我們向那些將現有應用程式轉換為 Flutter 的開發人員和從頭開始創建新 Flutter 應用程式的開發人員提問了不同的問題。允許選擇多個答案,59.2% 的開發人員從頭開始創建了新應用程式,26.5% 的開發人員要么將現有應用程式轉換為 Flutter,要么將 Flutter 組件添加到現有應用程式中。

開發人員開發和發布 Flutter 應用程式的經驗。

對於那些使用 Flutter 創建新應用程式的開發人員,我們詢問他們在何種程度上同意關於與其他 Google 服務的整合、開發生產力和目標平台的三个陳述。我們發現,超過 90% 的開發人員同意「與我使用過的其他技術相比,Flutter 缩短了構建和發布新應用程式的時間」(92.1%),並且同意「Flutter 使我的應用程式能夠面向更多平台」。(92.2%)

使用 Flutter 創建新應用程式的影響。

類似的問題也詢問了那些將 Flutter 應用於現有應用程式的開發人員。90.7% 的開發人員同意「與之前相比,Flutter 使我的應用程式能夠更快速地開發」,85.3% 的開發人員同意「與之前相比,Flutter 使我的應用程式能夠在更多平台上提供」。我們還發現,84.8% 的開發人員同意「Flutter 使我的應用程式比以前更美觀」。

將 Flutter 應用於現有應用程式的影響。

當然,我們也聽到了 Flutter 可以改进的地方,例如為所有支援的平台提供更多外掛、添加更多文件、提高感知效能、更好地與其他工具整合等等。一些評論還提到,某些問題領域在多年的調查之後依然沒有改變。雖然我們努力解决這些問題並改進產品,但我們感謝您的耐心!不過,我們很高兴了解到,我们的開發人员正以更高效的方式面向更多平台。

總結

特別感謝所有參與我們調查的人!Flutter 團隊成員每季度都会花時間分析和消化反馈,并为下一个季度及以后做出决策。我們會繼續收集您的意見,以了解我們可以做些什麼來讓您使用 Flutter 的体验更加愉快和高效。

Flutter 的 UX 研究團隊也在進行各種使用者體驗研究。如果您有興趣參與,請註冊以參加未來研究。

請繼續參與並分享您的想法。我們下次調查再见!


Flutter 是否提升了開發人員的生產力? 最初發佈在 Medium 上的 Flutter,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章內容使用 Gemini 1.5 Pro 自動產生】

敲鑼打鼓… 宣布 Flutter 拼圖 Hack 大賽得獎者!

The Flutter puzzle hack logo

Flutter 拼圖 Hack 大賽 邀請參賽者發揮創意,重新詮釋經典的滑動拼圖。可以說,作品讓評審們驚豔不已!

超過 5,500 位來自 120 多個國家、各個經驗層次的開發人員參與了 Flutter 拼圖 Hack 大賽。在公布各個類別的獲獎者之前,我們想向所有參與挑戰的開發者致敬。Flutter 社群再次團結一致,打造出非凡的成果。我們聚在一起參加了關於 動畫Firebase 主機滑動拼圖基礎 的演講。然後,看到 Flutter 開發人員打造出令人驚嘆的滑動拼圖陣容,一路分享亮點為您最喜歡的拼圖投票,真是令人興奮。感謝大家!

成為如此熱情且充滿創意的社群的一份子,真是太棒了。誰能想到有這麼多不同的方法來建立一個簡單的滑動拼圖遊戲?🤷‍♂️

廢話不多說… 頒獎時間到!

各個類別的獲獎者

最佳執行

Planets,作者:Jyotirmoy Paul

Flutter 拼圖 Hack 大賽獲獎者

這個專案在許多方面給評審們留下了深刻印象,包括出色的動畫運用、程式碼品質和純粹的創意。除了程式碼本身,Jyotirmoy 還分享了一些過程中很棒的學習時刻 - 看看他的分享,如果您有興趣。

最具創意

The Werewolves Escape,作者:Ahmad Ayaz Noor 和他的團隊

Flutter 拼圖 Hack 大賽獲獎者

這個功能豐富的專案結合了 Flutter 在網頁、行動和桌面端的優點。故事線讓滑動拼圖遊戲的趣味提升到了一個全新的層級。查看他們的 作品提交頁面,了解這個團隊在建立這個專案時學到了什麼。

最佳動畫/設計

Retro Slide Puzzle,作者:Ashish Beck

Flutter 拼圖 Hack 大賽獲獎者

這個拼圖的精緻視覺效果立刻吸引了評審們的注意。這個專案融合了流暢的 Rive 動畫、聲音外掛、自動完成和其他功能,讓遊戲體驗令人難以置信地令人滿足。

最佳跨平台

Slide Party,作者:Duong Bui Dai

Flutter 拼圖 Hack 大賽獲獎者

您是否曾經想過與朋友比賽,看誰能最快解開一個滑動拼圖?這個拼圖讓您夢想成真!不僅如此,由於 Flutter 的靈活性,玩家甚至不需要在同一台設備或作業系統上。

超過 40 位獲獎者!

儘管這些是各個類別的獲獎者,但從這麼多創意中選出獲獎者幾乎是不可能的。Flutter 拼圖 Hack 大賽還有超過 30 位其他獲獎者,以及許多其他優秀的參賽作品。前往 flutter.dev/events/puzzle-hack 查看獲獎者,並親自嘗試他們的拼圖!


宣布 Flutter 拼圖 Hack 大賽得獎者! 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

http://creativecommons.org/licenses/by/4.0/

【文章內容使用 Gemini 1.5 Pro 自動翻譯產生】

Dart 非同步程式設計:串流

一個顯示來自串流資料的簡單 Flutter 應用程式

本文涵蓋了反應式程式設計的基礎之一:串流,它們是 Stream 類型的物件。

如果您已閱讀我們先前關於 Futures 的文章,您可能記得 每個 Future 代表一個單一值(錯誤或資料),以非同步方式傳遞。串流的工作方式類似,只是它不是單一物件,串流可以隨著時間傳遞零個或多個值和錯誤

本文最初發表於 2020 年 2 月。此版本將包含的程式碼更新為可空安全性。

本文是基於《Flutter in Focus》影片系列《Dart 中的非同步程式設計》的第三篇文章。第一篇文章 Isolates and event loops 涵蓋了 Dart 支援背景工作的基礎知識。第二篇 Futures 討論了 Future 類別。

如果您喜歡透過觀看或聆聽來學習,本文中的所有內容都包含在以下影片中。

注意:本文中的程式碼已更新,以反映影片於 2019 年 6 月 28 日發佈後發生的最佳實務和 Dart 語言的變化(包括可空安全性)。

如果您考慮單一值與相同類型的 迭代器 的關係,那就是 Future 與串流的關係:Future 代表具有單一響應的單一請求,而串流代表具有多個響應的單一請求。

就像 Futures 一樣,關鍵是在事先決定 1) 當資料準備就緒時要做什麼,2) 發生錯誤時要做什麼,以及 3) 串流完成時要做什麼。與 Futures 一樣,在這個過程中,Dart 事件迴圈仍然在運行。

串流與 Dart 事件迴圈一起工作。

事件迴圈阻塞

例如,如果您使用 File 類別的 openRead() 方法從檔案讀取資料,此方法會返回一個串流。

資料塊從磁碟讀取並到達事件迴圈。Dart 函式庫查看它們並說:「啊,有人正在等待這個」,將資料加入到串流中,然後將其發送到您的應用程式。

當另一段資料到達時——它就會進入並出來。基於計時器的串流和從網路通訊端串流資料也與事件迴圈一起工作,使用時鐘和網路事件。

事件迴圈對資料進行排序。

監聽串流

接下來要了解的是如何使用串流提供的資料。

假設您有一個類別,它提供一個串流,每秒發出一個新的整數(1、2、3、4、5…)。您可以使用 listen() 方法訂閱串流。唯一的必需參數是一個函數。

1
2
3
4
5
final myStream = NumberCreator().stream;

final subscription = myStream.listen(
(data) => print('Data: $data'),
);

每次串流發出新值時,都會呼叫該函數並列印該值:

1
2
3
4
5
Data: 1
Data: 2
Data: 3
Data: 4
...

這就是 listen() 的工作原理。

重要:預設情況下,串流設定為單一訂閱。它們會保留其值,直到有人訂閱,並且它們在其整個生命週期中只允許一個監聽器。如果您嘗試兩次監聽同一個串流,則會收到異常。

幸運的是,Dart 也提供廣播串流。您可以使用 asBroadcastStream() 方法從單一訂閱串流建立廣播串流。廣播串流的工作方式與單一訂閱串流相同,但它們可以有多個監聽器。

廣播串流的另一個區別:如果在資料準備就緒時沒有人在監聽,則該資料會被丟棄。

1
2
3
4
5
6
7
8
9
final myStream = NumberCreator().stream;

final subscription = myStream.listen(
(data) => print('Data: $data'),
);

final subscription2 = myStream.listen(
(data) => print('Data again: $data'),
);

讓我們回到第一個 listen() 呼叫,因為還有一些事情要討論。

如前所述,串流可以像 Futures 一樣產生錯誤。透過將 onError 函數加入到 listen() 呼叫中,您可以捕獲和處理任何錯誤。

還有一個 cancelOnError 屬性,預設情況下為 true,但可以設定為 false,以便即使在發生錯誤後也能保持訂閱繼續進行。

您可以新增 onDone 函數,以便在串流完成傳送資料時執行一些程式碼,例如檔案已完全讀取完畢時。

結合所有四個參數——onErroronDonecancelOnError 和必需的參數 (onData)——您可以預先準備好應對任何情況。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
final myStream = NumberCreator().stream;

final subscription = myStream.listen(
(data) {
print('Data: $data');
},
onError: (err) {
print('Error!');
},
cancelOnError: false,
onDone: () {
print('Done!');
},
);

提示: listen() 返回的物件本身有一些有用的方法。它被稱為 StreamSubscription,您可以使用它來暫停、繼續,甚至取消資料流。

1
2
3
4
5
final subscription = myStream.listen(...);

subscription.pause();
subscription.resume();
subscription.cancel();

使用和操作串流

現在您已經知道如何使用 listen() 訂閱串流並接收資料事件,讓我們來談談讓串流真正酷炫的地方:操作它們。

一旦您在串流中獲得了資料,許多操作就會突然變得流暢而優雅。

讓我們回到之前的數字串流。

使用名為 map() 的方法,您可以從串流中獲取每個值,並動態地將其轉換為其他內容。給 map() 一個執行轉換的函數,它會返回一個新的串流,其類型與函數的返回值相符。

現在不再是整數串流,而是一個字串串流。在末尾呼叫一個 listen(),將 print() 函數傳遞給它,現在它會直接從串流中列印字串——非同步地,當它們到達時。

1
2
3
NumberCreator().stream
.map((i) => 'String $i')
.listen(print);
1
2
3
4
5
String 1
String 2
String 3
String 4
*/

您可以像這樣連結許多方法。例如,如果您只想列印偶數,則可以使用 where() 過濾串流。給它一個測試函數,該函數為每個元素返回一個布林值,它會返回一個新的串流,該串流只包含通過測試的值。

1
2
3
4
NumberCreator().stream
.where((i) => i % 2 == 0)
.map((i) => 'String $i')
.listen(print);
1
2
3
4
String 2
String 4
String 6
String 8

distinct() 方法是另一個好方法。對於使用 Redux store 的應用程式,該 store 會在 onChange 串流中發出新的應用程式狀態物件。

您可以使用 map() 將狀態物件串流轉換為應用程式一部分的視圖模型串流。然後,您可以使用 distinct() 方法獲取一個串流,該串流會過濾掉連續的相同值(以防 store 啟動一個不影響視圖模型中資料子集的更改)。

然後,您可以監聽並在獲得新的視圖模型時更新 UI。

1
2
3
4
myReduxStore.onChange
.map((s) => MyViewModel(s))
.distinct()
.listen( /* update UI */ )

Dart 內建了其他方法,您可以使用它們來調整和修改您的串流。此外,當您準備好使用更進階的功能時,還有 Dart 團隊維護的 async 套件,可在 pub.dev 上獲得。它包含可以合併兩個串流、快取結果以及執行其他基於串流的魔術的類別。

嘗試使用 async 套件來獲得更多基於串流的魔術。

若要獲得更多串流魔術,請查看 stream_transform 套件

建立串流

最後,還有一個值得一提的更進階的主題是如何建立您自己的串流。

就像 Futures 一樣,大多數情況下,您將使用網路函式庫、檔案函式庫、狀態管理等為您建立的串流,但您可以使用 StreamController 建立自己的串流。

讓我們回到我們一直使用的 NumberCreator 範例。這是它的實際程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
class NumberCreator {
NumberCreator() {
Timer.periodic(const Duration(seconds: 1), (timer) {
_controller.sink.add(_count);
_count += 1;
});
}

final _controller = StreamController<int>();
var _count = 0;
Stream<int> get stream => _controller.stream;
}

如您所見,它會持續計數,並使用計時器每秒增加一次計數。然而,有趣的是串流控制器。

StreamController 從頭開始建立一個全新的串流,並讓您可以存取它的兩端。有串流端本身,資料到達的地方。(我們在本文中一直在使用這個。)

1
Stream<int> get stream => _controller.stream;

還有接收端,這是將新資料加入到串流的地方:

1
_controller.sink.add(_count);

NumberCreator 使用了這兩個。當計時器關閉時,它會將最新的計數加入到控制器的接收端,然後它會使用公共屬性公開控制器的串流,以便其他物件可以訂閱它。

使用串流構建 Flutter Widget

現在我們已經介紹了建立、操作和監聽串流,讓我們來談談如何在 Flutter 中使用它們來構建 Widget。

如果您閱讀了先前關於 Futures 的文章,您可能記得 FutureBuilder。您給它一個 Future 和一個構建器方法,它會根據 Future 的狀態構建 Widget。

對於串流,有一個類似的 Widget 稱為 StreamBuilder。給它一個像 number creator 那樣的串流和一個構建器方法,它會在每次串流發出新值時重建其子 Widget。

1
2
3
4
5
6
7
StreamBuilder<String>(
stream: NumberCreator().stream.map((i) => 'String $i'),
builder: (context, snapshot) {
// 構建一些 Widget
throw UnimplementedError("Case not handled yet");
},
);

snapshot 參數是一個 AsyncSnapshot,就像 FutureBuilder 一樣。

1
2
3
4
5
6
7
8
9
StreamBuilder<String>(
stream: NumberCreator().stream.map((i) => 'String $i'),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text('No data yet.');
}
throw UnimplementedError("Case not handled yet");
},
);

您可以檢查其 connectionState 屬性以查看串流是否尚未傳送任何資料,或者它是否已完全完成。

1
2
3
4
5
6
7
8
9
10
11
StreamBuilder<String>(
stream: NumberCreator().stream.map((i) => 'String $i'),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text('No data yet.');
} else if (snapshot.connectionState == ConnectionState.done){
return const Text('Done!');
}
throw UnimplementedError("Case not handled yet");
},
);

您可以使用 hasError 屬性來處理資料值,並查看最新值是否為錯誤。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
StreamBuilder<String>(
stream: NumberCreator().stream.map((i) => 'String $i'),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text('No data yet.');
} else if (snapshot.connectionState == ConnectionState.done) {
return const Text('Done!');
} else if (snapshot.hasError) {
return const Text('Error!');
} else {
return Text(snapshot.data ?? '');
}
},
);

最重要的是要確保您的構建器知道如何處理串流的所有可能狀態。一旦您掌握了這一點,它就可以對串流的任何操作做出反應。(有關更多資訊,包括您可以使用的 DartPad 實例,請參閱 StreamBuilder API 頁面。)

總結

本文涵蓋了串流的含義、如何從串流中獲取值、操作這些值的方法,以及 StreamBuilder 如何幫助您在 Flutter 應用程式中使用串流值。

您可以從 Dart 和 Flutter 文件中了解更多關於串流的資訊:

敬請期待本系列的更多文章。接下來我們將討論 asyncawait。這是 Dart 提供的兩個關鍵字,可幫助您保持非同步程式碼的簡潔性和可讀性。

同時,您可以在我們的 YouTube 頻道上觀看關於《Dart 中的非同步程式設計》的下一部影片系列,或者訪問我們的網站以獲取更多關於 DartFlutter 的資訊。


Dart 非同步程式設計:串流 最初發佈於 Medium 上的 Dart,人們在那裡透過醒目顯示和回應這個故事來繼續對話。

【文章內容使用 Gemini 1.5 Pro 自動翻譯產生】

徵求 Google Summer of Code 2022 貢獻者

Google Summer of Code (GSoC) 是一個全球性的計畫,旨在讓學生開發者參與開源軟體開發。Google 贊助學生在夏季與一個指導性的開源組織合作進行一個 12 週以上的程式設計專案。在過去的 17 年裡,已有超過 18,000 名學生參與了 Google Summer of Code。

我們很高興地宣布,Dart 將在 2022 年的 Google Summer of Code 中擔任指導組織(這是第三次!)。查看成果文章,了解更多關於 2020 年和 2021 年的優秀專案。

您有興趣嗎?

現在就開始閱讀 專案構想列表,找到符合您的技能和興趣的專案。正式申請必須在 4 月 19 日之前提交,但鼓勵潛在申請者提交早期草稿並徵求回饋。

Dart 團隊預計會有足夠的導師,但只會接受少數申請,因此也請查看 其他指導組織

如果您有關於 Dart 和 GSoC 的具體問題,請在我們 專用的郵件列表 中提問。

有關 Google Summer of Code 的更多資訊,請觀看下面的影片。或者閱讀 Google Summer of Code 貢獻者指南

我們期待您的參與!


Google Summer of Code 2022 貢獻者 最初發佈於 Medium 的 Dart,人們在那裡透過醒目顯示和回應這個故事來繼續討論。