0%

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

使用 Flutter 開發跨平台應用程式 - Google Classroom 如何讓教師和學生步調一致

Google Classroom 應用程式最初於 2014 年推出,如今全球有 1.5 億教師和學生使用它來組織作業、成績和課堂通訊。該應用程式可在 Android 和 iOS 上使用,開發工作始於當年早些時候,並經歷了兩個行動平台的巨變時代。事實證明,管理這些分歧的變化帶來了挑戰。

儘管努力同步,但在 2021 年,也就是 7 年後,Classroom 應用程式的 Android 和 iOS 程式碼庫在功能、UI 和實作方面逐漸出現分歧。從最明顯的地方,例如屏幕對相同 UI 採取不同方法,到不太明顯的地方,例如驗證和應用程式啟動邏輯的差異;Classroom 逐漸變成一個難以維護和改進的應用程式,兩個程式碼庫給一個小型開發人員團隊帶來了沉重的負擔。

有許多選項可供選擇,從堅持現狀,到增加更多開發人員,再到使用跨平台框架完全重寫兩個程式碼庫。該團隊致力於改進,這排除了堅持現狀的選項,然後評估穩定兩個現有程式碼庫需要做些什麼,這排除了僅添加更多開發人員的方案。最終,團隊選擇了第三個方案:使用單一來源、跨平台的解決方案重新構想 Classroom,而這個方案最終是 Flutter。

Flutter 如何簡化 Classroom 應用程式

不一致的 UI

Classroom 最明顯的問題 - UI 變異 - 強迫教師熟悉 Android 和 iOS 的 UI。畢竟,很容易想像學生會問關於作業屏幕和說明的問題,而一個平台上的說明在另一個平台上對於學生來說可能毫無意義。

傳統方法是由不同的團隊開發的獨立的客戶端應用程式,這些應用程式會隨著時間推移而出現分歧。只有對每個功能進行一致而艱苦的同步工作才能阻止這種情況。相比之下,Flutter 的本質顛覆了這種預設結果。使用 Flutter,UI 預設情況下是相同的 [1],直到主動工作(通常為了適應性)迫使它們為了使用者利益而出現分歧。

[1] 在 Classroom 的 Flutter 客戶端中,保留了一些細微的故意差異,例如系統欄和底部控制項。Flutter 保留了這些平台特有的細節,同時將屏幕中央 90% 的區域留給單個 UI 實作來填充。

混亂的業務邏輯

Classroom 的 Android 和 iOS 客戶端之間存在著差異,這不僅僅是 UI 的問題。受伺服器端解決方案的影響,該方案將一些複雜的業務邏輯轉移到客戶端,舊版的 Classroom 應用程式還處理核心實作方面的差異。除了造成偶爾的平台特定錯誤(這可能會讓工程師難以重現!)之外,這還給任何評估兩種實作正確性的人都帶來了相當大的認知負擔。

使用 Flutter 重寫 Classroom 解決了一系列錯誤,包括先前報告的和未報告的錯誤,這純粹是 Flutter 如何處理原生平台互動的結果。

在原始程式碼中,多年的持續開發偶爾會模糊 UI、業務和平台特定邏輯之間的界限。這意味著使用者的錯誤報告幾乎總是需要付出巨大的努力才能隔離,因為整個呼叫堆疊都可能存在問題。是否由於檔案系統讀取錯誤、業務邏輯通信錯誤,或者由於 UI 接收了檔案但隨後遺失了檔案而導致請求的檔案無法載入?唯一的解決方法是 調查所有內容

當然,Flutter 開發人員可以像其他人一樣混淆這些界限並混合邏輯,但 Classroom 工程團隊發現,遵循框架最佳實務使這種嘗試變得十分明顯。Flutter 的聲明式 UI 系統強烈建議不要將業務邏輯意外放置在 UI Widget 中,而新的 MVVM 架構甚至有助於在位於 Flutter Widget 背後的龐大程式碼庫中強制執行明確的責任層級。

Flutter 應用程式仍然會定期與底層平台進行通信 - 畢竟,上傳和查看作業的使用者旅程離不開檔案系統 - 但 Flutter 將平台特定邏輯隔離到專用 Plugin 的模式再次阻止了例行磁碟 I/O 偷偷溜進不應該出現的地方。以下範例展示了 Flutter 應用程式在不混淆整個呼叫堆疊的情況下存取檔案系統的一種實際方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import "dart:io";
import "package:path/path.dart" as path;
import "package:path_provider/path_provider.dart" as path_provider;

// 載入學生為特定作業儲存的作業。
// 回傳值的 exists() 函式會在學生將作業交給狗狗吃掉的情況下返回 False。
Future<File> getHomework(Assignment assignment) async {
// 使用 `path_provider` 套件將平台特定的檔案系統怪癖抽象化
final Directory homeworkDirectory =
await path_provider.getApplicationSupportDirectory();

// 提取學生上傳的作業
return File(
path.join([homeworkDirectory.absolute.path, assignment.name]),
);
}

這個範例很簡單,Classroom 工程團隊最終開發了他們自己的幾個 Plugin,以包含更複雜的與主機平台的互動。有趣的是,這麼做讓他們的原生程式碼 更容易除錯,比他們最初的原生應用程式更容易。這是怎麼做到的呢?在 Flutter Plugin 中遵循「不要重複自己 (DRY)」原則意味著將儘可能多的業務邏輯提升到 Dart 程式碼中,只為原生互動保留最簡單的進出方法呼叫。這會強制在領域邏輯和平台邏輯之間建立堅如磐石的分隔;這意味著 Classroom 的 Android 或 iOS 程式碼中的任何錯誤都可能出現在隔離的單一責任函式中,這些函式很容易進行推理。

效能消耗

當使用者旅程失敗時,就會提交一個具體的錯誤,所有人都同意需要解決。但對於更微妙的問題,例如應用程式啟動時間隨著時間推移越來越慢,自應用程式推出以來每年都在惡化,該怎麼辦?再加上對保持多個客戶端同步的擔憂,突然之間,對應用程式啟動流程的緩慢進行故障排除感覺就像一項無望的任務。

在這裡,Flutter 通過自身速度足够快,不至於加剧问题,更重要的是,它提供了從頭開始的機會。Classroom 團隊知道他們是在建立新的東西,而不是繞過多年開發積累的骨架,他們通過移除冗餘的 API 呼叫,並行化其他獨立的 API 呼叫,以及在所有內容解析完成時顯示閃爍效果和其他 UI 預覽,使他們的授權和啟動流程更加清晰明了。结果是應用程式启动时间惊人地 *减少了 80%*!

注釋功能

Classroom 的大部分功能可以看作是一個相當例行的應用程式,它將使用者圍繞共享內容(如作業和上傳的作業)聚集在一起。但有一個功能顯得 非常棘手。Classroom 的主要功能之一是檔案共享,教師和學生都可以創建、查看和編輯檔案,包括自由形式的注釋,就像在真紙上用筆或記號筆繪畫一樣。這個注釋共享功能已經存在於 Classroom 的原生 Android 和 iOS 客戶端中,因此將其移植到 Flutter 的任何摩擦都會成為阻礙。

Classroom 團隊能夠將這個注釋功能重新打包到一個 Plugin 中,該 Plugin 將平台特定實作委派給單獨的函式庫。對於檔案注釋,這些函式庫成為了 Google One、Google Keep 和舊版 Classroom 應用程式中已經使用的預先存在的原生函式庫的薄薄包裝。在內部,Android 和 iOS 對檔案共享有不同的實作要求。在 iOS 上,Classroom 應用程式通過原生視圖存取檔案,但在 Android 上,它會直接打開 Google Keep 應用程式。但是,良好的 Plugin 設計原則能夠將這些實作細節隔離,同時仍然為應用程式中的其他部分提供一個乾淨的、單一的 Dart API 來進行導航。最終,Classroom 中「最棘手」的功能之一已成功移植到 Flutter。

以下是 Classroom 在 Android 上的注釋功能的可視化,概述了原生和 Flutter UI 組件的混合。

