【文章翻譯】Implementing structs by value in Dart FFI

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

在 Dart FFI 中實作傳值結構體

Dart 2.12 版本中,我們擴充了 C 語言互通功能 Dart FFI,使其能夠 傳值結構體。本文將討論將此功能加入 Dart SDK 的過程。如果您對低階語言實作細節或平台傳值結構體的慣例感興趣,請继续阅读。

本文將討論開發 API 和找出傳值結構體功能的 ABI(應用程式二進位制介面)。在我们開發此功能(以及其他 Dart FFI 功能)的兩年中,我們發現了許多需要變更 API 的限制。ABI 的過程同樣有趣,它說明了您可以採取多種方法來確定一個難題的細節。

C/C++ 中的傳值和傳址

如果您不是每天都撰寫 C 語言程式碼,這裡快速回顧一下。假設我們在 C 語言中有以下結構體和函數:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Coord {
double x;
double y;
struct Coord* next;
};

struct Coord TranslateByValue(struct Coord c) {
c.x += 10.0;
return c;
}

void TranslateByPointer(struct Coord* c) {
c->x += 10.0;
}

然後,我們可以在一些簡單的 C 語言程式碼中使用這些函數。假設我們有一個局部變數 c1

1
Coord c1 = {10.0, 10.0, nullptr};

如果我們將 c1 傳遞給 TranslateByValue,則參數是傳值的,這使得被調用者實際上操作結構體的副本:

1
Coord c2 = TranslateByValue(c1);

這表示 c1 保持不變。

但是,如果我們使用指向包含 c1 的記憶體的指標傳址 c1,則 c1 將被就地修改:

1
TranslateByPointer(&c1);

c1.x 現在包含 20.0

API 設計之旅

最初的 Dart FFI 原型 已經支援傳遞結構體的指標。但是,我們多次重新設計了 API,以適應各種使用案例和限制。

初始設計

我們的初始設計允許在記憶體中配置結構體,將這些指標傳遞給 C 語言,並修改結構體的欄位。透過這種方法,Struct 類別擴充了 Pointer 類別:

1
2
3
4
5
6
7
8
9
10
11
class Coordinate extends Pointer {
@Double()
external double x;

@Double()
external double y;

external Pointer next;

static int get sizeOf => sizeOf<Coordinate>();
}

Dart FFI 使用者撰寫上述程式碼片段,Dart FFI 內部會產生 sizeOf 的實作以及 xynext 的 getter 和 setter 實作。

但是,兩年前我們意識到 這個設計存在一個問題。透過讓 Coordinate 擴充 Pointer,我們無法區分 CoordinateCoordinate*

區分 Coordinate 和 Coordinate*

我們 引入Struct 到 Dart FFI,並讓結構體擴充此類別:

1
2
3
4
5
6
7
8
9
class Coordinate extends Struct {
@Double()
external double x;

@Double()
external double y;

external Pointer<Coordinate> next;
}

現在,Dart 中的 Pointer<Coordinate> 代表 C 語言中的 Coordinate*,Dart 中的 Coordinate 代表 C 語言中的 Coordinate

這表示 next 欄位的類型為 Pointer<Coordinate>,這使得 @Pointer 註釋變得冗餘。因此,我們 擺脫了 Pointer 註釋

1
2
3
4
5
6
7
8
9
class Coordinate extends Struct {
@Double()
external double x;

@Double()
external double y;

external Pointer&lt;Coordinate&gt; next;
}

因為我們現在將結構體的指標表示為 Pointer 物件,所以我們開始在 Pointer 上使用 allocate 工廠:

1
final c = Pointer&lt;Coordinate&gt;.allocate();

為了存取 Pointer<Coordinate> 的欄位,我们需要一个 Coordinate 類型的物件,因為該物件具有欄位 xynext。為此,我們已經在 Pointer 上使用了 load 方法。

1
c.load&lt;Coordinate&gt;().x = 10.0;

當然,在調用 load 時必須寫 <Coordinate> 很冗長。(必須撰寫類型引數與從 Pointer<Uint8> 中載入 Dart int 相同。)我们需要在 load 上使用此類型引數的原因是向 Dart 類型系統指定此方法的返回類型。

擴充方法來救援

Dart 2.7 引入了 擴充方法。透過擴充方法,我們可以在 Pointer<T> 中的類型引數 T 上進行 模式匹配

1
2
3
extension CoordinatePointer on Pointer&lt;Coordinate&gt; {
Coordinate get ref => load();
}

在類型引數上進行模式匹配使我們能夠 擺脫調用站點的冗長

1
c.ref.y = 10.0; // ref 被模式匹配為 Coordinate 類型。

我們還可以利用擴充方法模式匹配,使 Struct<S> 的類型引數變得冗餘,將使用者結構體的定義變更為:

1
2
3
4
5
6
7
8
9
class Coordinate extends Struct {
@Double()
external double x;

@Double()
external double y;

external Pointer&lt;Coordinate&gt; next;
}

之前,類型引數 <S> 限制了 Struct 欄位 Pointer<S> addressOf。相反,我們將欄位變更為擴充 getter:

1
2
3
extension&lt;T extends Struct&gt; on T {
Pointer&lt;T&gt; get addressOf =&gt; ...
}

停止洩漏後端儲存

從 C 語言向 Dart 返回傳值結構體時,我們不希望使用 malloc 分配 C 語言記憶體來儲存結構體,因為這樣速度會很慢,並且 會讓使用者承擔釋放記憶體的負擔。因此,我們將結構體複製到 TypedData 中,Coordinate 可以使用 PointerTypedData 作為後端儲存。

但是,在第一次重新設計中引入的 addressOf 的類型為 Pointer。此類型表示它始終由 C 語言記憶體支援,但現在已不再如此。

因此,我們 棄用了 addressOf

為了優化

最後一步是要求調用各種 Dart FFI 方法,包括與結構體相關的方法,都具有 編譯時常數類型引數

1
2
3
extension on Pointer&lt;T extends NativeType&gt; {
T load&lt;T extends NativeType&gt;() =&gt; ...
}

調用方法允許我們更好地優化程式碼,並且更符合 C 語言的語義。

請注意,最後一個變更會在 Dart 2.12 中觸發棄用通知,並且此變更在 Dart 2.13 中強制執行。

ABI 探索之旅

現在 API 已經到位,下一個問題是:在傳值或返回結構體時,C 語言期望這些結構體在哪裡? 這就是所謂的應用程式二進位制介面 (ABI)。

文件

很自然地會去查閱文件。ARM 提供了 Arm 架構的程序調用標準 - ABI 2019Q1ARM 64 位架構 (AArch64) 的程序調用標準。但是,x86 和 x64 的官方文件 從網路上消失了,導致人們搜尋這些資訊,並求助於非官方的鏡像或 逆向工程

快速瀏覽文件會顯示傳值結構體的各種位置:

  • 在多個 CPU 和 FPU 暫存器中。
  • 在堆疊上。
  • 指向副本的指標。(副本位於調用者的堆疊框架上。)
  • 部分在 CPU 暫存器中,部分在堆疊上。

當在堆疊上传遞時,還有一些關於所需對齊方式以及所有未使用的 CPU 和 FPU 暫存器是否被阻塞或回填的進一步問題。

當返回傳值結構體時,結構體可以在兩個位置傳回:

  • 在多個 CPU 和 FPU 暫存器中。
  • 由被調用者寫入記憶體位置,在這種情況下,調用者傳遞指向該記憶體位置的指標。(此預留記憶體也在調用者的堆疊框架上。)

當傳遞指向結果位置的指標時,一個進一步的問題是,這是否與普通的 CPU 引數暫存器衝突。

重構 Dart FFI 編譯

初步調查就足以讓我們意識到,我們必須重新設計 Dart FFI 編譯器管道的一部分。我們曾經重複使用 Location 類型,該類型最初是用於將 Dart 程式碼編譯為組合語言的。

但是,在 Dart ABI 中,我們從不使用非字對齊的堆疊位置或同時使用兩個以上的暫存器。一個嘗試擴充 Location 類型以支援這些額外位置的實驗最終導致了一個巨大的複雜差異,因為 Location 在 Dart 虛擬機器中被大量使用。

因此,我們 替換了 Dart FFI 的編譯管道

探索原生 ABI

讓我們來探索一下 ABI。

假設我們有以下結構體和 C 函數簽名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Struct3Bytes {
int8_t a;
int16_t b;
};

struct Struct3Bytes MyFunction(
struct Struct3Bytes,
struct Struct3Bytes,
struct Struct3Bytes,
struct Struct3Bytes,
struct Struct3Bytes,
struct Struct3Bytes,
struct Struct3Bytes,
struct Struct3Bytes);

各種 ABI 如何在 MyFunction 中傳遞這些結構體?

在 x64 上的 Linux 中,有 6 個 CPU 引數暫存器。結構體足夠小,可以放入單個暫存器中,因此前 6 個引數進入 6 個 CPU 引數暫存器,最後 2 個進入堆疊。堆疊引數以 8 位元組對齊。並且,返回值也適合放入 CPU 暫存器中(更大的範例)。

1
2
3
mov     rdi, rsi
mov rdx, rcx
...

那麼,在 Windows 上會發生什麼?

完全不同。Windows 只有 4 個引數暫存器。但是,第一個暫存器用於傳遞指向要將返回值寫入的記憶體位置的指標。並且,所有引數都透過指向副本的指標傳遞,因為結構體的大小為 3 位元組,這不是 2 的冪。

1
2
3
mov     qword ptr [rsp+32], r9  ; arg8
mov qword ptr [rsp+24], r8 ; arg7
...

讓我們看看另一個範例:Linux 和 Android 上的 ARM32。假設我們有以下結構體和 C 函數簽名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Struct16BytesHomogenousFloat {
float a0;
float a1;
float a2;
float a3;
};

struct Struct16BytesHomogenousFloat MyFunction(
struct Struct16BytesHomogenousFloat,
struct Struct16BytesHomogenousFloat,
struct Struct16BytesHomogenousFloat,
struct Struct16BytesHomogenousFloat,
struct Struct16BytesHomogenousFloat,
struct Struct16BytesHomogenousFloat,
struct Struct16BytesHomogenousFloat,
struct Struct16BytesHomogenousFloat,
struct Struct16BytesHomogenousFloat,
struct Struct16BytesHomogenousFloat);

這些特定類型的結構體稱為同類複合類型,因為它們只包含相同的元素。並且,具有最多 4 個成員的同類浮點數的處理方式與普通結構體不同。在這種情況下,Linux 對結構體中的各個浮點數使用浮點暫存器

