釋出時間:2020年5月2日 - 3分鐘閱讀
這是一個實驗。讓我們在不使用Flutter框架的情況下,建立在移動裝置上顯示一些東西所需的最小程式碼。
不美不美,自成一派
Flutter是一個高階GUI框架,它使用dart:ui
(Skia引擎的一個抽象)來實際顯示一些東西,並與這些東西所顯示的平臺進行互動。當然,我們自己也可以直接使用這個低階別的dart:ui
庫。
設定
讓我們建立一個新的Flutter專案。
$ flutter create flutter_without_flutter
複製程式碼
然後用這段程式碼替換lib/main.dart
。
import ‘dart:ui’;
void main() {
window.onBeginFrame = beginFrame;
window.scheduleFrame();
}
void beginFrame(Duration duration) {
}
複製程式碼
解釋:我們用window
單例註冊一個名為beginFrame
的全域性函式,然後要求圖形引擎對該函式進行回撥。它應該準備並最終繪製一個幀,也就是在裝置螢幕上顯示一些東西。
繪製一些東西
因為我習慣用邏輯單位而不是物理畫素來工作,所以 beginFrame
的第一步是將裝置的物理螢幕尺寸轉換為更熟悉的數值。
void beginFame(Duration duration) {
final pixelRatio = window.devicePixelRatio;
final size = window.physicalSize / pixelRatio;
final physicalBounds = Offset.zero & size * pixelRatio;
…
複製程式碼
下一步是設定一個使用邏輯單元的Canvas
。
…
final recorder = PictureRecorder();
final canvas = Canvas(recorder, physicalBounds);
canvas.scale(pixelRatio, pixelRatio);
…
複製程式碼
然後讓我們用紅色的顏料畫一個圓圈。
…
final paint = Paint()..color = Color(0xFFF44336);
final center = size.center(Offset.zero);
canvas.drawCircle(center, size.shortestSide / 4, paint);
…
複製程式碼
最後一步是將呼叫Canvas
方法建立的錄音,用它來構建一個所謂的場景,然後由windows
單例渲染。
…
final picture = recorder.endRecording();
final sceneBuilder = SceneBuilder()
..pushClipRect(physicalBounds)
..addPicture(Offset.zero, picture)
..pop();
window.render(sceneBuilder.build());
}
複製程式碼
順便說一下,通常的Flutter專案的即時熱程式碼過載仍然有效,這就是為什麼它可能會有用,使用這種設定來建立低水平的圖形應用程式,例如2D遊戲。
移動起來
為了移動圓圈並使其在螢幕上彈跳,我們將把它的中心點儲存在一個全域性變數(稱為center
)中,把它的速度儲存在另一個全域性變數(稱為velocity
)中,每次呼叫beginFrame
時修改這些變數,然後在beginFrame
函式結束時使用scheduleFrame
請求另一次繪製操作。
下面是相關的修改。
Offset center;
Offset velocity;
void beginFrame(Duration duration) {
…
canvas.scale(pixelRatio, pixelRatio);
final radius = size.shortestSide / 4;
if (center == null) {
center = size.center(Offset.zero);
velocity = Offset(3, 5);
} else {
if (center.dx < radius || center.dx > size.width — radius)
velocity = velocity.scale(-1, 1);
if (center.dy < radius || center.dy > size.height — radius)
velocity = velocity.scale(1, -1);
center += velocity;
}
final paint = Paint()..color = Color(0xFFF44336);
canvas.drawCircle(center, radius, paint);
…
window.scheduleFrame();
}
複製程式碼
因為我們無法控制beginFrame
函式的排程頻率,所以我們可能應該利用傳遞的duration
,並將圓的速度與傳遞的時間繫結。為了計算呼叫之間所經過的時間,我們引入另一個全域性變數,叫做lastDuration
。
Duration lastDuration;
void beginFrame(Duration duration) {
…
if (center == null) {
…
} else {
…
final delta = (duration — lastDuration).inMilliseconds / 1000;
center += velocity * delta;
}
lastDuration = duration;
…
複製程式碼
如果你保持應用程式的執行,只是修改了soure程式碼,圓圈應該已經開始移動,就像魔法一樣。
對觸控的反應
最後但同樣重要的是,讓我們在每次點選螢幕時改變速度。為了檢測觸控,我們需要新增另一個回撥函式onPointerDataPacket
。這個函式接收一個PointerData
物件的列表,這些物件描述了觸控向下、觸控移動和觸控向上的事件。不過我們只對 PointerChange.up
型別的事件感興趣。
這裡是新的主函式。
void main() {
window.onBeginFrame = beginFrame;
window.onPointerDataPacket = pointerDataPacket;
window.scheduleFrame();
}
複製程式碼
這裡是新的回撥函式。
void pointerDataPacket(PointerDataPacket packet) {
for (final data in packet.data) {
if (data.change == PointerChange.up) {
velocity = Offset.fromDirection(
_random.nextDouble() * pi * 2,
_random.nextDouble() * 800–400,
);
}
}
}
final _random = Random();
複製程式碼
利用這個基礎,你應該可以從頭開始建立自己的類似Flutter的GUI框架。
我將此作為一個練習留給讀者:-)
通過www.DeepL.com/Translator(免費版)翻譯