四個並排的行動裝置屏幕,共同展示了選擇和注釋檔案的使用者流程

更籠統地說,典型的 Flutter Plugin 設計看起來像下面這樣,其中一個單一的、簡化的介面載入平台特定的函式庫,這些函式庫反過來又使用 FFI 或 JNI 與底層平台進行通信。這允許 Flutter 應用程式與所有構建目標的平台特定原生 API 互動,而不會將這些考慮因素洩露到 Dart 程式碼中。

Flutter Plugin 設計圖

回顧

開發速度

Classroom 團隊花費了兩年的時間來使用一個團隊重寫他們的應用程式,這個團隊從 1 名工程師(用於最初的原型設計階段)到開發高峰期的 10 名全職工程師不等。這不是一筆小投入,但團隊基於更快開發和維護的承諾做出了這個決定。Classroom 於 2023 年 6 月在 iOS 上推出了他們的 Flutter 重寫版本,並於 2024 年 1 月在 Android 上推出該專案。此後,用於新功能的平均工程師時間減少了三分之二,這意味著開發速度提高了三倍!在等了兩年之後,利益相關者對期待已久的 ✨更快功能開發✨ 感到非常高興。

Classroom 團隊決定重寫的部分原因是,他們知道他們的專案永遠不會「完成」,新的功能很可能會在很長一段時間內不斷添加。這為一個有說服力的案例提供了支持,即重寫,即使是一項昂貴的重寫,最終也會得到回報。Classroom 團隊計算他們使用 Flutter 重寫應用程式後何時才能實現投資回報的公式是:

估算使用 Flutter 重寫應用程式後,提高的速度何時會超過重寫時間的公式。公式為重寫時間除以使用 Flutter 編寫功能所需時間的減少量,等於達到盈虧平衡點的發佈後功能數量。

在 iOS 推出 9 個月後,Classroom 估計已經通過 Flutter 提供的開發速度提高三倍,從而收回了最初投資的 40%。

開發人員體驗

除了利益相關者之外,唯一比開發速度提高更讓 開發人員自己 高興的是 - 開發人員自己。對於 Classroom 團隊來說,他們 3 倍的速度增長來自於僅需編寫一次每個功能(或者至少 1.5 次,在具有大量原生組件的情況下),消除了協調兩個團隊的成本,而這兩個團隊的時間表通常相差數月,當然還有熱重載。熱重載本身及其大約 99% 的重建時間減少,讓 Classroom 團隊的士氣比他們使用兩個原生客戶端時更高。最終,Classroom 團隊發現,在他們轉向 Flutter 之後,很容易吸引和留住工程師。

此外,Classroom 團隊發現,平均來說,新的功能需要減少至少 50% 的程式碼行數才能實現。實際上,減少量可能要高得多,因為他們在重寫過程中建立的每個功能在兩個原生客戶端中都沒有真正實現!換句話說,50% 的程式碼行數實現了所有舊的功能,還覆蓋了 大量 功能缺口(包括 iOS 上的離線支援)。

總結

在開始重寫大約兩年後,Classroom 團隊將他們的應用程式發佈到 Android 和 iOS,並添加了足夠的額外功能,以償還他們前期投資的 40%。他們的全新應用程式啟動速度是舊版本啟動速度的 五倍,為開發人員和最終使用者節省了時間和挫折。展望未來,新的功能平均開發成本是舊狀況的 三分之一,可以在兩個平台上同時發佈,並且 更容易 除錯和維護。最終,在 Classroom 切換到 Flutter 以重新投資他們的 未來 後,使用者、開發人員和利益相關者的士氣達到了前所未有的高度。


使用 Flutter 開發跨平台應用程式 - Google Classroom 如何讓教師和學生步調一致 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

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

優化 Flutter Web 載入速度的最佳實務

身為一名與 Google Flutter 團隊合作,並在私人時間進行個人專案的 Flutter 開發人員,我遇到並理解了關於 Flutter Web 應用程式載入速度的擔憂。優化載入速度對於提升效能和提供良好的使用者體驗至關重要,尤其是在 Flutter 開發人員擴展到 Web 開發時。本指南提供可執行的策略和最佳實務,以加速 Flutter Web 應用程式的效能。

渲染

CanvasKit 是 Flutter Web 應用程式的預設 渲染器,它透過利用 WebGL 在平台間提供高效能和像素完美的一致性。這種功能對於需要豐富動畫和高保真度的複雜圖形應用程式特別有用。然而,CanvasKit 的內建檔案大小(約 1.5 MB)可能是一個缺點,特別是在初始載入時間至關重要的應用程式中。

儘管 flutter.js 載入 API 可並行下載 CanvasKit 和 main.dart.js,但所有 Flutter Widget 都必須等到它們完全載入到瀏覽器中,這可能會在應用程式變得互動之前導致明顯的延遲。為了減輕這些問題並優化載入體驗,開發人員可以選擇 Wasm 渲染模式。

由於 Flutter Web 中的 WebAssembly 支援 被視為實驗性並且可能會發生變化,因此這些步驟適用於願意嘗試尖端功能的開發人員。功能和指令可能會演變,因此請務必參閱最新的 Flutter 文件以瞭解目前的實務。

相容性

當使用 Wasm 進行構建時,dart:html 套件不受支援。此限制意味著您必須仔細考慮您的應用程式相依的 API。或者,web 套件dart2wasmdart2js 支援。

效能

Wasm 不僅與 CanvasKit 相比減少了應用程式大小,而且與 JavaScript 相比,啟動速度也更快。

延遲載入

Dart 的 延遲載入函式庫 允許您分割程式碼,並且只在需要時載入部分程式碼,從而減少初始載入時間。以下部分將討論如何使用延遲載入。

宣告延遲載入

在 Dart 檔案的頂部,宣告您要延遲載入的載入。在載入陳述式中,指定 deferred,後面接著識別符號。當您需要使用函式庫時,請使用延遲載入的 loadLibrary() 方法非同步載入它:

1
2
3
4
5
6
import 'package:myapp/hello.dart' deferred as hello;

Future<void> loadHelloLibrary() async {
await hello.loadLibrary();
hello.sayHi();
}

呼叫載入函數

在您的 Flutter 應用程式中,根據需要呼叫此函數,例如,作為對使用者互動的回應。以下範例在使用者按下 Widget 時載入所需的函式庫:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import 'package:flutter/material.dart';

void main() {
runApp(MaterialApp(home: MyApp()));
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
loadHelloLibrary();
},
child: Text('Load Feature'),
),
),
);
}
}

未等待的函數呼叫

為了減少顯示應用程式初始 Widget 所花費的時間,請嘗試在呼叫 runApp 之前不要等待代價高昂的 Future。可以將一些 Future 設為未等待,以便它們在完成後更新 UI。unawaited 函數 允許應用程式程式設計師明確告知「未等待的 Future」提示,這些 Future 不會等待。此改進透過讓應用程式感覺更具回應性,在應用程式啟動和頁面載入期間都增強了使用者體驗。但是,必須小心管理這些函數,以避免與應用程式狀態一致性和資源管理相關的問題。

1
2
3
4
5
6
7
8
9
10
import 'dart:async';
import 'package:flutter/material.dart';

void main() {
unawaited(downloadVideos().then((videos) {
playlist.add(videos);
}));

runApp(const MyApp());
}

媒體檔案

以最佳解析度顯示資產

Flutter 會自動根據設備的像素密度 以適當的解析度載入資產。這可確保在不同螢幕尺寸上顯示最佳視覺效果。雖然優化資產以進行有效傳遞很重要,但請優先考慮在探索替代圖片格式之前提供所需精確解析度的資產,就像我們在下一節中將討論的那樣。

更好的圖片壓縮

PNG 和 JPG 是網站上使用最普遍的圖片格式。這些格式以其廣泛的支援和相容性而聞名。然而,新一代格式(如 WebPAVIF)在減少檔案大小方面取得了重大進展,而不會顯著降低圖片品質。例如,原始大小為 319 KB 的 PNG 圖片可以用 WebP 格式壓縮到僅 38 KB,或者更令人印象深刻的是,可以用 AVIF 格式壓縮到 10 KB。這些檔案大小的縮減是透過肉眼幾乎無法察覺的品質損失來實現的,這證明了這些格式在保持視覺保真度的同時,提升網站載入速度的潛力。