1
2
3
vmov            s1, r2
vmov s0, r1
...

在 Android 上,使用 SoftFP 而不是 HardFP。這表示浮點數在整數暫存器中傳遞,而不是在浮點暫存器中傳遞。此外,我們正在傳遞一個指向結果的指標。這導致了一種 奇怪的情況,其中第一個引數部分在整數暫存器中傳遞,部分在堆疊上传遞。

1
2
3
str             r2, [r0, #4]
str r1, [r0]
...

如果其中任何一個出錯,都可能導致執行階段出現區段錯誤。因此,在每個硬體和作業系統組合上正確處理 ABI 的所有邊角案例至關重要。

透過 godbolt.org 探索

由於文件非常簡潔,我們透過編譯器資源管理器 godbolt.org 找出許多邊角案例。編譯器資源管理器 並排顯示 C 程式碼和編譯後的組合語言:

A screenshot of godbolt.com showing that the assembly code for sizeof(Struct3Bytes) is returning 3 in the return register.

上述螢幕截圖顯示,在 Windows x86 上,sizeof(Struct3Bytes) 為 3 位元組,因為 3 被移入返回暫存器 eax 中。

當我們 稍微變更 結構體時,我們可以檢查大小是否仍然為 3:

1
2
3
4
struct Struct3Bytes {
uint8_t a;
int16_t b;
};

大小不是 3:mov eax, 4。由於 int16 必須為 2 位元組對齊,因此結構體必須為 2 位元組對齊。這表示,在配置這些結構體的陣列時,每個結構體之後有一個 1 位元組的填補,以確保下一個結構體為 2 位元組對齊。因此,此結構體在本機 ABI 中為 4 位元組。

透過產生的測試探索

不幸的是,編譯器資源管理器不支援 MacOS 和 iOS。因此,為了使手動探索更有效率(並且為此功能提供一個良好且龐大的測試套件),我們編寫了一個測試產生器。

主要思想是以這樣的方式產生測試:如果測試崩潰,則可以使用 GDB 來查看問題所在。

使發現 segmentation fault 時更容易看到問題的一種方法是使所有引數都具有可預測且易於識別的值。例如,以下測試使用連續的整數,以便可以在暫存器和堆疊上輕鬆發現這些整數值:

1
2
3
4
5
6
7
8
9
struct Struct8Bytes {
int16_t a;
int16_t b;
int32_t c;
};

int main() {
struct Struct8Bytes s = {1, 2, 3};
}

另一種簡化尋找問題的方法是在各處加入列印語句。例如,如果我們在從 Dart 轉換到 C 語言的過程中沒有遇到 segmentation fault,但我們設法損壞了所有引數,則列印引數會有所幫助:

1
2
3
4
5
6
7
8
struct Struct1ByteInt8 {
int8_t a0;
};

void MyFunction(struct Struct1ByteInt8 a0, int8_t a1) {
printf("%" PRId8 "\n", a0.a0);
printf("%" PRId8 "\n", a1);
}

加入測試就像在 組態檔案 中加入函數類型一樣簡單。快速加入測試的能力導致了一個 龐大的測試套件

果然,這個測試套件在本機 ABI 中發現了另一個奇怪的案例 - 這次是在 iOS-ARM64 上。在 ARM64 上的 iOS 上,堆疊上的非結構體引數不是以字大小對齊,而是以其自身的大小對齊。結構體以字大小對齊,除非結構體是只包含浮點數的同類結構體,則 它以浮點數的大小對齊

總結

到此結束了我們對 API 設計和 ABI 探索的旅程。透過良好的測試套件和全面的程式碼審查,我們在 2020 年 12 月在 master 分支上 加入了對在 Dart FFI 中傳遞傳值結構體的支援,並且它在 Dart 2.12 中可用!如果您有興趣使用 Dart FFI,則可以從 dart.dev 上的 C 語言互通文件 開始。如果您對 API 設計和 ABI 探索有任何問題或意見,請在下方留言。我們很想聽到您的聲音!

感謝 Dart 語言團隊和(其餘的)Dart 虛擬機器團隊對此 Dart FFI 功能的貢獻,也感謝 Kathy Walrath 和 Michael Thomsen 對此部落格文章的塑造!


在 Dart FFI 中實作傳值結構體 最初發佈在 Dart 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章翻譯】Announcing Dart 2.13

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

宣佈 Dart 2.13

A graphic showing highlights of what’s in this post: type aliases, better FFI, null safety, Docker support

作者:Kevin Moore 和 Michael Thomsen

今天,我們宣佈推出 Dart 2.13,其中包含類型別名——目前我們第二個最受歡迎的語言功能。Dart 2.13 還包含改進的 Dart FFI 和更好的效能,並且我們有新的 Dart 官方 Docker 映像。這篇文章提供了在 2.12 中引入的空安全功能的更新,討論了新的 2.13 功能,有一些關於 Docker 和 Google Cloud 支援 Dart 後端的令人興奮的消息,並預覽了一些您可以在未來版本中看到的一些變更。

空安全更新

我們在 3 月的 Dart 2.12 版本中推出了健全的空安全。空安全是 Dart 最新的主要生產力功能,旨在幫助您避免空錯誤——這類錯誤通常難以發現。隨著該版本的推出,我們鼓勵套件發佈者開始將 pub.dev 上的共享套件遷移到空安全。

我們非常高興地看到空安全被採用的速度如此之快!在發佈後的短短幾個月內,pub.dev 上前 500 個最受歡迎的套件中,有 93% 已經支援空安全。我們要向所有套件開發人員表示衷心的感謝,感謝他們如此迅速地完成了這項工作,並幫助整個生態系統向前發展!

有了這麼多支援空安全的套件,您很有可能可以開始將您的應用程式遷移到使用空安全。第一步是使用 dart pub outdated 檢查您的應用程式的相依性。有關詳細資訊,請參閱空安全遷移指南。我們還更改了我們的 dart createflutter create 範本,以便它們現在預設在新應用程式和套件中啟用空安全。

宣佈類型別名

類型別名是 2.13 語言中的一項新功能。它擴展了我們早期的支援,允許建立函數類型的類型別名,但不支援任何其他類型。這個備受期待的功能在語言問題追蹤器中被評為第二高

使用類型別名,您可以為任何現有類型建立新名稱,然後可以在任何可以使用原始類型的地方使用該名稱。您並不是真的在定義一個新類型,只是引入了一個簡寫別名。別名甚至通過類型相等測試:

1
2
3
4
5
typedef Integer = int;

void main() {
print(int == Integer); // true
}

那麼您可以使用類型別名做什麼呢?一種常見的用法是為類型指定一個更短或更具描述性的名稱,使您的程式碼更具可讀性和可維護性。

一個很好的例子是使用 JSON(感謝 GitHub 使用者 Levi-Lesches 提供的這個例子)。在這裡,我們可以定義一個新的類型別名 Json,它將 JSON 文件描述為從 String 鍵到任何值(使用動態類型)的映射。然後,我們可以在定義我們的 fromJson 命名建構函數和 json getter 時使用該 Json 類型別名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef Json = Map<String, dynamic>;

class User {
final String name;
final int age;

User.fromJson(Json json) :
name = json['name'],
age = json['age'];

Json get json => {
'name': name,
'age': age,
};
}

您也可以在命名類別的類型別名上調用建構函數,因此以下是完全合法的:

1
2
3
4
main() {
var j = Json();
j['name'] = 'Michael';
}

透過使用類型別名為複雜類型命名,您可以使讀者更容易理解程式碼的不變性。例如,以下程式碼定義了一個類型別名來描述包含泛型類型 X 的鍵和 List<X> 類型值的映射。透過為類型指定一個帶有單個類型參數的名稱,映射的規則結構對於程式碼的讀者來說變得更加明顯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef MapToList<X> = Map<X, List<X>>;

void main() {
MapToList<int> m = {};
m[7] = [7]; // OK
m[8] = [2, 2, 2]; // OK
for (var x in m.keys) {
print('$x --> ${m[x]}');
}
}

=>

7 --> [7]
8 --> [2, 2, 2]

如果您嘗試使用不匹配的類型,您將收到分析錯誤:

1
2
3
4
5
m[42] = ['The', 'meaning', 'of', 'life'];

=>

元素類型 'String' 無法賦值給列表類型 'int'

您甚至可以在重命名公共程式庫中的類別時使用類型別名。想像一下,您在公共程式庫中有一個現有的類別 PoorlyNamedClass,您想將其重命名為 BetterNamedClass。如果您只是重命名類別,那麼您的 API 客戶將會收到突然的編譯錯誤。使用類型別名,您可以繼續進行重命名,但然後為舊的類別名稱定義一個新的類型別名,然後為舊名稱添加一個 @Deprecated 註釋。使用 PoorlyNamedClass 時會導致警告,但會繼續編譯並像以前一樣工作,讓使用者有時間升級他們的程式碼。

以下是您如何在 mylibrary.dart 檔案中實作 BetterNamedClass 並棄用 PoorlyNamedClass

1
2
3
4
class BetterNamedClass {...}

@Deprecated('請改用 BetterNamedClass')
typedef PoorlyNamedClass = BetterNamedClass;

以下是當有人嘗試使用 PoorlyNamedClass 時會發生的情況:

1
2
3
4
5
6
7
8
9
import 'mylibrary.dart';

void main() {
PoorlyNamedClass p;
}

=>

'PoorlyNamedClass' 已棄用,不應使用。請改用 BetterNamedClass。

類型別名功能從 Dart 2.13 開始提供。要啟用它,請將 pubspec 中的 Dart SDK 下限約束設定為至少 2.13:

1
2
environment:
sdk: ">=2.13.0 <3.0.0"

由於語言版本控制,此功能向後相容。SDK 約束低於 2.13 的套件可以安全地引用在 2.13 套件中定義的類型別名,即使 2.13 之前的套件無法定義自己的類型別名。

Dart 2.13 FFI 變更

我們在 Dart FFI(我們用於調用 C 程式碼的互操作機制)中也有一些新功能。

首先,FFI 現在支援具有內聯陣列的結構體(#35763)。考慮一個具有內聯陣列的 C 結構體,如下所示:

1
2
3
struct MyStruct {
uint8_t arr[8];
}

您現在可以直接在 Dart 中包裝它,使用 Array 的類型參數指定元素類型:

1
2
3
4
class StructInlineArray extends Struct {
@Array(8)
external Array<Uint8> arr;
}

其次,FFI 現在支援壓縮結構體(#38158)。通常,結構體在記憶體中的佈局方式使得成員位於更容易被 CPU 存取的地址邊界中。使用壓縮結構體,可以省略一些填充以降低整體記憶體消耗,通常以特定於平台的方式。使用新的 @Packed(<alignment>) 註釋,您可以輕鬆指定填充。例如,以下程式碼建立了一個在記憶體中具有 4 位元組對齊的結構體:

1
2
3
4
5
6
7
8
9
10
11
12
@Packed(4)
class TASKDIALOGCONFIG extends Struct {
@Uint32()
external int cbSize;
@IntPtr()
external int hwndParent;
@IntPtr()
external int hInstance;
@Uint32()
external int dwFlags;
...
}

Dart 2.13 效能變更

我們正在繼續努力減少 Dart 程式碼的應用程式大小和記憶體佔用。在大型 Flutter 應用程式中,表示 AOT 編譯的 Dart 程式的中繼資料的內部結構可能會佔用相當大的記憶體塊。這些中繼資料大多數都用於啟用熱重新載入、互動式除錯和人類可讀的堆疊追蹤格式等功能——這些功能在已部署的應用程式中從未使用過。在過去的一年中,我們一直在重構 Dart 原生運行時,以盡可能消除這種開銷。其中一些改進適用於以發佈模式構建的所有 Flutter 應用程式,但有些改進需要您透過使用 --split-debug-info 旗標將除錯資訊從 AOT 編譯的應用程式中分離出來,以放棄人類可讀的堆疊追蹤。

Dart 2.13 包含許多變更,這些變更在使用 --split-debug-info 時顯著減少了程式中繼資料佔用的空間。以 Flutter Gallery 應用程式為例。在 Android 上,發行版 APK 的大小為 112.4 MB(包含除錯資訊),106.7 MB(不包含除錯資訊)(總體減少了 5%)。此 APK 包含許多資產。僅查看 APK 內的程式碼中繼資料,它從 Dart 2.12 的 5.7 MB 減少到 Dart 2.13 的 3.7 MB(減少了 35%)。

如果應用程式大小和記憶體佔用對您很重要,請考慮使用 --split-debug-info 旗標省略除錯資訊。請注意,這樣做的話,您需要使用符號化命令使堆疊追蹤再次可讀。

官方 Docker 支援和 Google Cloud 上的 Dart

Dart 現在可以作為Docker 官方映像使用。雖然 Dart 多年來一直提供 Docker 映像,但這些新的 Dart 映像已通過 Docker 的測試和驗證,符合最佳實務。它們還支援提前 (AOT) 編譯,這可以顯著減少構建的容器的大小,並可以提高在容器環境(例如Cloud Run)中的部署速度。

雖然 Dart 仍然專注於讓 Flutter 等應用程式框架能夠在每個螢幕上驅動漂亮的像素,但我們意識到,大多數使用者體驗背後至少有一個託管服務。透過簡化使用 Dart 構建後端服務,我們支援完整的堆疊體驗,讓開發人員可以使用與他們用於在前段驅動 widget 的相同語言和業務邏輯將他們的應用程式擴展到雲端。

一般來說,將 Dart 用於 Flutter 應用程式後端尤其適合 Google 的託管無伺服器平台 Cloud Run 的簡潔性和可擴展性。這包括縮放為零,這意味著當您的後端沒有處理任何請求時,您不會產生成本。我們與 Google Cloud 團隊合作,提供Dart 函數框架,這是一個套件、工具和範例的集合,可以輕鬆編寫 Dart 函數來部署,而不是完整的伺服器來處理 HTTP 請求和 CloudEvents。

請查看我們的Google Cloud 文件以開始使用。

關於接下來的一些話

我們已經在為即將發佈的版本進行一些令人興奮的變更。與往常一樣,您可以使用語言漏斗追蹤器來關注我們的進度。

我們正在研究的一個領域是 Dart 和 Flutter 的一套新的規範 lint。Lint 是一種配置 Dart 靜態分析的強大方法,但由於有數百個可能的 lint 可以開啟或關閉,因此很難決定選擇什麼。我們目前正在定義兩套規範的 lint,我們將在 Dart 和 Flutter 專案中預設應用這些 lint。我們預計這將在下一個穩定版本中預設啟用。如果您想要預覽,請查看兩個套件 lintsflutter_lints

最後,如果您進行 Dart VM 運行時的深度嵌入,請注意,我們計劃棄用現有的機制。我們將用一個基於 Dart FFI 的更快、更靈活的模型來替換它(請參閱追蹤問題 #45451)。

Dart 2.13 現已推出

Dart 2.13,包含類型別名和改進的 FFI,現已在 Dart 2.13Flutter 2.2 SDK 中推出。

如果您一直在等待您的相依性遷移到空安全,您可能需要再次檢查,使用 dart pub outdated。前 500 個最受歡迎的套件中,有 93% 已經遷移,您很有可能已經沒有障礙了。我們還要向已經遷移的開發人員表示衷心的感謝!

我們很樂意聽到您對本部落格文章中討論的新功能和變更的體驗。請在下方留言或在 Twitter 上聯繫我們 @dart_lang


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

【文章翻譯】What’s new in Flutter 2.2

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

Flutter 2.2 新功能

Flutter 2.2 版本專注於完善和優化,包括 iOS 效能改進、Android 延遲組件、更新的 Flutter Web 服務工作者等等!

今天,我們正式發佈 Flutter 2.2。您可以透過切換到 stable channel 並升級您目前的 Flutter 安裝,或前往 flutter.dev/docs/get-started 開始新的安裝來取得它。

儘管距離 Flutter 2 發佈只有幾個月,但我們在 2.2 版本中有很多改進要分享。此版本合併了框架、引擎和 Plugin 儲存庫中的 2,456 個 PR 並解決了 3,105 個問題。特別感謝廣大的 Flutter 社群,他們提供了大量的 PR 和 PR 審查,包括貢獻最多 PR(17 個)的 Abhishek01039 和審查最多 PR(9 個)的 xu-baolin,他們為 Flutter 2.2 做出了貢獻。感謝所有貢獻者幫助將 Flutter 2.2 帶入 stable channel。沒有您的協助,我們無法做到。

隨著每個新的 Flutter 版本穩定發佈,都會有一系列新的更新,無論是效能增強、新功能還是錯誤修復。此外,發佈版本還包含一些尚未準備好投入生產使用的功能,但我們希望您能夠驗證它們是否按您的預期運作。最後,每個新版本都帶有一組相關的工具更新和來自更廣泛的 Flutter 社群的更新。老實說,如今每次 Flutter 新版本發佈時都會發生很多事情,我們不可能在一篇部落格文章中涵蓋所有內容,但我們會盡力突出重點。

穩定版 Flutter 2.2 更新

此版本在 Flutter 2 的基礎上涵蓋了廣泛的改進,包括 Android、iOS 和 Web 的更新,新的 Material 圖示、文字處理更新、捲軸行為和 TextSpan Widget 的滑鼠游標支援,以及有關如何從單一原始碼庫最佳支援多種平台的新指南。所有這些功能現在都可以在 stable 版本中使用,您可以用於生產應用程式。所有這些功能都建立在新的 Dart 版本之上。

Dart 2.13

Flutter 2.2 與 Dart 2.13 版本一起發佈。除了其他功能外,此 Dart 更新還包含一個新的類型別名功能,使您能夠為類型和函數建立別名:

類型別名使您能夠為長而複雜的類型提供簡潔的短名稱,它還讓您以非破壞性的方式重新命名類別。Dart 2.13 中還有更多新功能;在 Dart 2.13 發佈公告 中查看詳細資訊。

Flutter Web 更新

Flutter 最新的穩定平台 Web 在此版本中得到了改進。

首先,我們使用新的服務工作者載入機制優化了快取行為,並修復了 main.dart.js 的重複下載問題。在以前的 Flutter Web 版本中,服務工作者在向使用者提供應用程式的舊版本時,會在背景中下載應用程式的更新。下載更新後,使用者必須刷新瀏覽器頁面幾次才能看到這些更改。從 Flutter 2.2 開始,當新的服務工作者偵測到更改時,使用者將等待更新下載才能使用應用程式,但他們將看到更新,而無需再次手動刷新頁面。

若要啟用此更改,您需要重新產生 Flutter 應用程式的 index.html 檔案。若要執行此操作,請儲存您的修改,刪除 index.html 檔案,然後在您的專案目錄中執行 flutter create . 以重新建立它。

我們還對兩個 Web 渲染器都進行了改進。對於 HTML,我們加入了對 字體功能 的支援,以啟用設定 FontFeature 以及使用畫布 API 渲染文字,以便在懸停時文字顯示在正確的位置。對於 HTML 和 CanvasKit,我們加入了對著色器遮罩和 computeLineMetrics 的支援,解決了 Flutter Web 和行動應用程式之間的奇偶性差距。例如,開發人員現在可以使用 不透明遮罩 透過著色器遮罩執行淡出轉場,並像在行動應用程式中一樣使用 computeLineMetrics

對於 Flutter Web 以及 Flutter 本身,無障碍功能是我們的首要任務之一。按照設計,Flutter 透過建立 SemanticsNode 樹來實作無障碍功能。當 Flutter Web 應用程式使用者啟用無障碍功能時,框架會產生一個與 RenderObject DOM 樹平行的 DOM 樹,並將語義屬性轉換為 Aira。在此版本中,我們改進了語義節點位置,以在使用轉換時縮短行動和桌面 Web 應用程式之間的差距,這意味著當 Widget 透過轉換進行樣式設定時,焦點框應正確顯示在元素之上。若要實際查看此功能,請查看 Victor Tsaran 的這個影片,他在影片中使用 VoiceOver 和 Flutter Gallery App:

我們還透過命令列標誌在配置文件和發行模式下公開了語義節點除錯樹,以幫助開發人員透過視覺化為其 Web 應用程式建立的語義節點來除錯無障碍功能。

若要為您自己的 Flutter Web 應用程式啟用此功能,請執行以下操作:

1
2
$ flutter run -d chrome --profile 
--dart-define=FLUTTER_WEB_DEBUG_SHOW_SEMANTICS=true

啟用該標誌後,您將可以在 Widget 上方看到語義節點,這讓您能夠除錯並查看語義元素是否放置在不應放置的位置。如果您發現此類範例,請隨時 提交錯誤報告

雖然我們在支援一組核心無障碍功能方面取得了重大進展,但我們將繼續改進無障碍功能支援。在 2.2 穩定版本之後,在 master 和 dev channel 上提供的版本中,我們加入了一個 API,讓開發人員可以透過程式設計方式 自動啟用應用程式的無障碍功能,並且正在修復 使用螢幕閱讀器與 Tab 鍵 的問題。

最後,但同樣重要的是,最新版本的 Flutter DevTools 現在支援 Flutter Web 應用程式的佈局瀏覽器。

此更新為您提供了與行動和桌面應用程式相同的佈局除錯工具。

iOS 頁面轉場和增量安裝

對於 iOS,在此版本中,我們 在 Cupertino 中使頁面轉場更加流暢,透過將渲染動畫畫面的時間縮短 75%,而在低端手機上可能會縮短更多時間。我們不僅尋找最終使用者效能的改進;我們始終在尋找改進開發效能的方法。

在此版本中,我們 在開發過程中實作了增量 iOS 安裝。在我們的基準測試中,我們發現安裝更新版本的 iOS 應用程式所需的時間減少了 40%,這減少了測試應用程式更改時的週轉時間。

使用 Flutter 建立平台適應性應用程式

隨著 Flutter 在 stable 版本中擴展到支援更多平台,考慮支援不同外觀尺寸(例如行動裝置、平板電腦和桌面),以及不同輸入類型(觸控 vs. 滑鼠 + 鍵盤)和具有不同慣例的平台(例如導航抽屜 vs. 系統選單用於導航)的應用程式變得很有用。我們將能夠根據不同目標平台的細節進行調整的應用程式稱為「平台適應性」應用程式。

對於在建立平台適應性應用程式時需要牢記的考量,我們建議您參考 Kevin Moore 的 建立平台適應性應用程式 議程。若要更詳細地查看,請查看 flutter.dev 上的平台適應性應用程式指南

最後,對於根據這些原則為多個平台編寫的範例應用程式,我們建議您使用 gSkinner 的 FlokkFlutter Folio 應用程式。您可以下載 FlokkFolio 的程式碼,以及從各個應用程式商店下載 FlokkFolio,或直接從瀏覽器中執行它們。另一個很棒的範例是是用於建立指南本身的應用程式:

Flutter 平台適應性應用程式指南的 UX 部分是基於新的 Material 大型螢幕指南。Material 團隊的這項新指南包括重新設計多個主要佈局文章,以及更新多個元件和更新的設計工具包,所有這些都是針對大螢幕進行的。

Flutter 的目標一直是啟用不僅可以在多個平台上執行,而且在所有目標平台上都能 出色 執行的應用程式。Flutter 擁有您需要的支援,不僅可以將應用程式定位到多個平台,而且還打算為每個平台的螢幕大小、輸入模式和慣例 量身打造 您的應用程式。

更多 Material 圖示

談到 Material 指南,在此版本中,我們不僅加入了一個,而是 兩個 獨立的 PR,為 Flutter 加入新的 Material 圖示,包括 Dash 本人的圖示!

這些更新使您的應用程式可用的 Material 圖示總數增加到 7,000 多個。如果您在這麼多豐富的圖示中找不到您要尋找的圖示(誰會呢?),您可以在 fonts.google.com/icons 根據類別和名稱搜尋。

根據名稱搜尋 Flutter Material 圖示

找到完美的圖示後,新的「Flutter」標籤會顯示如何使用它,或者您可以僅下載該圖示以作為獨立資產用於您的應用程式中。將 Dash 加入您的 Flutter 應用程式中從未如此簡單。

改進的文字處理

隨著我們繼續改進 Flutter 以支援每個平台的細節,我們繼續推進到在行動外觀尺寸上並不那麼重要,但在桌面外觀尺寸上很重要的新領域。其中一個領域是文字處理。在此版本中,我們已開始重構處理文字輸入的方式,以啟用取消鍵盤輸入(當鍵盤輸入透過 Widget 階層級別傳播時)、以及透過引入能夠完全自訂與文字動作相關的鍵盤輸入的能力。

能夠取消鍵盤輸入讓 Flutter 能夠實作使用空格鍵和箭頭鍵而不會觸發捲軸事件的功能,為您的最終使用者提供更直观的體驗。您可以在自己的應用程式中使用相同的功能來處理鍵盤輸入,然後再輸入到父 Widget 中。另一個範例是,在此版本中,您可以在 Flutter 應用程式中的 TextField 和按鈕之間按 Tab 鍵,並且它可以正常運作:

Flutter 2.2 可以取消鍵盤輸入從 Widget 階層級別向上傳播,例如允許 TAB 將焦點從 TextField 切換

自訂文字動作允許您執行諸如在 TextField 中對 Enter 鍵進行特殊處理之類的操作;例如,您可以觸發在聊天客戶端中發送訊息,同時仍然允許透過 Ctrl+Enter 插入換行符。這些相同的文字動作 讓 Flutter 本身能夠提供不同的鍵盤輸入,以將文字編輯的行為與主機作業系統本身相匹配,例如 Windows 和 Linux 上的 Ctrl+C,但 macOS 上的 Cmd+C。

舉例來說,以下範例覆蓋了預設的左箭頭動作,並為退格鍵和刪除鍵提供了一個新的動作:

不可饒恕的 TextField 範例,其中按下左箭頭或 ESC 鍵會清除文字

我們還有更多工作要做,但我們正在努力為您提供完整的文字編輯動作。我們的目標是,當 Flutter 桌面到達 stable 版本時,您的使用者將無法區分在 Flutter 應用程式和主機作業系統上的任何其他應用程式中編輯文字。

自動捲軸行為

作為我們不斷努力讓 Flutter 應用程式像每個平台上最好的應用程式一樣運作的一部分,我們在此版本中再次查看了捲軸條。在實際顯示捲軸條方面,Android 和 iOS 都是一樣的;預設情況下,它們不會顯示捲軸條。另一方面,對於桌面應用程式,當內容比容器大時,通常會自動顯示捲軸條,這需要您加入 Scrollbar 父 Widget。若

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

【文章翻譯】Announcing Flutter 2.2 at Google I/O 2021

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

宣布 Flutter 2.2:領先的多平台 UI 工具組的持續發展

在今天的 Google I/O 上,我們宣布了 [Flutter 2.2](https://flutter.dev/docs/whats-new),這是我們開源工具組的最新版本,可以用於從單一平台建立適用於任何設備的精美應用程式。Flutter 2.2 是迄今為止最佳的 Flutter 版本,它提供了更新,使開發人員比以往更容易透過應用程式內購買、付款和廣告來獲利;連接到擴展應用程式以支援新功能的雲端服務和 API;以及工具和語言功能,讓開發人員能夠消除一整類錯誤,提高應用程式效能,並縮減套件大小。

建立在 Flutter 2 的基礎之上

Flutter 2.2 建立在 Flutter 2 的基礎之上,Flutter 2 將 Flutter 從行動端擴展到包含 Web、桌面和嵌入式使用。它專為環境計算世界而設計,在環境計算世界中,使用者擁有各種不同的設備和外形尺寸,並希望獲得跨越他們日常生活的連貫體驗。有了 Flutter 2.2,企業、新創公司和企業家都可以建立高品質的解決方案,這些方案可以發揮其可尋址市場的全部潛力,讓創意靈感(而非目標平台)成為唯一的限制因素。

Flutter 現在是跨平台開發最受歡迎的框架。

最近的一項行動開發人員研究突顯了 Flutter 的成長。分析公司 [SlashData](https://www.slashdata.co/) 的 [2021 年行動開發人員人口預測](https://www.slashdata.co/reports/?category=mobile-desktop) 顯示,Flutter 現在是跨平台開發最受歡迎的框架,45% 的開發人員選擇使用它,從 2020 年第一季到 2021 年第一季成長了 47%。我們自己的數據證實了這種向 Flutter 的轉變;在過去 30 天中,Play 商店中超過八分之一的新應用程式都是使用 Flutter 建立的。

在 I/O 上,我們分享了現在僅在 Play 商店中就有超過 20 萬個應用程式是使用 Flutter 建立的。這些應用程式來自像騰訊這樣的公司,其 [微信](https://apps.apple.com/us/app/wechat/id414478124) 通訊應用程式在 iOS 和 Android 上擁有超過 12 億使用者;[字節跳動](https://www.bytedance.com/en/products/),TikTok 的創始人,他們現在已經使用 Flutter 建立了 70 個不同的應用程式;以及其他來自包括 [BMW](https://www.press.bmwgroup.com/global/article/detail/T0328610EN/the-my-bmw-app:-new-features-and-tech-insights-for-march-2021?language=en)、[SHEIN](https://apps.apple.com/app/id878577184)、[Grab](https://apps.apple.com/app/id647268330) 和 [DiDi](https://play.google.com/store/apps/details?id=com.xiaojukeji.didi.global.customer&hl=None) 等公司的應用程式。當然,Flutter 不僅僅被大型企業使用。一些最具創新性的應用程式來自您可能從未聽說過的名字:例如,[Wombo](https://play.google.com/store/apps/details?id=com.womboai.wombo&hl=None),病毒式的唱歌自拍應用程式;[Fastic](https://play.google.com/store/apps/details?id=de.fastic.app&hl=None),間歇性禁食應用程式;以及 [Kite](https://play.google.com/store/apps/details?id=com.zerodha.kite3&hl=None),一個精美的投資交易應用程式。

介紹 Flutter 2.2

Flutter 2.2 版本重點改進開發體驗,幫助您為客戶提供更可靠、更高效能的應用程式。

聲明性空安全現在是新專案的預設值。空安全增加了對空引用異常的保護,讓開發人員能夠在程式碼中表達非空類型。由於 Dart 的實作是 *聲明性的*,編譯器可以在執行時消除空檢查,為您的應用程式提供更高的效能。生態系統迅速做出回應,大約有 5,000 個套件已經更新以支援空安全。

此版本中也包含許多效能改進:對於 Web 應用程式,我們提供了使用服務工作者的背景快取;對於 Android 應用程式,Flutter 支援延遲組成部分;對於 iOS,我們一直在努力開發工具以預先編譯著色器,以消除或減少第一次運行的卡頓。我們還為 DevTools 套件添加了一些新功能,這些功能可以幫助您了解應用程式中的記憶體分配方式,以及對第三方工具擴展的支援。

此外,我們一直在著手幾個重要的潤色領域,例如改進 Web 目標的可存取性。

我們的發展不僅限於 Flutter 的核心。我們還與其他 Google 團隊合作,幫助將 Flutter 整合到我們更廣泛的開發人員堆疊中。特別是,我們繼續建立值得信賴的服務,幫助開發人員負責任地在應用程式中獲利。此版本中更新了我們的 [新廣告 SDK](https://developers.google.com/admob/flutter/quick-start),包含空安全和對適應性橫幅格式的支援。我們還引入了一個由 Google Pay 團隊合作開發的 [新的付款 Plugin](http://pub.dev/packages/pay),它讓您可以在 iOS 和 Android 上對實體商品進行付款。此外,我們更新了 [應用程式內購買 Plugin](https://pub.dev/packages/in_app_purchase),以及相應的 [Codelab](https://codelabs.developers.google.com/codelabs/flutter-in-app-purchases)。

作為驅動 Flutter 的「秘訣」,[Dart](https://dart.dev) 也在此版本中進行了更新。Dart 2.13 擴展了對原生互操作性的支援,包括在 FFI 中支援陣列和封包結構。它還包括對類型別名的支援,這提高了可讀性,並為某些重構情境提供了一個平緩的路徑。我們繼續為更廣泛的生態系統添加整合,包括一個 Dart [GitHub Action](https://github.com/marketplace/actions/setup-dart-sdk) 和一個經過優化的 [Docker 官方映像](https://hub.docker.com/_/dart),專為雲端部署業務邏輯而設計。

超越 Google 專案

雖然 Google 仍然是 Flutter 專案的主要貢獻者,但我們很高兴看到圍繞 Flutter 的更廣泛生態系統的成長。

在最近幾個月中,Flutter 的成長表現特別突出,它被用於越來越多的平台和作業系統。在 Flutter Engage 上,我們宣布了 [豐田將把 Flutter 带入他們的下一代車輛信息娛樂系統](https://medium.com/googleplaydev/seamless-multi-platform-app-development-with-flutter-ea0e8003b0f9#f53d)。上個月,Canonical 發佈了他們的首個 [帶有整合 Flutter 支援的 Ubuntu](https://ubuntu.com/blog/ubuntu-21-04-is-here),具有 Snap 整合和對 Wayland 的支援。

兩位新合作夥伴證明了這個不斷發展的生態系統。[三星正在將 Flutter 移植到 Tizen](https://github.com/flutter-tizen/flutter-tizen), 開源儲存庫可供其他人貢獻。而 [索尼正在帶頭努力為嵌入式 Linux 提供解決方案](https://github.com/sony/flutter-embedded-linux)。

設計師也從這個專案的開源性質中受益,[Adobe 宣布了其更新的 XD to Flutter 外掛](https://medium.com/adobetech/announcing-xd-to-flutter-v2-0-82d09f3909a7)。Adobe XD 為設計師提供了一個很好的方式來實驗和迭代。現在,隨著增強的 Flutter 支援,設計師和開發人員可以在相同的資產上進行合作,讓好點子更快地投入生產。

最後,微軟繼續與我們合作;除了 Surface 團隊一直在努力使用 Flutter 建立可折疊體驗之外,本週還推出了為 Windows 10 建立的 [Flutter 對 UWP 應用程式的 Alpha 版支援](https://flutter.dev/desktop#windows-uwp)。我們很高兴看到更多利用 Flutter 中內建的平台適應來提供跨行動、桌面、Web 和其他平台的優質體驗的應用程式。

建立出色的體驗

最重要的是,我們建立 Flutter 是為了幫助開發人員建立出色的體驗。我們熱衷於這樣的想法:應用程式開發可以變得更好;我們可以透過消除傳統的障礙來讓您接觸到您的受眾,來賦予您力量。

我們喜歡看到您如何將 Flutter 運用於實際工作中。一個例子來自美國退伍軍人事務部的一個專案。下面的影片顯示了他們的 Flutter 應用程式如何幫助他們為患有創傷後壓力症候群的士兵提供康復服務。

Google I/O 上有 [關於 Flutter 的各種研討會、簡報和隨選課程](https://events.google.com/io/program/content?4=topic_flutter),我們很高興能與大家分享我們的成果。不要忘記查看我們用 Flutter 建立的有趣的 [照片亭 Web 應用程式](https://photobooth.flutter.dev),它讓您能夠與我們的 Dash 吉祥物和她的朋友們合照自拍!


在 Google I/O 2021 上宣布 Flutter 2.2 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

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

【文章翻譯】How It’s Made: I/O Photo Booth

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

深入探討使用 Flutter 和 Firebase 建立 Web 應用程式

我們(Very Good Ventures 的團隊)與 Google 合作,為今年的 Google I/O 帶來了互動式體驗:一個 照片亭!您可以與知名的 Google 吉祥物合影: Flutter 的 Dash、Android Jetpack、Chrome 的 Dino 和 Firebase 的 Sparky,並使用貼紙裝飾照片,包括派對帽、披薩、時髦眼鏡等等。最後,您可以將照片分享到社交媒體,並下載它們以更新您的活動個人檔案照片!

Flutter 的 Dash、Firebase 的 Sparky、Android Jetpack 和 Chrome 的 Dino

我們使用 網頁上的 FlutterFirebase 建立了 I/O 照片亭。由於 Flutter 現在支援 Web 應用程式,我們認為這將是一個很好的方法,讓今年的虛擬 Google I/O 全球的與會者都能輕鬆地使用這個應用程式。Flutter 的 Web 支援消除了必須從應用程式商店安裝應用程式的障礙,也讓您能夠選擇在您偏愛的設備上運行它:行動裝置、桌面或平板電腦。這讓任何有瀏覽器和設備的人都能使用 I/O 照片亭,而無需下載。

儘管 I/O 照片亭被設計為 Web 體驗,但所有程式碼都是使用平台無關的架構撰寫的。當相机插件等元素的原生支援在各自的平台上可用時,同一程式碼可以在所有平台(桌面、Web 和行動裝置)上運行。

使用 Flutter 建立虛擬照片亭

為 Web 建立 Flutter 相機 Plugin

第一個挑戰是為網頁上的 Flutter 建立一個相機 Plugin。最初,我們聯繫了 Baseflow 的團隊,因為他們維護著現有的開源 Flutter 相機 Plugin。雖然 Baseflow 致力於為 iOS 和 Android 建立頂級的相機 Plugin 支援,但我們很樂意使用 聯合 Plugin 方法 並行開發 Plugin 的 Web 支援。我們盡可能地遵循官方 Plugin 介面,以便在 Plugin 準備就緒時能夠將它合併回官方 Plugin 中。

我們識別了兩個對在 Flutter 中建立 I/O 照片亭相機體驗至關重要的 API。

  1. 初始化相機: 應用程式首先需要存取您的設備相機。在桌面裝置上,這可能是網路攝影機,而在行動裝置上,我們選擇的是前置相機。我們還提供了 1080p 的預期解析度,以根據您的設備最大限度地提高相機品質。
  2. 拍攝照片: 我們使用了內建的 HtmlElementView,它使用平台視圖將原生 Web 元素渲染為 Flutter Widget。在此專案中,我們將 VideoElement 渲染為一個原生 HTML 元素,這就是您在拍攝照片之前在螢幕上看到的內容。我們使用 CanvasElement,它被渲染為另一個 HTML 元素。這使我們可以在您點擊拍攝照片按鈕時從媒體流中捕獲圖片。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Future<CameraImage> takePicture() async {
final videoWidth = videoElement.videoWidth;
final videoHeight = videoElement.videoHeight;
final canvas = html.CanvasElement(
width: videoWidth,
height: videoHeight,
);
canvas.context2D
..translate(videoWidth, 0)
..scale(-1, 1)
..drawImageScaled(videoElement, 0, 0, videoWidth, videoHeight);
final blob = await canvas.toBlob();
return CameraImage(
data: html.Url.createObjectUrl(blob),
width: videoWidth,
height: videoHeight,
);
}

相機權限

在我們讓 Flutter 相機 Plugin 在 Web 上運作之後,我們建立了一個抽象層來顯示不同的 UI,具體取決於相機權限。例如,在等待您允許或拒絕瀏覽器使用相機的權限,或者沒有可用的相機可以存取時,我們可以顯示一個說明訊息。

1
2
3
4
5
6
7
8
9
Camera(
controller: _controller,
placeholder: (_) => const SizedBox(),
preview: (context, preview) => PhotoboothPreview(
preview: preview,
onSnapPressed: _onSnapPressed,
),
error: (context, error) => PhotoboothError(error: error),
)

在此抽象層中,placeholder 返回初始 UI,因為應用程式正在等待您授予對相機的權限。preview 在您授予權限後返回 UI,並提供相機的即時視訊流。error builder 允許我們在發生錯誤時捕獲錯誤,並渲染對應的錯誤訊息。

鏡像照片

我們的下一個挑戰是鏡像照片。如果我們直接使用相機拍攝照片,您看到的將不是您在鏡子中看到的那樣。 一些設備有設定可以精確處理這一點,因此,如果您使用前置相機拍攝照片,您會在拍攝時看到鏡像版本。

在我們的第一種方法中,我們嘗試捕獲預設的相機視圖,然後在 y 軸上應用 180 度變換。這似乎有效,但後來我們遇到了 一個問題,Flutter 有時會覆蓋變換,導致視訊恢復為未鏡像的版本。

在 Flutter 團隊的幫助下,我們透過將 VideoElement 包裹在 DivElement 中並更新 VideoElement 以填充 DivElement 的寬度和高度解決了這個問題。這使我們能夠將鏡像應用到 video element 上,而不會讓 Flutter 覆蓋變換效果,因為父元素是 div。這種方法為我們提供了所需的鏡像相機視圖!

Un-mirrored view
Mirrored view

堅持嚴格的縱橫比

對於大型螢幕,強制執行 4:3 的嚴格縱橫比,對於小型螢幕,強制執行 3:4 的縱橫比比想像的要難!在整個 Web 應用程式的設計中強制執行這個比率,以及確保在您將照片分享到社交媒體時,照片看起來完美無瑕,這一點非常重要。這是一項具有挑戰性的任務,因為設備上內建相機的縱橫比差異很大。

為了強制執行嚴格的縱橫比,應用程式首先使用 JavaScript getUserMedia API 請求設備相機所能提供的最大解析度。然後,我們將此 API 饋送到 VideoElement 流中,這就是您在相機視圖中看到的內容(當然是鏡像的)。我們還應用了一個 object-fit CSS 屬性,以確保 video element 覆蓋其父容器。這使用 Flutter 中內建的 AspectRatio Widget 設定縱橫比。結果,相機不會對顯示的縱橫比做出任何假設;它始終返回支援的最大解析度,然後符合 Flutter 提供的約束(在本例中為 4:3 或 3:4)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
final orientation = MediaQuery.of(context).orientation;
final aspectRatio = orientation == Orientation.portrait
? PhotoboothAspectRatio.portrait
: PhotoboothAspectRatio.landscape;
return Scaffold(
body: _PhotoboothBackground(
aspectRatio: aspectRatio,
child: Camera(
controller: _controller,
placeholder: (_) => const SizedBox(),
preview: (context, preview) => PhotoboothPreview(
preview: preview,
onSnapPressed: () => _onSnapPressed(
aspectRatio: aspectRatio,
),
),
error: (context, error) => PhotoboothError(error: error),
),
),
);

使用拖放添加朋友和貼紙

I/O 照片亭體驗中的一個重要部分是與您最喜歡的 Google 朋友合影並添加道具。您可以將朋友和道具拖放到照片中,以及調整它們的大小和旋轉它們,直到得到一張您喜歡的圖片。您會注意到,在將朋友添加到螢幕時,您可以拖動和調整它們的大小。朋友們也有動畫 - 精靈表來實現這個效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
for (final character in state.characters)
DraggableResizable(
canTransform: character.id == state.selectedAssetId,
onUpdate: (update) {
context.read<PhotoboothBloc>().add(
PhotoCharacterDragged(
character: character,
update: update,
),
);
},
child: _AnimatedCharacter(name: character.asset.name),
),

為了調整物件的大小,我們建立了一個可拖動、可調整大小的 Widget,它可以包裝在任何 Flutter Widget 周圍,在本例中是朋友和道具。此 Widget 使用 LayoutBuilder 來根據視窗的約束處理 Widget 的縮放。在內部,我們使用 GestureDetectors 來接入 onScaleStartonScaleUpdateonScaleEnd。這些回調提供關於手勢的詳細資訊,這些資訊需要反映您對朋友和道具所做的更改。

Transform Widget 和 4D 矩陣變換根據您使用多個 GestureDetectors 報告的各種手勢,處理朋友和道具的縮放和旋轉。

1
2
3
4
5
6
7
Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..scale(scale)
..rotateZ(angle),
child: _DraggablePoint(...),
)

最後,我們建立了一個單獨的套件來確定您的設備是否支援觸控輸入。可拖動、可調整大小的 Widget 根據觸控功能進行適應。在支援觸控輸入的設備上,可調整大小的錨點和旋轉圖示不可見,因為您可以捏合和拖動來直接操作圖片,而在沒有觸控輸入的設備(例如您的桌面設備)上,會添加錨點和旋轉圖示以適應點擊和拖動。

優先考慮網頁上的 Flutter

使用 Flutter 進行以 Web 為中心的開發

這是我們使用 Flutter 建立的第一個 Web 專案,它與行動應用程式具有不同的特性。

我們需要確保應用程式對任何設備上的任何瀏覽器都 響應式和自適應。也就是說,我們必須確保 I/O 照片亭會根據瀏覽器大小進行縮放,並且能夠處理行動裝置和 Web 輸入。我們通過以下幾種方式實現了這一點:

  • 響應式調整大小: 您應該能夠將瀏覽器調整到所需的尺寸,並且 UI 應相應地調整。如果您的瀏覽器視窗處於縱向模式,則相機將從具有 4:3 縱橫比的橫向視圖翻轉到具有 3:4 縱橫比的縱向視圖。
  • 響應式設計: 桌面瀏覽器的設計將 Dash、Android Jetpack、Dino 和 Sparky 顯示在右側,而行動裝置則顯示在頂部。桌面設計還使用相機右側的抽屜,而行動裝置則使用 BottomSheet 類別。
  • 自適應輸入: 如果您從桌面設備存取 I/O 照片亭,則滑鼠點擊被視為輸入,如果您使用的是平板電腦或手機,則使用觸控輸入。這在調整貼紙大小並將它們放置在照片中時尤其重要。行動裝置支援捏合和拖動,而桌面裝置支援點擊和拖動。

可擴展架構

我們還將建立可擴展行動應用程式的方法應用於此應用程式。我們從一個堅實的基礎開始 I/O 照片亭,包括健全的空安全、國際化,以及從第一次提交開始的 100% 單元測試和 Widget 測試覆蓋率。我們使用 flutter_bloc 進行狀態管理,因為它允許輕鬆地測試業務邏輯並觀察應用程式中的所有狀態更改。這對開發人員日誌和追蹤性特別有用,因為我們可以精確地看到從一個狀態到另一個狀態的變化,並更快地隔離問題。

我們還實作了功能驅動的單一儲存庫結構。例如,貼紙、分享和實時相機預覽是在它們自己的資料夾中實作的,每個資料夾包含其各自的 UI 組件和業務邏輯。它們與外部相依項整合,例如相機 Plugin,這些相依項存在於 packages 子目錄中。這種架構使我們的團隊能夠並行處理多個功能,而不會中斷他人的工作,最大限度地減少了合併衝突,並使我們能夠有效地重複使用程式碼。例如,UI 組件函式庫是一個單獨的套件,稱為 photobooth_ui,相機 Plugin 也是單獨的。

透過將組件分成獨立的套件,我們可以提取和開源不與此特定專案相關聯的單獨組件。即使是 UI 組件函式庫套件也可以開源給 Flutter 社群,類似於 MaterialCupertino 組件函式庫。

Firebase + Flutter = 完美的搭配

Firebase 驗證、儲存、託管等等

照片亭利用 Firebase 生態系統進行各種後端整合。firebase_auth 包 支援在應用程式啟動時匿名登入使用者。每個工作階段都使用 Firebase Auth 建立一個具有唯一 ID 的匿名使用者。

這在您到達分享頁面時會發揮作用。您可以下載照片以儲存為您的個人檔案照片,或者您可以直接分享到社交媒體。如果您下載了照片,它將儲存在您的設備上。如果您分享了照片,我們會使用 firebase_storage 包 將照片儲存在 Firebase 中,以便我們以後能夠檢索它,為社交媒體貼文填充內容。

我們在 Firebase 儲存桶上定義了 Firebase 安全規則,以使照片在建立後不可變。這可以防止其他使用者修改或刪除此儲存桶中的照片。此外,我們使用 Google Cloud 提供的 Object Lifecycle Management,定義一個規則,刪除所有 30 天前的物件,但您可以按照應用程式中概述的說明請求更快地刪除您的照片。

此應用程式還使用 Firebase 托管 來快速且安全地託管 Web 應用程式。action-hosting-deploy GitHub Action 允許我們根據目標分支自動將部署到 Firebase 托管。當我們將更改合併到 main 分支時,action 會觸發一個工作流程,將應用程式的開發風味構建並部署到 Firebase 托管。同樣,當我們將更改合併到 release 分支時,action 會觸發生產部署。GitHub Action 與 Firebase 托管的組合使我們的團隊能夠快速迭代,並始終擁有最新構建的預覽。

最後,我們使用 Firebase Performance Monitoring 監控關鍵的 Web 效能指標。

使用 Cloud Functions 與社交媒體互動

在生成您的社交媒體貼文之前,我們首先要確保照片看起來完美無瑕。最終的圖片包括一個紀念 I/O 照片亭的精美框架,並裁剪為 4:3 或 3:4 的縱橫比,使其在社交媒體貼文中看起來很棒。

我們使用 OffscreenCanvas API 或 CanvasElement 作為 polyfill 來組合原始照片和包含您的朋友和道具的圖層,並生成一張您可以下載的單張圖片。image_compositor 包 處理此處理步驟。

然後,我們利用 Firebase 強大的 Cloud Functions 來幫助將照片分享到社交媒體。當您點擊分享按鈕時,您將被帶到所選平台上的新標籤頁,其中包含預先填寫的貼文。貼文包含一個 URL,該 URL 會重新導向到我們編寫的 Cloud Function。當瀏覽器分析 URL 時,它會檢測 Cloud Function 生成的動態元資訊。此資訊允許瀏覽器在您的社交媒體貼文中顯示照片的精美預覽圖片,以及指向分享頁面的連結,您的關注者可以在其中查看照片並導航回到 I/O 照片亭應用程式以拍攝自己的照片。

1
2
3
4
5
6
7
8
function renderSharePage(imageFileName: string, baseUrl: string): string {
const context = Object.assign({}, BaseHTMLContext, {
appUrl: baseUrl,
shareUrl: `${baseUrl}/share/${imageFileName}`,
shareImageUrl: bucketPathForFile(`${UPLOAD_PATH}/${imageFileName}`),
});
return renderTemplate(shareTmpl, context);
}

最終的產品看起來像是這樣:

有關如何在 Flutter 專案中使用 Firebase 的更多資訊,請查看此 codelab

最終產品

這個專案很好地體現了以 Web 為中心的應用程式建立方法。我們驚訝地發現,與我們使用 Flutter 建立行動應用程式的體驗相比,建立此 Web 應用程式的流程是如此相似。我們必須考慮視窗大小、響應式、觸控與滑鼠輸入、圖片加載時間、瀏覽器相容性以及建立 Web 應用程式時需要考慮的所有其他事项。但是,我們仍然使用相同的模式、架構和編碼標準編寫 Flutter 程式碼。在為 Web 建立應用程式時,我們感到賓至如歸。Flutter 套件的工具和不斷發展的生態系統,包括 Firebase 工具套件,讓 I/O 照片亭成為可能。

Very Good Ventures 團隊,他們參與了 I/O 照片亭的開發

我們已將所有程式碼開源。在 GitHub 上查看 photo_booth 專案,並在 Facebook 和 Twitter 上使用 #IOPhotoBooth 向我們展示您的照片!


製作過程:I/O 照片亭 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章翻譯】Which factors affected users’ decisions to adopt Flutter? — Q1 2021 user survey results

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

哪些因素影響了使用者採用 Flutter 的決定?— 2021 年第一季使用者調查結果

Flutter 團隊在此分享本季使用者調查的結果!本季,我們在 3 月 5 日至 11 日期間,歷時 7 天,收集了超過 8,000 份回覆。這個季度的調查計畫旨在以結構化的形式聽取您的意見,以便 Flutter 團隊能夠專注於對使用者最重要的方面。先前調查的結果也發佈在 Medium 上。

使用者滿意度

在本季度的調查中,8,652 位受訪者中有 92% 表示對 Flutter 感到滿意(55% 非常滿意,37% 有點滿意),與上一季度的結果類似。

本季值得注意的是,89% 的受訪者也對 Dart 語言感到滿意,創下歷史新高。這個消息讓團隊非常鼓舞,因為這個百分比在 2019 年起步時只有 80%。Dart 團隊做了很多工作來改善開發人員使用 Dart 語言的體驗,以達成此滿意度,包括 健全的空安全。我們很高興分享這個調查結果,並感謝那些承認該語言獨特優勢的人。

對 Dart 感到滿意的使用者比例從 2019 年第三季度的 80% 增加到 2021 年第一季度的 89%,增長了 9%。

哪些因素影響了使用者採用 Flutter 的決定?

Flutter 團隊希望了解不同的因素如何影響使用者為新專案採用 Flutter 的決定。目標是找到團隊應該投入的領域,以減輕使用者的疑慮,並讓使用者感到滿意。此外,透過分享結果,我們希望您也能使用這些資訊來尋找貢獻 Flutter 社群的方式,或在您的組織或社群中宣傳 Flutter。

由於這次調查的目標是現有的 Flutter 使用者,因此我們詢問了使用者最近評估 Flutter 用於其下一個新專案的經驗。我們了解到 75% 的受訪者在過去 3 個月內評估了 Flutter 與其他技術的比較,其中 67% 是出於商業原因,其次是學習(17%)和興趣(14%)。

首先,我們詢問什麼因素讓使用者傾向於採用 Flutter。如以下圖表所示,使用者傾向於採用 Flutter 的主要原因是 Flutter 支援以下功能,按重要性排序:

  1. 單一程式碼庫。
  2. 現成的 UI Widget。
  3. 熱重載。
讓開發人員傾向於為其下一個專案採用 Flutter 的因素

接下來,我們詢問什麼因素讓使用者傾向於不採用 Flutter。使用者最擔心的因素是以下這些,按重要性排序:

  1. Google 對 Flutter 的承諾。
  2. Flutter 對於他們正在構建的應用程式規模的成熟度。
  3. Flutter 生態系統中提供的套件和插件。
讓開發人員不願為其下一個專案採用 Flutter 的因素

從這些結果中,團隊了解到使用者擔心 Flutter 是否會長期存在。這是一個合理的擔憂,因為您將您的職業或應用程式與我們的框架綁定在一起。因此,我們想再次提到內部採用的規模。在 Flutter Engage 上,我們分享了 Google 有超過 30 個團隊正在使用 Flutter 構建任務關鍵的應用程式。這些團隊包括 Google Pay、Google Ads、Nest Hub 和 Stadia,這些團隊擁有數千名開發人員致力於 Flutter 開發。今年,Flutter 將再次在 Google I/O 上佔據重要地位。請留意我們最新的公告,並從 5 月 5 日開始註冊 I/O 網站 上列出的 Flutter 會議。

我們還想指出,Flutter 的成長不僅僅是因為 Google 的支援——Google 外部的貢獻者比 Google 內部的貢獻者更多。全球充滿活力的開源開發人員社群正在為我們共同的成功做出貢獻!看看其他大型和小型貢獻者,例如 Toyota 或 Ubuntu。

接下來的擔憂是 Flutter 的成熟度,包括它周圍的生態系統。我們正在投資開發套件和插件生態系統,並致力於核心框架的穩定性和品質。隨著 Flutter 2 的發布和不斷增長的生態系統支援它,我們看到人們在這裡的觀點正在改善。現在網頁支援已穩定,桌面支援也處於 Beta 階段,我們在即將到來的版本中主要將會以完善框架和提升您使用 Flutter 可以達到的生產力為主題。同時,您可以查看 Flutter 網站的 展示區,了解 BMW、eBay、Nubank、騰訊、Square 和 Sonos 等主要品牌如何使用 Flutter 構建應用程式。

我們還收集了數千條關於各種主題的原始評論,以回應開放式問題。我們至少舉辦了三個不同的研討會,Flutter 子團隊的工程師在這些研討會上聚在一起閱讀和消化關於各種主題的評論,包括生態系統、開發人員工具等等。研討會期間產生的想法將作為團隊未來幾個季度目標的輸入。

Flutter 團隊用於根據調查中的評論生成和組織有關開發人員工具的想法的線上便利貼

社群貢獻者也可以做一些事情。我們從使用者評論中了解到,許多 Flutter 開發人員仍然希望擁有品質更高的套件,並提供更好的維護,儘管他們承認 Flutter 的生態系統隨著時間推移而成熟。有很多關於多媒體套件(如音訊和影片播放器)和藍牙套件的提及,這些套件需要更好的支援。我們歡迎您貢獻這些領域,擴展覆蓋範圍,例如向現有套件提交 PR,因為 Flutter 團隊努力提供高品質的 第一方套件

基於雲端的服務和產品

Flutter 團隊知道一些 Flutter 使用者對雲端服務有興趣,但團隊並不清楚使用者在尋找哪種類型的雲端解決方案。感謝調查,我們了解到 40% 的開發人員使用基於雲端的服務,27% 的開發人員計畫在他們的 Flutter 專案中使用基於雲端的服務。

對於那些已經使用基於雲端的服務的人來說,最受歡迎的雲端解決方案是 Firebase (82%),其次是 Google Cloud Platform (GCP) (34%) 和 Amazon Web Services (AWS) (25%)。

使用者表示,他們選擇 Firebase 和 GCP 的原因是它們易於與 Flutter 一起使用。許多選擇 AWS 和 Azure 的使用者表示,這是因為他們已經熟悉這些平台。

Flutter 團隊與 Firebase 和 GCP 密切合作。我們將繼續支援 FlutterFire Plugin,團隊也與 GCP 團隊合作,使其更容易與 Flutter 一起使用。

接下來要做什麼?

Flutter UXR 團隊將每季度進行調查。您可以在 flutter.dev 上,Flutter IDE Plugin 中,或 Twitter 上 @FlutterDev 找到公告。您也可以註冊即將到來的 UX 研究,參與其他研究。請繼續分享您的想法,因為團隊正在尋找重要問題的答案。

再次感謝所有參與此次調查並提供寶貴意見的人。我們閱讀了每則意見回饋,並將其銘記於心。我們的目標是打造一款您喜愛的產品,我們感謝您的時間和努力幫助我們!


哪些因素影響了使用者採用 Flutter 的決定?— 2021 年第一季使用者調查結果 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章翻譯】AngularDart, Flutter, and the web: Spring update

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

AngularDart、Flutter 與網路:春季更新

兩個月前,我們發布了 Flutter 網路支援的首個穩定版本。這對整個客戶端開發來說是一個重要的里程碑,它結合了 Flutter 建立的 UI 框架、Dart 工業級強度的 JavaScript 工具鏈以及 Web 平台的底層能力,在行動裝置和瀏覽器之間提供一致性。

自該版本發布以來,我們很高興看到開發人員開始在網路上使用 Flutter。即使在這個早期階段,超過 10% 的 flutter build 指令都以網路為目標,這表明 Web 應用程式已經開始使用 Flutter 部署。在 Google 內部,許多團隊開始依賴 Flutter 的網路支援。而且我們自己在 Flutter 開發中也大量使用它,包括 DartPadDevTools(我們的效能和除錯工具套件)。

幾年前,當我們第一次開始為 Flutter 加入網路支援時,我們希望它能帶來好的結果,但這仍然感覺像是一個登月計劃。雖然還有很多工作要做,但我們相信 Flutter 的網路支援有潛力以高品質的解決方案填補一個重要的利基市場。因此,我們正在調整 Dart 開源網路工具的投資,以專注於 Flutter。

在改變我們的重點的同時,我們也正在縮減我們在 AngularDart 上的外部工作,這是一個從最初的 Angular 專案演變而來的獨立網路框架。六個月前,我們宣布了 AngularDart 的路線圖,其中包括發布一個長期穩定的更新(AngularDart 6)和支援空安全,但優先考慮 AngularDart 工程工作在主要的內部客戶,包括 Google 廣告Google Play 管理中心。雖然 AngularDart 框架本身相對容易開源,但 Google 的構建和發布系統是 高度專業化的,導致顯著的差異,使得同步內部和外部版本變得非常困難。

專注於大多數 AngularDart 使用者可以幫助我們充分利用我們才華橫溢的工程資源,我們希望每個人都能因此受益。對於廣告和 Play 管理中心等專案,我們可以更直接地滿足他們的需求,而無需擔心外部影響;對於外部使用者,我們可以將所有精力投入到為您提供一個高品質的 Flutter 產品,該產品適用於網路、桌面、行動和嵌入式場景。

我們可以將所有精力投入到為您提供一個高品質的 Flutter 產品,該產品適用於網路、桌面、行動和嵌入式場景。

在過去的幾個月中,我們一直在與 AngularDart 最大的外部使用者交談,以幫助他們制定自己的路線圖。對於某些人來說,Flutter 的網路支援是一個完美的選擇;其他人則對 AngularDart 的穩定性和成熟度感到滿意;還有一些人正在利用這個機會過渡到 Angular(Google 流行的基於 TypeScript 的網路框架)。

Angular 本身繼續快速增長。在過去的一年中,每週下載量增長了 60% 以上,並且近 兩百萬個公共 GitHub 儲存庫依賴於它。它的語法對於今天使用 AngularDart 的人來說會很熟悉,對於尋找現代網路框架的開發人員來說,這是一個合乎邏輯的選擇。

對於想要使用 Dart 的開發人員,我們認為 Flutter 的網路支援將是一個令人興奮的選擇。例如,PropOps——一家房地產領域的科技初創公司——使用 Flutter 建立業務線應用程式取得了巨大的成功,如下面的螢幕截圖所示:

PropOps 網路應用程式的螢幕截圖
PropOps:一個基於雲端的房產管理工具,使用 Flutter 的網路支援構建。

在接下來的幾週內,我們將發布一個穩定的 AngularDart 外部版本,支援空安全;屆時,它將轉入維護模式。如果一個承諾的聯盟或社群團體想要接管該專案的領導權並繼續發展它,我們很樂意收到他們的來信。

我們正在全力推進 Flutter 的網路支援以及 Angular 本身:請關注下週 Google I/O 活動 的更多資訊!


AngularDart、Flutter 與網路:春季更新 最初發佈在 Dart 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

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

【文章翻譯】Announcing XD to Flutter v2.0!

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

宣布 XD to Flutter v2.0!

* 為設計師提供通往生產 Flutter 程式碼的快速路徑 *

XD to Flutter

Flutter 是 Google 的開源 UI 工具包,可使用單一程式碼庫為行動、桌面、網頁和嵌入式設備構建精美的應用程式。在過去的幾年中,它已成為想要為多個平台構建應用程式的開發人員的領先選擇;但設計師需要一個視覺工具來製作原型和建立 Flutter UI,而不是手工編寫 Dart 原始碼。這就是 XD to Flutter 誕生的原因!

從插件的第一次預覽發布至今已將近一年,我們一直都在不斷改進和完善它,進行了一些小更新,在去年夏天發布了重大的 v1.0 版本,現在又發布了 v2.0 版本,以配合 Flutter 2 的發布!

等等,XD to Flutter 是什麼?

這個名字可能已經洩露了答案,但 XD to Flutter 插件是一個功能強大、易於使用的工具,可以將您在 Adobe XD 中設計的精美作品匯出為乾淨、功能性的 Flutter 程式碼。您可以從設計中複製特定視覺元素的程式碼,匯出可重複使用的 Widget,甚至可以匯出完整的視圖。

這意味著,使用 XD to Flutter 插件,您可以透過點擊按鈕,讓您的設計在幾乎任何設備上運行。它不會為您編寫整個應用程式,但會為您提供一個良好的開端。

XD to Flutter 由 gskinner 與 Adobe 合作開發,並作為 Adobe XD 本身的插件發布,因此您可以將它用於您正在建立的任何現有 Adobe XD 設計。

太棒了!那麼有什麼新功能?

XD to Flutter 的初始版本對輸出設計中的所有不同視覺元素提供了良好的支援 - 向量圖形、圖片、富文字、背景模糊、混合模式、陰影等 - 但結果可能是靜態且缺乏彈性的。

雖然它對於擷取圖示或文字樣式很方便,但我們希望它能做更多事情!XD 使設計師能夠使用響應式佈局、可捲軸區域、堆疊和網格等工具來建立動態 UI;我們希望插件支援所有這些功能,而 v2.0 版本讓我們取得了很大進展。

響應式調整大小

XD to Flutter 支援 XD 的響應式佈局功能,讓您可以在其封閉的父元素中「固定」元素,並精確控制它們的調整大小方式。

Adobe XD 中的響應式設計
Flutter 中的響應式調整大小

這是透過在開源的 adobe_xd 套件中使用自訂的 Pinned 佈局 Widget 來實現的,開發人員可以直接在他們的專案中利用它。

Pinned Widget 程式碼範例

堆疊和捲軸組

「堆疊」和捲軸組提供了在 Adobe XD 中以動態方式佈置螢幕上內容的新方法。XD 中的堆疊讓您可以在水平或垂直列表中排列許多不同的元素,元素之間的間距各不相同;它們更類似於 Flutter 中的 Flex Widget,而不是它們的同名 Stack Widget。

捲軸組預測性地讓您可以在設計中直接定義一個區域,以垂直或水平方式捲軸更大的內容組。

XD to Flutter v2.0 支援這兩種功能,將它們轉換為常見的 Flutter Widget(ColumnRowSingleChildScrollView)。您甚至可以將堆疊放在捲軸組中,輕鬆建立一個捲軸項目列表。

XD 中的堆疊和捲軸組(左)以及 Flutter 中的堆疊和捲軸組(右)

填補和背景元素

另一個新功能是背景元素,讓您可以將視覺元素指定為組的背景。這可以與填補配對,將背景的邊緣與內容分開。

Flutter 匯出使用 Stack Widget 將背景元素放置在內容後面,內容被放置在 Padding Widget 中。

XD 中的填補和背景(左)以及 Flutter 中的填補和背景(右)

Flutter 2 和空安全性

上面描述的佈局功能使 UI 具有更強的響應性,這與 Flutter 2 對桌面和網頁等外觀形式的支援增強相得益彰。

Flutter 2 還引入了健全的空安全性 - 一個語言功能,可以幫助開發人員在空值問題引發應用程式問題之前捕獲它們。XD to Flutter v2.0 包括一個新的設定「匯出空安全程式碼」,確保生成的程式碼是面向未來的。

「匯出空安全程式碼」設定和輸出

聽起來很棒!我該如何開始?

無論您是使用它來複製複雜漸變的程式碼,還是匯出完全響應式、參數化、互動式 Widget,加入已經使用 XD to Flutter 插件的數千名創意專業人士都很簡單。

您可以透過從 Adobe XD 的「插件」選單中選擇「瀏覽插件…」並搜尋「Flutter」(奇怪的是,搜尋「XD to Flutter」不起作用)來安裝它,或者只訪問 adobe.com/go/xd_to_flutter

安裝好之後,從插件面板中打開 XD to Flutter 面板,然後點擊「需要幫助?」連結以查看 插件文件

Flutter 2 是框架向前邁出的令人興奮的一步,重點是構建可以在幾乎任何地方運行的精美應用程式。在 gskinner,我們很高興能與 Adobe 和 Google 合作,確保 XD to Flutter 繼續使將迷人的設計忠實地轉換為工作產品的過程變得更加容易。

敬請期待插件的更多令人興奮的更新!


宣布 XD to Flutter v2.0! 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章翻譯】Announcing Dart support for GitHub Actions

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

宣布 GitHub Actions 支援 Dart

GitHub Actions 是一個熱門的 GitHub 儲存庫持續整合 (CI) 服務。今天,我們宣布由 Dart 團隊提供的官方 setup-dart action。此 action 支援下載、安裝和設定 Dart SDK,讓您可以在 GitHub Actions 上輕鬆執行 Dart 建置和測試工作。

如果您為 pub.dev 製作 Dart 套件,我們鼓勵您使用 GitHub Actions 為您的儲存庫啟用 CI 測試,以便為您(和套件使用者)提供有關套件是否健康的訊號。GitHub Actions 上的 CI 工作對 公開儲存庫 是免費的。

介紹 setup-dart

如果您有一個包含 Dart 應用程式的 GitHub 儲存庫,您只需點擊幾下即可使用 GitHub Actions 和 setup-dart action 啟用 CI 測試。這裡我們有一個名為 myapp 的新 GitHub 儲存庫,其中包含使用 Dart 工具產生的小型應用程式的起點,透過執行
dart create --template console-full myapp

接下來,打開 GitHub 網頁 UI,然後點擊 Actions 標籤:

在介紹下方,您應該會看到一個建議 Dart 儲存庫工作流程的區段:

點擊 設定此工作流程 按鈕,您將進入 GitHub UI,以新增一個新的 .github/workflow/dart.yml 檔案。這是一個 YAML 檔案,定義了要在儲存庫中執行的 GitHub Actions 工作流程。讓我們回顧一下範本 工作流程 檔案建議的每個組成部分。

首先,我們定義工作流程的名稱,該名稱將顯示在 Actions 管理 UI 中:

1
name: Dart

接下來,我們定義何時應該執行工作流程(觸發工作流程的事件)。這裡,工作流程設定為在每次推送到 main 分支或 main 分支的 PR 時執行。我個人發現,當我難以讓工作流程執行時,通常是因為我拼錯了分支名稱。

1
2
3
4
5
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

然後是一個作業列表,每個作業都有一個名稱(這裡是測試)。每個作業都有自己的定義,包括在哪裡執行作業(此作業在 Ubuntu Linux 上執行)以及要執行的步驟:

1
2
3
4
jobs:
test:
runs-on: ubuntu-latest
steps:

至於具體的測試,我們首先檢出正在測試的儲存庫,然後執行新的 setup-dart action。這會下載並安裝 Dart SDK,然後將 dart CLI 和 pub 全局目录添加到路徑中。這裡我們沒有指定要安裝哪個 Dart SDK,因此 action 將安裝最新的穩定通道 SDK。

1
2
- uses: actions/checkout@v2
- uses: dart-lang/setup-dart@v1

然後,我們可以繼續執行我們想要的測試,例如格式化、分析和單元測試:

1
2
3
4
5
6
7
8
9
10
11
- name: 安裝相依性
run: dart pub get

- name: 驗證格式
run: dart format --output=none --set-exit-if-changed .

- name: 分析專案原始碼
run: dart analyze

- name: 執行測試
run: dart test

有關完整範例,請參閱我們的 package:characters 工作流程

指定 SDK 版本

setup-dart action 支援指定您想要安裝的 Dart 版本。這可以採用兩種形式之一:

  • 特定版本,例如 2.9.0 或 2.12.0–259.12.beta
  • 來自 發佈通道 的最新版本,例如 stable 或 beta

要指定版本,請使用 sdk 參數,直接使用或作為 測試矩陣 的一部分:

1
2
3
4
5
6
7
8
9
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
sdk: [2.10.0, stable, beta]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: dart-lang/[email protected]
with:
sdk: ${{ matrix.sdk }}

此矩陣的結果是九個單獨執行的作業:

現在一切都通過了,您可能想要 在您的 README.md 檔案中加入一個狀態徽章 以顯示測試狀態。

其他解決方案

GitHub Actions 是持續整合的幾種熱門解決方案之一。其他支援 Dart SDK 的解決方案包括 TravisAppVeyorCodeShip。對於 Flutter 應用程式的測試,選項包括 Nevercode 的 CodemagicCirrus CIBitrise。Flutter 應用程式的 GitHub Actions 由社群貢獻的 action 啟用,例如 flutter-action

目前就這樣。我們希望您喜歡 Dart 的這個新的 CI 支援,如果您對 setup-dart 有任何回饋或問題,請在 議題追蹤器 中告知我們。


宣布 GitHub Actions 支援 Dart 最初發佈在 Dart 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章翻譯】Dart in Google Summer of Code 2021

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

Dart 參與 2021 Google 暑期程式碼計畫 (GSoC)

Google 暑期程式碼計畫 (GSoC) 是一個全球性的計畫,旨在讓學生開發者參與到開源軟體開發中。Google 贊助學生在暑假期間與一個指導性的開源組織合作,進行一個為期 10 週的程式設計專案。在過去的 16 年裡,已有超過 16,000 名學生參與了 Google 暑期程式碼計畫。

我們很高興地宣布,Dart 將成為 2021 年 Google 暑期程式碼計畫 的指導組織。這將是 Dart 第二次參與 Google 暑期程式碼計畫。在 2020 年,Dart 組織指導了 5 名成功的學生

您有興趣嗎?

現在就開始閱讀 專案構想清單,找到符合您技能和興趣的專案。雖然正式申請將於 3 月 29 日開放,但您今天就可以與潛在的導師討論和調整專案構想。

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

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

有關 Google 暑期程式碼計畫的更多資訊,請觀看下面的影片。或閱讀 Google 暑期程式碼計畫學生指南

我們期待您的參與!


Dart 參與 2021 年 Google 暑期程式碼計畫 最初發佈於 Medium 上的 Dart,人們在那裡透過醒目顯示和回應這個故事來繼續對話。