PNG 319 KB / WebP 38 KB / AVIF 10 KB

然而,重要的是要注意,並非所有瀏覽器都支援 WebPAVIF 圖片。在將這些格式整合到您的網站之前,請驗證它們與您的觀眾最常使用的瀏覽器的相容性。這將幫助您確定這些新一代圖片格式是否符合您的網站要求和觀眾需求。

快取

記憶體、磁碟、Service Worker 快取

利用記憶體快取、磁碟快取和 Service Worker 的功能可以顯著減少初始頁面載入後的載入時間。這是因為這些快取機制需要檔案先載入一次,然後才能快取它們。記憶體快取儲存在 RAM 中,提供快速的存取速度,但易失性。另一方面,磁碟快取雖然速度較慢,但提供持久性。Service Worker 作為可程式化的網路代理,可以跨記憶體和磁碟實現複雜的快取策略。

瀏覽器或作業系統通常會自動管理記憶體和磁碟快取,除非有特定的要求需要以程式方式操作它們,否則不需要手動干預。雖然 Flutter 在一定程度上管理 Service Worker,但開發人員可以靈活地在 Flutter 之外實作自訂 Service Worker,以增強對快取和網路互動的控制。

Wasm

瀏覽器會快取 Wasm 檔案(如 CanvasKit,以及很快的 dart2wasm 輸出),以及它們的編譯原生程式碼。這意味著快取的 Wasm 模組載入速度與原生二進制檔案一樣快,與 JavaScript 不同,JavaScript 需要重新解析、重新編譯和重新 JIT(Just-In-Time)處理。

雖然 Flutter 的 Wasm 構建選項尚未完全穩定,但當 dart2wasm 穩定後,採用現代 JS-interop 實務會使您受益。例如,避免使用 dart:htmldart:js 等傳統函式庫,並優先考慮 package:webdart:js_interop。此外,請考慮檢查您正在使用的其他套件是否與 Wasm 相容。

預載入

HTML <head> 標籤、HTTP 響應標頭

預載入資產(如圖片、字體和 JavaScript 檔案)可以顯著提高網頁載入速度。透過在 HTML <head> 標籤中預載入或使用 HTTP 響應標頭,您可以指示瀏覽器在需要用於渲染之前下載這些資源。這樣可以消除延遲,並確保更順暢的使用者體驗。若要預載入資產,請在 <head> 部分添加 <link> 標籤,並將 rel 屬性設定為 preload。僅預載入立即使用的資產,最優化的是應用程式的第一個螢幕,否則瀏覽器會將預載入視為浪費頻寬。

HTML <head> 標籤

1
2
3
4
5
6
7
8
<head>
<link rel="preload" href="assets/logo.webp" as="image" type="image/webp" />
</head>
<body>
<picture>
<source src="assets/logo.webp" type="image/webp" />
</picture>
</body>

Firebase 托管的 HTTP 響應標頭

以下程式碼塊是一個 firebase.json 範例,其中包含一個鍵/值組合,展示如何為資產預載入添加 HTTP 標頭。

1
2
3
4
5
6
"headers": [
{
"key": "Link",
"value": "<assets/logo.webp>; rel=preload; as=image"
}
]

登陸頁面

Flutter 使您能夠使用純粹的 HTML/CSS 為您的應用程式構建完全互動式的登陸頁面。當使用者與您的登陸頁面互動時,flutter.js 會預載入您的 Flutter 應用程式 - 確保使用者導航到 Flutter 應用程式時立即啟動。這對於具有 播放 按鈕的遊戲和需要登入的應用程式特別有用。

載入/啟動畫面

雖然我們一直關注應用程式載入速度的技術優化,但感知載入速度更為關鍵。您的目標應該是讓您的應用程式 感覺很快

載入/啟動畫面在增強這種感知方面非常有效。透過提供視覺活動,它們向使用者保證應用程式正在迅速啟動。相反,空白螢幕會引起不確定性,可能會導致沮喪和頁面重新整理。

為了實現最快的回應能力,請使用純粹的 CSS/HTML 在您的 index.html 檔案中直接實作啟動畫面。這可以最大限度地減少任何潛在的延遲。

例如,請查看 Flutter Gallery 實作

結語

在本文件中,我們探討了加速 Flutter Web 應用程式初始載入和渲染效能的方法。您可以採用多種策略,但請記住,每個解決方案都涉及權衡取捨。選擇最適合您的特定需求和使用者需求的優化。透過結合這些方法، 您可以為您的 Flutter Web 應用程式創造更流暢、更具回應性的使用者體驗。


優化 Flutter Web 載入速度的最佳實務 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

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

Flutter 和 Dart 在 Google Cloud Next 上

如果您錯過了,Flutter 團隊上週首次參加了 Google Cloud Next,這證明了 Flutter 在開發社群中的影響力日益增長。

我們與 Google Cloud、Firebase、Very Good VenturesServerpod 的團隊合作,提供重點關注簡化工作流程的閃電演講、議程和演示攤位。

如果您錯過了,以下回顧了我們分享的內容。

使用 Firestore、Cloud Run 和 Flutter 快速開發

我們實時開發了一個最小可行 Flutter 應用程式,透過 Google Cloud Run 支援的 API 支援 Firestore 資料庫。您可以從這些 資源 了解更多有關如何自己執行此操作的資訊。

利用 Flutter、Firebase 和 Gemini 開發智慧型應用程式

我們使用 Flutter、Firebase 和 Gemini 從頭開始建立了一個動態的真實世界應用程式,現場展示了 Firebase 的 Firestore 和儲存功能如何簡化資料管理和媒體處理。觀看 演講的錄製

如何使用 Serverpod 開發全堆疊 Dart

我們展示了開發人員如何在伺服器上使用 Dart 共用代碼並使用 Serverpod 補充他們的 Flutter 應用程式,Serverpod 是一個全堆疊 Dart 解決方案,它使用代碼生成來建立匹配的客戶端-伺服器代碼以及基於您的架構的豐富功能的 Postgres ORM。查看 這個 Observable Flutter 集 以了解更多資訊。

使用 Firestore 對 Flutter 應用程式進行現場編碼

我們展示了 Google 的 Project IDX 如何讓為 Flutter 設定開發環境變得更加容易!查看 Project IDX 開始使用。

使用 Flutter、Dart Frog 和 Gemini 開發全堆疊應用程式和遊戲

Very Good Ventures (VGV) 展示了如何使用 Gemini 和 Flutter 以「選擇你的冒險」風格建立一個 AI 支援的遊戲。現在就觀看 演講

如何使用 Dart Frog 和 Google Cloud 共用代碼

VGV 展示了如何使用 Dart Frog 在您的前端和後端之間快速建立和運行 代碼共用,啟動一個全堆疊專案,並在幾分鐘內讓它在設備上和 Cloud Run 中運行。

您可能錯過了

查看 Cloud Next 中的這些公告,我們認為這些公告有可能幫助 Flutter 和 Dart 開發人員更輕鬆地在您的應用程式中構建 AI 功能。

Firestore 向量搜尋支援 + 擴展發佈

Firestore 中的向量搜尋嵌入,以及 Firestore 向量搜尋擴展,讓 Flutter 和 Dart 開發人員能夠將 Firestore 資料轉換為豐富的、可搜尋的知識圖,為高度創新的生成式 AI 功能提供支持。當您使用 Firestore 時,這為將 AI 支援的產品推薦、智慧型聊天機器人和其他複雜的體驗直接整合到您的 Flutter 應用程式中打開了大門。

Vertex Gemini Dart SDK 私人預覽

Vertex Gemini SDK 是一組用於 Firebase 的客戶端 SDK,支援 Dart、Kotlin、Swift 和 JavaScript。它們旨在透過輕鬆存取 Vertex AI 服務來加速開發。Flutter 和 Dart 開發人員可以透過此 Google 表單 請求存取私人預覽。

向我們展示您正在構建的內容

對於想要構建由 Google Cloud 支援的高品質跨平台體驗的開發人員來說,未來一片光明,我們想看看您正在構建什麼。如果您將 Flutter 與 Firebase 或任何 Google Cloud 產品或服務結合使用,請在 TwitterLinkedIn 上告訴我們!

如果您只是想開始使用 Flutter 和 Google Cloud,請查看以下資源。

直到下次 😉 - 我們迫不及待想看看您會建立什麼!


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

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

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

Flutter 如何讓畫布渲染的應用程式對輔助技術使用者更友善

Flutter 框架支援的目標平台之一是 Web。Flutter 應用程式透過將所有 UI 渲染到畫布元素上,來保證像素精準度和平台一致性。但是,預設情況下,畫布元素不可存取。本案例研究說明了此類畫布渲染的 Flutter 應用程式如何支援存取性。

Dash, the Flutter mascot, with a tablet and a drink.

Flutter 擁有大量的預設 Widget,這些 Widget 會 自動產生存取性樹。存取性樹是一個存取性物件的樹,輔助技術可以查詢其屬性和特性,並在其上執行操作。對於自訂 Widget,Flutter 的 Semantics 類別允許開發人員描述 Widget 的含義,幫助輔助技術理解 Widget 的內容。

出於效能考量,在撰寫本文時,Flutter 的存取性預設情況下是選擇性啟用的。Flutter 團隊希望最終在 Flutter Web 中預設啟用語義。然而,在目前的狀況下,這會在許多情況下導致顯著的效能成本,並且需要一些優化才能更改預設值。希望始終啟用 Flutter 存取性模式的開發人員可以使用以下程式碼片段:

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

void main() {
runApp(const MyApp());
if (kIsWeb) {
SemanticsBinding.instance.ensureSemantics();
}
}

注意:如果您的應用程式絕對需要知道使用者是否正在使用螢幕閱讀器等輔助設備,請允許使用者選擇性啟用。

啟用 Flutter 的存取性支援後,HTML 會自動更改,如本頁面的其餘部分所示。

注意: 螢幕閱讀器只是利用所述方法獲益的輔助技術的一個例子。為了提高可讀性,螢幕閱讀器被用作此類技術和其他輔助技術的代表。

Flutter 的存取性選擇性啟用

Flutter 的選擇性啟用機制是一個隱藏的按鈕。它在 HTML 中放置一個按鈕,準確地說,是一個 flt-semantics-placeholder 元素,其 role 等於 “button” - 對有視力的人來說是不可見且不可達的。它是一個自訂元素,已應用樣式,因此它不會顯示,除非您使用螢幕閱讀器,否則不可選取。

1
2
3
4
5
6
7
8
9
10
11
12
<flt-semantics-placeholder
role="button"
aria-live="polite"
aria-label="Enable accessibility"
tabindex="0"
style="
position: absolute;
left: -1px;
top: -1px;
width: 1px;
height: 1px;"
>&lt;/flt-semantics-placeholder&gt;
1
2
3
4
/* `<flt-semantics-placeholder>` inherits from `<flutter-view>`. */
flutter-view {
user-select: none;
}

啟用後的變更

當一個使用螢幕閱讀器的使用者點擊這個按鈕時會發生什麼?考慮一個不太複雜的例子,例如 Flutter Gallery 中的 卡片,如以下螢幕截圖所示。

A classic card component with an image, a heading, and some text.

為了更好地理解使用者點擊按鈕時發生了什麼變化,請比較啟用和未啟用 Chrome DevTools 的螢幕截圖,當您 檢查存取性樹 時。第二張螢幕截圖顯示的語義資訊比第一張多很多。

未啟用:

Chrome DevTools showing an “Enable accessibility” button.

已啟用:

Chrome DevTools showing a rich accessibility tree with headings, buttons, groups, etc.

實作細節

Flutter 的核心思路是建立一個可存取的 DOM 結構,反映目前在畫布上顯示的內容。這包括一個 flt-semantics-host 父自訂元素,它具有 flt-semanticsflt-semantics-container 子元素,而這些子元素又可以巢狀。考慮一個按鈕 Widget,例如 TextButton。此 Widget 在 DOM 中由 flt-semantics 元素表示。flt-semantics 元素上的 ARIA 標註(例如,rolearia-label)和其他 DOM 屬性(tabindex,事件監聽器)允許螢幕閱讀器向使用者宣佈這個元素為按鈕,並支援點擊和點選它,即使它不是一個字面上的

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

全球遊戲挑戰賽:公布 20 強!

在過去 9 週裡,看到數千位開發人員齊心協力,使用 Flutter 和 Dart 為地球開發史詩般的遊戲,令人振奮不已。參賽作品包括 RPG、平台遊戲、AI 驅動遊戲等等。您的創意橫跨大陸、類型、格式、平台,甚至銀河系!

您的創意總是讓我們驚嘆不已,因此我們的 Flutter 評審團在評估來自全球各地的才華橫溢的開發人員、設計師、藝術家和倡導者的數百款遊戲時,面臨著艱難的抉擇。話不多說,以下是 20 強!

重大揭曉!

請敲響鼓聲!以下是晉級決賽的 20 個專案:

接下來呢?

從這裡開始會更加令人興奮!由全球公民、聯合國為計畫聯盟遊戲、Google Play、YouTube 和 Flutter 社群代表組成的最終評審團將選出我們的 10 強決賽。10 強將於 2024 年 5 月宣布。

在此同時,從 4 月 22 日開始,您就可以為您最喜歡的遊戲投票!前往 globalgamers.devpost.com 投票,幫助我們選出社群選擇的冠軍!

感謝您,繼續建設!

我們衷心感謝所有參與者的參與,再次熱烈祝賀我們的 20 強!來自 Google 和全球公民的所有人向您致敬 - 繼續懷抱更大的夢想,繼續編碼,並繼續為我們的地球努力奮鬥,一次一場精彩的遊戲!


全球遊戲挑戰賽:公布 20 強! 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

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

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

Dart 中 JS 互操作的歷史

Wasm 支援剛剛在現有的 Flutter beta 版本中推出,這得益於 Dart 3.3 中達成的令人興奮的 JavaScript 互操作里程碑。為了慶祝,我們回顧了 Dart 和 JS 互操作長達十年的發展歷程。

AI Image generated by Gemini

互操作性一直是 Dart 從一開始就關注的核心議題。當 Dart 在 2011 年首次發佈時,它被設計為 可嵌入跨平台 的。它運行在獨立的虛擬機上,嵌入在瀏覽器中,並編譯為 JavaScript。當 Flutter 在 2015 年出現時,我們已經準備好將它嵌入到這裡。現在,我們也很興奮地 將目標定位到 WasmGC 執行時

起初,我們迅速努力暴露 Dart 嵌入的每個平台的功能。這就是我們的 SDK 平台特定函式庫出現的方式:dart:io 暴露了 VM 上的文件系統,dart:html 暴露了 Web 上的瀏覽器 API,等等。這些函式庫看起來和感覺起來像普通的 Dart 函式庫,但在幕後隱藏了一些複雜的低階原生元素,使它們能夠運作。這是我們發明的第一種互操作形式。它表達力強,但僅限於 SDK 函式庫。

在 Web 上,開發人員需要的遠不止瀏覽器 API。因此,我們開始尋找將互操作性擴展到更多目標的方法。作為起點,我們在 2013 年引入了 dart:js,以啟用存取 JavaScript 函式庫。

1
2
3
4
5
6
7
// Short example JavaScript code to illustrate Dart/JS interop
window.myTopLevel = {
field1: 0,
method2() {
return this.field1;
}
}
1
2
3
4
5
6
7
8
9
10
11
// Access via `dart:js` (2013)
import 'dart:js' as js;

void main() {
// This line has a typo! oops :(
var object = js.context['myTopLevl'];
object['field1'] = 1;
// This call fails with a noSuchMethod because method2
// returns an int, oops
object.callMethod('method2', []).substr(1);
}

我們當時就知道 dart:js 不是我們想要的程式設計模型。您必須使用字串來存取 JavaScript 中的名稱 - 忘記編譯時找到問題,更別提代碼補全了!實作起來也很昂貴。它在大多数操作中高度依赖盒子和深度复制。因此,我們在 2014 年和 2015 年繼續 草擬 想法,直到發佈了 package:js v0.6。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Access via `package:js` (2015)
import 'package:js/js.dart';

// Magic annotations allow us to declare API signatures:
@JS()
class MyObject {
external int get field1;
external void set field1(int value);
external String method2();
}

@JS()
external MyObject get myTopLevel;

void main() {
// Access to code is less error prone: analyzer can check that
// these symbols match a declaration, and we get code-completion too!
var object = myTopLevel;
object.field1 = 1;
// But types are not checked, this unsoundly invokes substring on an int
object.method2().substring(1);
}

有了 package:js,我們終於擁有一個開放的 API,它既高效又使用者友善。您可以在抽象類別上添加一些註解,然後 voila,您就可以存取 JavaScript API。它就像魔法一樣運作,直到它失效。有很多事情您無法使用 package:js 做到:直接存取瀏覽器 API、重新命名成員、轉換、附加 Dart 邏輯,以及 更多。為了彌補,我們還發佈了 dart:js_util - 一個類似於 dart:js 的輕量級高效低階 API,作為後備方案。package:js 中的所有限制確實困擾著我們,但我們束手無策。我們需要從 Dart 語言中獲得更多幫助才能做得更好。

在那段時間裡,我們已經在為語言做最大的變更,我們從未做過 - 我們正在使 Dart 健全。具有諷刺意味的是,當我們在 2018 年使用 Dart 2.0 發佈新的類型系統時,互操作性變得 更差 了!除了早期的限制之外,使 package:js 特殊的魔法也有陰暗面 - 它無法檢查類型的有效性。這意味著我們的互操作性是我們其他健全語言中不健全的來源。

然後,我們的旅程轉變為專注於改善 Dart 和 JS 互操作,作為一項協同努力。我們秉持著明確的原則(要慣用、表達力強、可組合、精確、易於使用、務實、非魔法且完整),朝著一個以類型和靜態分派為基礎的設計前進,並挑戰 Dart 語言。此後,語言同步演進。

  • 在 2019 年,Dart 2.7 添加了靜態擴展方法。您可以將自訂 Dart 邏輯附加到 JS 互操作類別,並轉換值,例如將 JS Promise 轉換為 Dart Future,而無需使用包裝器。
  • 在 2021 年,我們發佈了 package:js v0.6.4 中的 @staticInterop。最終,JS 互操作變得足夠表達力強 - 您可以暴露以前專屬於 SDK 函式庫(如 dart:html)管理的瀏覽器 API。
  • 在 2023 年,當我們在 Dart 3.0 中放棄不健全的空安全時,我們終於看到了取得的進展,我們的設計和 @staticInterop 的工作使我們清楚地知道,我們已經準備好解決長期以來存在的健全性差距。

那一年,我們引入了編譯到 WasmGC,並利用 JS 互操作在它上面運行豐富的框架,例如 Flutter Web。這激發了對 JS 類型 的工作,以便在程式設計模型中明確定義 Dart 和 JS 的界限,並找到在 Wasm 和 JS 編譯目標中與 JS 相處的統一方法。我們還開始了 擴展類型 語言實驗 - 一項在 Dart 3.3 中推出的功能,它彌合了 Dart 語言和 JS 互操作之間的差距。多年來,JS 互操作一直存在一些行為,例如類型擦除,這些行為與 Dart 中的其他任何東西都不匹配。有了擴展類型,JS 互操作終於可以變得慣用,並在 Dart 開發工具中獲得它應有的支援。

儘管一路上經歷了許多變化和轉折,但有一件事始終保持一致:我們 Dart 社群的積極參與。社群成員率先對 dart:js 進行測試和貢獻,然後又影響了 package:js 的設計。他們編寫了工具來解決功能差距(package:js_wrapping),並嘗試透過自動生成 Dart API 來提高生產力(package:js_facade_genpackage:js_bindingspackage:typings)。每一次貢獻都幫助 Dart 的互操作設計變得更好。對於在這裡的每個人,感謝您使這段旅程如此令人興奮!

最後,我們來到了 2024 年。我們在 Dart 3.3 中發佈了 dart:js_interop,以及 package:web,這是 Dart 中最新的 JS 互操作解決方案,使 將 Flutter 編譯到 Wasm 成为可能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Access via `dart:js_interop` (2024)
import 'dart:js_interop';

// Declarations use extension types, which are very similar to package:js
// declarations. The main difference: they are statically dispatched.
extension type MyObject._(JSObject _) implements JSObject {
external int get field1;
external void set field1(int value);
external String method2();
}

@JS()
external MyObject get myTopLevel;

void main() {
var object = myTopLevel;
object.field1 = 1;
// At last, access is sound - this line fails with a type error
// when returning from method2.
object.method2().substring(1);
}
  • dart:js_interop 是一種靜態、健全、慣用、表達力強且一致的互操作形式,基於擴展類型,能夠暴露任何 JavaScript 或瀏覽器 API。
  • package:web 使用 dart:js_interop 來完成 dart:html 13 年前做的事情,但以一種在 JavaScript 和 WasmGC 中都支援的方式。

今天,我們很高興地慶祝 Dart/JS 互操作的新形式,以及它帶來的未來。了解我們的過去,我们確信這不是旅程的終點,而是我們歷史上一個令人興奮的時刻。

我們迫不及待地想看看您將用它建立什麼!


Dart 中 JS 互操作的歷史 最初發佈在 Dart 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

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

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

Flutter 登上 GDC 舞台

去年,Flutter 的休閒遊戲工具包迎來了 重大更新。本週,Flutter 在舊金山舉行的 遊戲開發者大會 (GDC) 上首次亮相,這是遊戲產業最頂尖的專業活動,致力於支持遊戲開發人員及其技能的發展。請继续阅读,了解開發者們在 Flutter 上構建遊戲的進展,以及 Flutter 遊戲開發的未来展望。

Flutter for Games hero image

Flutter 休閒遊戲工具包 發佈以來,Flutter 在遊戲開發領域取得了顯著進展。在過去的 12 個月裡,使用 Flutter 開發了超過 15,000 款新的行動遊戲。

Flutter 已證明是各種規模遊戲應用的吸引人選項,它為首次接觸遊戲開發的新手開發者(例如 Tatsuya,他利用 Flutter 在閒暇時間創造了 Tinies Merge,並在 Google Play 的獨立遊戲節 上入圍前 20 名)以及 Trivia Crack 等熱門遊戲背後的團隊(在全球擁有數億次下載量!)提供了支持。

為什麼選擇 Flutter 開發遊戲?

對於 2D 休閒遊戲,特別是介面驅動型或回合制遊戲,Flutter 提供了令人信服的优势。它是開源軟體,這意味著它消除了授權費,提供了對遊戲機制的深度控制,以及對豐富的插件生態系統的存取。Flutter 的「熱重載」功能加快了開發速度,而其優化的引擎確保了跨設備(包括瀏覽器)的順暢效能。Flutter 能夠一次構建,跨平台部署,包括 iOS、Android、網頁、桌面和 Google Play 遊戲 for PC,簡化了遊戲的覆蓋範圍。因此,如果您是遊戲開發者,不要忽視 Flutter 的潛力——嘗試用 Flutter 開發您的下一款遊戲,並幫助我們傳播這個消息!

2024 年及以後:共同打造 Flutter 遊戲開發生態系統

在 2024 年,我們致力於賦能您使用 Flutter 創造出色的休閒遊戲體驗。我們的重點將放在:

  • 簡化開發: 預計將擴展遊戲開發資源,例如範本、插件整合和深入指南,以加快您的工作流程,並釋放 Flutter 的遊戲開發潛力。
  • 擴展覆蓋範圍: 我們正在努力使 Flutter 遊戲在網頁和其他平台上的部署更加順暢,為您的作品打開新的受眾群體。
  • 為未來奠定基礎: 雖然 2D 行動遊戲支援是我們的首要重點,但我們熱衷於將 3D 功能作為 Flutter GPU 努力的一部分進行探索。您可以查看我們的一些 早期實驗,了解我們的進展。

特別感謝社群

我們對社群的堅定支持和重大貢獻表示衷心的感謝,這些貢獻推動了 Flutter 在遊戲開發領域的非凡發展。

我們對您的工作感到敬畏,並對許多傑出的貢獻表示衷心的感謝,包括:

這個列表只是社群中人才的冰山一角。對於我們無法個別提名的所有人:我們對您的貢獻深表感謝,您使 Flutter 成為一個充滿活力的遊戲開發場所!

同時,我們也向使用 Flutter 發佈遊戲的開發者們表示最衷心的感謝。您的創意和激情激勵著我們不斷突破可能的界限,使 Flutter 成為全球開發者更強大的工具。

Flutter 作為遊戲開發平台的旅程才剛剛開始。我們致力於與社群共同成長,聆聽您的回饋,并構建您所需的工具。

我們迫不及待想看看您會建立什麼!


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

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

Dart 在 2024 年 Google 暑期程式碼計畫

Google 暑期程式碼計畫 (GSoC) 致力於將來自世界各地的學生開發者引入開源軟體開發。Google 在夏季贊助學生與一個開源的指導組織合作,進行一個為期 12 週(或更長)的程式設計專案。在過去的 19 年中,超過 20,000 名貢獻者參與了 Google 暑期程式碼計畫。

我們很興奮地宣布,Dart 專案將在 2024 年 Google 暑期程式碼計畫中(第五次!)指導貢獻者。

您有興趣嗎?

現在就開始,閱讀 專案想法清單 尋找符合您的技能和興趣的專案。正式申請必須在 4 月 2 日之前提交。我們鼓勵潛在申請人提前提交草稿並要求回饋。

Dart 團隊預計有足夠的指導者,只能接受少數申請,因此請查看 [其他指導組織](https://summerofcode.withgoogle.com/programs/2024/organizations)。

如果您對 Dart 和 GSoC 有任何特定問題,請在 我們專用的郵件清單 上提出。

若要進一步了解 Google 暑期程式碼計畫,請觀看以下影片或閱讀 Google 暑期程式碼計畫的貢獻者指南

我們期待收到您的來信!


Dart 在 2024 年 Google 暑期程式碼計畫 最初發佈在 Dart 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

undefined

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

Flutter 如何促進 L+R 設計師和開發人員的合作

您可能熟悉 Global Citizen (GC),這是一個致力於消除世界貧困和幫助地球的組織。當 GC 想要重新編寫一個行動應用程式來幫助完成這項工作時,他們聯繫了我們的工作室 L+R,共同設計、開發和同步推出 Android 和 iOS 版本的應用程式。

Flutter 的靈活性、預建 Widget 清單和強大的動畫功能讓 L+R 團隊能夠實施以設計為導向的開發流程。使用 Flutter 時,我們的開發人員擁有一個空白畫布,可以將自訂設計變為現實。對於 L+R 來說,這意味著我們的設計團隊可以釋放他們的創造力,打造以使用者為中心的應用程式,這些應用程式看起來很棒,體驗也很棒。

以下是 Global Citizen 應用程式中的一些螢幕截圖:

設計流程

在開始新的客戶專案時,設計師首先在 Figma 中建立一個設計系統。這包括品牌指南 - 例如調色盤和文字排版 - 以及小的可重複使用元件。

在開始重寫 Global Citizen 應用程式時,設計團隊擴展了現有指南,以提供具有更高對比度元素的新面貌。這不僅有助於引導使用者的注意力,還改善了可訪問性。

UI 元素在 Figma 中被建立為 元件。每個元件可能具有不同的 變體,具體取決於元件類型或狀態。例如,下圖顯示了幾個按鈕變體:

在開發新的元件時,設計團隊參考了 Figma 中的 Material 和 iOS 設計套件以獲取靈感。這有助於將設計轉換為程式碼,因為這些 Figma 元件與 Material 和 Cupertino 函式庫中提供的 Widget 密切相關。

在整個設計過程中,團隊將小的可重複使用元件組合成更大的功能特定元件,然後將這些元件組合成完整的頁面。這使 Global Citizen 的產品團隊能够擴展應用程式,添加新的功能,同時保持與最初愿景的一致性。

專案範本

在 L+R,我們團隊從第一個公開發佈版本開始就一直在使用 Flutter。為了讓我們的團隊快速簡便地構建 Flutter 應用程式,我們建立了一個 開源儲存庫,它作為專案範本,為我們構建的所有 Flutter 應用程式提供了基石。

這個簡潔的專案包含一組可以自訂並用於客戶應用程式的 Widget。例如,Figma 設計檔案中所呈現的按鈕(如上所示)可以使用專案範本中的 _BaseButton 類別構建。此類別接受不同的顏色、文字樣式和內嵌,以最佳匹配為該專案設定的設計系統。

為了讓根據品牌指南輕鬆地為這些元件設定樣式,我們使用 ThemeData 類別。開發人員從 Figma 中取得顏色方案和文字樣式,並將它們對應到相應的主題屬性。

我們的團隊盡可能地利用 ThemeData 中現有的屬性,然後使用 ThemeExtension 功能完成剩餘的設定。

Flutter 的 Widget 函式庫

如上所述,專案範本包含一組可重複使用的 Widget。在開發這些 Widget 時,我們盡可能地使用 Material 和 Cupertino 函式庫中的預先存在的 Widget。在 Global Citizen 應用程式中,大約一半的元件來自 Material 或 Cupertino Widget。

例如,Material 中的 TextFieldTextFormField Widget 包含與設計相符的所有必要的自訂和功能。Material 中的 TextButton 也用於實作 _BaseButton Widget。在這種情況下,我們利用了 TextButton 現有的突出顯示功能,但我們將其包裝到一個具有擴展功能的新 Widget 中,以更好地滿足我們的需求。

擁有這些開箱即用的 UI 元件,可以輕鬆地為其設定樣式,這使得構建和維護我們的 Widget 變得更加容易。但是,有時我們的設計師會創建無法使用 Flutter 的 Material 或 Cupertino Widget 重新建立的自訂元件。在這些情況下,開發團隊會使用低階 Flutter Widget 創建我們自己的自訂 Widget。

Global Citizen 應用程式中的一個範例是進度指示器。對於這個應用程式,我們的設計師創建了一個自訂進度指示器,如以下 GIF 中所示。現有的 Material 和 Cupertino 進度指示器不適用於此設計。但是,我們的開發人員可以輕鬆地使用容器、行和列以及內在動畫創建一個全新的 Widget。

社群函式庫對於更複雜和靈活的元件也非常有用。例如,我們使用社群構建的 flushbarGlobal Citizen 應用程式中顯示警報通知。

熱重載

在將 Figma 設計轉換為 Flutter 程式碼時,設計師和開發人員的合作非常重要。設計師通常會提供回饋,導致樣式、佈局或動畫的微調。熱重載使這種合作變得更加高效。

使用熱重載,開發人員可以在與設計團隊進行螢幕共享時調整程式碼(無論是在 UX 階段、UI 概念階段、前端實施階段,甚至是在設計 QA 階段)。在螢幕共享時,設計師可以立即看到更改的結果。這使得除錯和迭代變得更快。

流暢的動畫

有助於應用程式感覺精緻的一件事是添加周到的動畫。Flutter 的動畫框架靈活而強大,但設計師可能難以理解。對我們團隊有所幫助的一件事是參考開源應用程式中的精美動畫。具體來說,我們查看了 Wonderous 應用程式 的程式碼庫。從那時起,我們利用了為 Wonderous 提供動力的開源函式庫 flutter_animate,為關鍵功能添加了簡單但豐富的動畫。

以下鏈接中的影片突出了「採取行動」流程的一部分,使用者在其中瀏覽一系列信息螢幕以了解有關問題的更多信息。

查看 action_learn_animations 影片。

使用 flutter_animate,我們能夠執行以下操作:

  • 在使用者向前移動時為步驟指示器製作動畫
  • 在頁面之間添加輕微的淡入淡出轉場
  • 在觀看影片後將「播放影片」按鈕轉換為成功勾號

總的來說,Flutter 允許設計和開發使用靈活的方法。Flutter 的預建 Widget 使創建可重複使用、可以設定樣式以匹配客戶品牌形象的 UI 元件變得更加容易。Flutter 社群在技術發展過程中發揮著重要作用,提供高品質的函式庫並擴展可用資源。Flutter 的關鍵功能(如熱重載)支持設計師和開發人員之間更好的協作。所有這些加在一起就會產生一個既美觀又實用的應用程式!

有關應用程式的更多信息,請查看 新 Global Citizen 應用程式:每日行動主義和可衡量的影響力,盡在您的口袋。您也可以 將 Global Citizen 應用程式下載 到您的行動裝置上。

L+R 是一個國際策略、設計和行動技術工作室。我們提供許多服務,例如 UX 設計、行動開發和策略咨詢。


Flutter如何促進L+R設計師和開發人員的合作 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

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

使用 Gemini API、Impeller 更新和 Windows Arm64 支援徹底改變應用程式開發

今天,我們向您介紹一個新的 Flutter 版本,Flutter 3.19。此版本帶來了新的 Gemini Dart SDK、讓開發人員能夠為 Widget 動畫加入精細控制的 Widget、透過 Impeller 更新提升渲染效能、幫助實作深層連結的工具、Windows Arm64 支援等等!

Flutter 社群持續令人印象深刻,由 168 位社群成員合併了 1429 個拉取請求,其中 43 位社群成員提交了他們的第一個 Flutter 拉取請求!

請繼續閱讀,了解 Flutter 社群為這個最新版本貢獻的所有新增功能和改進!

AI 整合

Gemini Google AI Dart SDK beta 版本發佈

Google AI Dart SDK 已發佈至 beta 版本。這讓您能夠使用 Google 最新一代 AI 模型 Gemini 來為您的 Dart 或 Flutter 應用程式建立生成式 AI 功能。現在在 pub.dev 上有一個 google_generative_ai 套件。在 這篇部落格文章 中了解如何在您的 Dart 或 Flutter 應用程式中使用 Google AI Dart SDK,或者直接跳入 Dart 快速入門

架構

捲軸改進

過去,如果您拖動兩根手指,Flutter 的捲軸速度會快兩倍。您現在可以使用 MultiTouchDragStrategy.latestPointer 來設定預設的 ScrollBehavior,以獲得與手指數量無關的捲軸行為。有關此變更的更多資訊,請參閱 遷移指南

我們還完成了針對 SingleChildScrollViewReorderableList 的錯誤修復,解決了许多報道的當機和意外行為。

在二維捲軸中,我們解決了一個問題,因此現在,如果您在捲軸進行時(無論是在任何方向)拖動或點擊,捲軸活動會按預期停止。

two_dimensional_scrollables 套件中的 TableView Widget 也已在上次版本發佈後更新,提供了更多潤色,加入了對合併單元的支援,並在 3.16 的上次穩定版本發佈後採用了 2D 基礎的更多新功能。

AnimationStyle

感謝 Flutter 社群成員 TahaTesser貢獻,Flutter 有了一個新的 AnimationStyle Widget,讓使用者可以覆蓋 Widget(例如 MaterialAppExpansionTilePopupMenuButton)中的預設動畫行為,讓開發人員能夠覆蓋動畫曲線和持續時間。

SegmentedButton.styleFrom

Flutter 社群成員 AcarFurkan 加入了一個 styleFrom 靜態實用方法,就像其他按鈕類型提供的那些方法一樣。此方法讓您能夠快速建立可以與其他分段按鈕共用或用於設定應用程式 SegmentedButtonThemeSegmentedButtonButtonStyle

Adaptive Switch

這個自適應組件在 macOS 和 iOS 上看起來和感覺起來像原生組件,而在其他地方則具有 Material Design 的外觀和感覺。 它不依賴於 Cupertino 函式庫,因此其 API 在所有平台上完全相同。

查看 自適應開關拉取請求 和在 Switch.adaptive 建構函數的 API 頁面 上的實時範例。

SemanticsProperties 無障礙識別碼

SemanticsProperties 中的一個新的無障礙識別碼為原生無障礙層級結構中的語義節點提供了一個識別碼。在 Android 上,它會在無障礙層級結構中顯示為 resource-id。在 iOS 上,這會設定 UIAccessibilityElement.accessibilityIdentifier。我們感謝社群成員 bartekpacia 做出的此項變更,這涉及了 引擎框架

提升對文字 Widget 狀態的存取權

我們在 TextFieldTextFormField 中加入了對 MaterialStatesController 的支援,以便您可以監聽 MaterialState 變更。

UndoHistory 堆疊

我們 修復了 一個問題,在該問題中,撤消/重做歷史記錄可能會在日文鍵盤上消失。您現在可以在項目被推入到 UndoHistory 堆疊之前修改它。

引擎

Impeller 進展

Android OpenGL 預覽

在 3.16 穩定版本中,我們邀請使用者在啟用 Vulkan 的 Android 設備上試用 Impeller,覆蓋了現場 77% 的 Android 設備。在過去的幾個月裡,我們將 Impeller 的 OpenGL 後端提升到與 Vulkan 後端的功能一致,例如,透過加入對 MSAA 的支援。這意味著,幾乎所有 Android 設備上的 Flutter 應用程式都應該能夠正確渲染,除了少數尚未完成的功能,例如自訂著色器和對外部紋理的完全支援,這些功能很快就會推出。

我們請求 Flutter 開發人員升級到最新的穩定版本,並提交有關在 啟用 Impeller 時觀察到的任何不足的錯誤。在此階段的回饋對確保 Impeller 在 Android 上取得成功以及我們能夠在今年晚些時候的版本中自信地將其設為預設渲染器至關重要。Android 硬體生態系統比 iOS 生態系統更加多元。因此,關於 Impeller 最有用的回饋應包括有關發生問題的特定設備和 Android 版本的詳細資訊。

此外,作為提醒,Impeller 的 Vulkan 後端在 除錯 版本中啟用了超出 Skia 所使用的額外除錯功能,這些功能具有額外的執行時間開銷。因此,重要的是要從 分析發佈 版本中提供關於 Impeller 效能的回饋。錯誤報告應包含來自 DevTools 的時間線,以及在相同設備上的 Skia 後端的比較。最後,與往常一樣,我們非常感謝包含演示問題的小型可重現測試案例的回饋。

路線圖

在渲染保真度之後,我們在 Impeller Android 預覽期的主要關注點是效能。我們持續取得了漸進的進展,但是還有一些較大的改進正在進行中。我們預計工作將利用 Vulkan 子傳遞 來大幅提高進階混合模式的效能。此外,我們還預計渲染策略從始終在 CPU 上細分每條路徑轉變為 「先模板化然後覆蓋」 方法的變化將大幅降低 Impeller 在 Android 和 iOS 上的 CPU 使用率。最後,我們預計對 高斯模糊 的全新實作將與 Skia 實作的吞吐量相符,並改進 iOS 上模糊的慣用方法。

API 改進

字形資訊

此版本包含 dart:uiParagraph 物件上的兩個新方法:getClosestGlyphInfoForOffsetgetGlyphInfoAt,它們分別回傳新的類型 GlyphInfo 的物件。查看有關新的 GlyphInfo 類型的文件。

GPU 追蹤

在金屬 (iOS、macOS、模擬器) 上的 Impeller 和啟用 Vulkan 的 Android 設備上,Flutter 引擎現在在除錯和分析版本的時間線中報告每個畫面的 GPU 時間。可以透過 DevTools 中的「GPUTracer」標題檢視 GPU 畫面時間。

請注意,由於非 Vulkan Android 設備可能會錯誤地報告其對查詢 GPU 時間的支援,因此 Impeller 的 GPU 追蹤只能在這些設備上透過在 AndroidManifest.xml 檔案中設定的旗標來啟用。

1
2
3
<meta-data
android:name="io.flutter.embedding.android.EnableOpenGLGPUTracing"
android:value="true" />

效能優化

專用常數

團隊為 Impeller 加入 對專用常數的支援。在 Impeller 的著色器中利用此功能,將 Flutter 引擎的未壓縮二進制文件大小減少了 將近 350KB

背景濾鏡加速

還有很多工作要做,但是此版本包含了一些對 Impeller 上的背景濾鏡和模糊的良好效能改進。特別是,開源貢獻者 knopp 注意到,Impeller 錯誤地請求了從螢幕上紋理讀取的能力。移除此功能 提高了包含多個背景濾鏡的場景在我們的基準測試中的效能,提高了 20-70%,具體取決於複雜程度。

此外,Impeller 不再無條件地為每個背景濾鏡儲存模板緩衝區。相反,任何會影響操作的剪輯都會被記錄,並且在為背景濾鏡還原儲存層時會被重新播放到新的模板緩衝區中。

透過此項變更,我們在執行 Vulkan 後端且啟用 Impeller 的 Pixel 7 Pro 上運行的動畫進階混合模式的基準測試結果顯示,平均 GPU 畫面時間從 55ms 提高到 16ms,並且 90% 的柵格執行緒 CPU 時間從大約 110ms 降低到 22ms。

Android

深層連結網頁驗證器

我們從開發人員那裡了解到,深層連結(將使用者從網頁 URL 引導到行動應用程式中的特定頁面)一直以來都很難實現,而且也很容易出错。因此,我們首先創建了一個驗證工具,以幫助開發人員了解哪些連結設定不正確,並提供實現指南。我們很高兴地宣布,Flutter 深層連結驗證器的早期版本現已推出!

在此早期版本中,Flutter 深層連結驗證器支援 Android 上的網頁檢查,這意味著驗證 assetlinks.json 檔案的設定。您可以打開 DevTools,點擊 深层链接 標籤,並匯入包含深層連結的 Flutter 專案。深層連結驗證器會告訴您您的網頁文件是否設定正確。您可以參閱深層連結驗證工具的 測試說明 以獲取更多資訊。

我們希望此工具是簡化深層連結實現旅程的第一步。我們將繼續努力為 iOS 上的網頁檢查和 iOS 和 Android 上的應用程式檢查提供未來的支援!

支援 Share.invoke

文字欄位和視圖上的預設 Share 按鈕過去在 Android 中缺失,但我們已在這個版本中加入了它,這是我們持續努力確保所有預設內容選單按鈕在每個平台上都可以使用的一部分。您可以在 PR #107578 中追蹤這項持續進行的工作。

原生資產功能

如果您對 Flutter 與 Flutter 程式碼中來自其他語言的函數的互操作性感興趣,您現在可以在 Android 上透過原生資產執行 FFI 調用,這是我們對支援原生資產的 持續工作 的一部分。

紋理層混合合成 (TLHC) 模式

Flutter 3.19 包含的工作現在讓 Google Maps 和文字輸入放大鏡可以在 TLHC 模式下工作,這意味著您的應用程式能夠擁有更好的效能。如果您正在使用 Google Maps,我們鼓勵您測試這些更改並給我們提供您的回饋!

這項工作不包括 Framework 或 Engine 下的提交,但您可以在 PR 5408 中查看這項工作,以及測試 THLC 的步驟。

自訂系統範圍文字選取工具列按鈕

Android 應用程式可以加入出現在所有文字選取選單中的自訂文字選取選單項目(當您長按文字時出現的選單)。Flutter 的 TextField 選取選單現在會將這些項目整合進去。

iOS

Flutter iOS 原生字體

Flutter 文字現在在 iOS 上看起來更加緊湊,也更加原生。根據 Apple 的設計指南,iOS 上的較小字體應該更加分散,以便在行動裝置上更容易閱讀,而較大的字體應該更加緊湊,以免占用太多空間。之前,我們在所有情況下都錯誤地使用了較小、更加分散的字體。現在,預設情況下,Flutter 將會為較大的文字使用緊湊字體。

DevTools

DevTools 更新

這個版本中 DevTools 的一些亮點是:

  • 在 DevTools 中加入了一個新的功能和螢幕,用於驗證 Android 上深層連結的設定。
  • 增強追蹤 選單中加入了一個用於追蹤平台通道活動的選項。這對包含 Plugin 的應用程式很有用。
  • 效能和 CPU 分析工具螢幕現在可以在沒有連接的應用程式的情況下使用。先前從 DevTools 儲存的效能資料或 CPU 分析資料可以從這些螢幕中重新載入以供查看。
  • VS Code 中的 Flutter 側邊欄現在具有在當前專案未啟用時啟用新平台的功能,側邊欄中的 DevTools 選單現在有一個選項,用於在外部瀏覽器視窗中打開 DevTools。

若要進一步了解,請查看 DevTools 的發行備註,2.29.02.30.02.31.0

桌面

Windows Arm64 支援

感謝社群成員 pbo-linaro 的出色努力,Windows 上的 Flutter 現在開始支援 Arm64 架構。此項初始支援為在 Windows Arm64 設備上原生執行的 Flutter 應用程式鋪平了道路,讓這些應用程式能夠更加高效地運作。雖然仍處於開發階段,可以在 GitHub 議題 #62597 上追蹤進度,但這項舉措表明,對於想要優化其應用程式以支援更廣泛的 Windows 設備的 Flutter 開發人員來說,這是一項很有前景的增強功能。

生態系統

必要的理由隱私說明

Flutter 現在在 iOS 上包含一個隱私說明,以符合 即將生效的 Apple 要求

Flutter 和 Dart 套件生態系統的進展

如果您錯過了,請查看 1 月份關於 Flutter 和 Dart 套件生態系統進展 部落格文章。

棄用和重大變更

停止支援 Windows 7 和 8

隨著 Flutter 的發展,我們很興奮能夠專注於最新的技術,因此在 Dart 3.3 和 Flutter 3.19 版本中停止對 Windows 7 和 8 的支援。這個調整符合 Microsoft 的策略,讓我們可以在現代作業系統上改進 Flutter。我們感謝開發人員做出的調整,並且致力於幫助您完成這個過渡。此舉為在支援的 Windows 版本上建立更安全、更高效且功能更豐富的開發環境鋪平了道路。感謝您在我們共同創新 Flutter 生態系統的過程中,對我們的理解和適應能力。

Impeller 抖動旗標

如 3.16 穩定版本發佈備註中所述,全域旗標 Paint.enableDithering 已被 移除。查看網站上的 重大變更公告 以獲取完整詳細資訊。

棄用 iOS 11

由於在調用某些網路 API 時發生 執行時間當機,Flutter 不再支援 iOS 11。這意味著針對 Flutter 3.16.6 和更高版本構建的應用程式將無法在這些設備上執行。

棄用自動渲染模式

此版本中的 重大變更 包含在 v3.16 發佈之後過期的棄用 API。若要查看所有受影響的 API,以及其他內容和遷移指南,請查看 此版本中的棄用指南Flutter fix 支援許多這些棄用,包括 IDE 中的快速修復。可以使用 dart fix 命令列工具評估和應用批量修復。

與往常一樣,非常感謝社群為 貢獻測試 - 這些測試幫助我們識別出這些重大變更。若要進一步了解,請查看 Flutter 的重大變更政策

這是第一個採用 flutter_driver 套件到棄用政策的版本,除了已經支援的套件 flutterflutter_test 之外。

結語

正如我们在本公告开头所强调的贡献者数量之多,我们是有目的的。Flutter 发展成为一个强大的、高效的工具包,这完全得益于我们令人难以置信的社群的奉献和辛勤工作。衷心感谢每一个人。

若要深入了解此版本中所取得的成果,我們邀請您查看 發行備註和變更日誌,以獲取 Flutter 3.19 中加入的內容的完整列表。

Flutter 3.19 與 Dart 3.3 現在已在 stable channel 上提供。開始使用 Flutter 的最新旅程就像運行 flutter upgrade 一樣簡單。