使用 Google 的 Flutter 進行真正的跨平台移動開發
已發表: 2022-03-10Flutter 是來自 Google 的開源跨平台移動開發框架。 它允許從單個代碼庫為 iOS 和 Android 構建高性能、美觀的應用程序。 它也是谷歌即將推出的 Fuchsia 操作系統的開發平台。 此外,它的架構方式可以通過自定義 Flutter 引擎嵌入器將其引入其他平台。
為什麼創建 Flutter 以及為什麼要使用它
跨平台工具包歷來採用以下兩種方法之一:
- 他們將 Web 視圖包裝在本機應用程序中,並像構建網站一樣構建應用程序。
- 它們封裝了原生平台控件,並在它們之上提供了一些跨平台抽象。
Flutter 採用了不同的方法,試圖讓移動開髮變得更好。 它為開發人員提供了一個框架應用程序,以及一個具有可移植運行時的引擎來託管應用程序。 該框架建立在 Skia 圖形庫的基礎上,提供實際渲染的小部件,而不僅僅是本機控件的包裝器。
這種方法提供了以完全自定義的方式構建跨平台應用程序的靈活性,就像 web wrapper 選項提供的那樣,但同時提供了流暢的性能。 同時,Flutter 附帶的豐富的小部件庫,以及大量的開源小部件,使其成為一個功能非常豐富的平台。 簡而言之,Flutter 是移動開發人員在跨平台開發方面所擁有的最接近的東西,幾乎沒有妥協。
鏢
Flutter 應用程序是用 Dart 編寫的,Dart 是一種最初由 Google 開發的編程語言。 Dart 是一種面向對象的語言,支持提前編譯和即時編譯,非常適合構建原生應用程序,同時通過 Flutter 的熱重載提供高效的開發工作流程。 Flutter 最近也遷移到了 Dart 2.0 版。
Dart 語言提供了其他語言中的許多特性,包括垃圾收集、異步等待、強類型、泛型以及豐富的標準庫。
Dart 提供了來自各種語言(例如 C#、JavaScript、F#、Swift 和 Java)的開發人員應該熟悉的交叉特性。 此外,Dart 可以編譯為 Javascript。 結合 Flutter,這允許跨 Web 和移動平台共享代碼。
事件的歷史時間表
- 2015 年 4 月
在 Dart 開發者峰會上展示的 Flutter(原代號 Sky) - 2015 年 11 月
Sky 重命名為 Flutter - 2018 年 2 月
Flutter beta 1 在 2018 年世界移動通信大會上宣布 - 2018 年 4 月
Flutter beta 2 發布 - 2018 年 5 月
Flutter beta 3 在 Google I/O 上宣布。 Google 宣布 Flutter 已準備好用於生產應用程序
與其他開發平台的比較
蘋果/安卓原生
本機應用程序在採用新功能時提供的摩擦最小。 由於應用程序是使用平台供應商(Apple 或 Google)自己的控件構建的,並且通常遵循這些供應商制定的設計指南,因此他們傾向於使用戶體驗更符合給定平台。 在大多數情況下,本機應用程序將比使用跨平台產品構建的應用程序執行得更好,儘管在許多情況下差異可以忽略不計,具體取決於底層跨平台技術。
原生應用程序的一大優勢是,如果需要,它們可以立即採用 Apple 和 Google 在測試版中創建的全新技術,而無需等待任何第三方集成。 構建本機應用程序的主要缺點是缺乏跨平台的代碼重用,如果針對 iOS 和 Android,這可能會使開發成本高昂。
反應原生
React Native 允許使用 JavaScript 構建原生應用程序。 應用程序使用的實際控件是本機平台控件,因此最終用戶可以獲得本機應用程序的感覺。 對於需要超出 React Native 抽象提供的自定義的應用程序,仍然可能需要原生開發。 在需要大量定制的情況下,在 React Native 的抽象層中工作的好處會減少到在某些情況下本地開發應用程序會更有益的程度。
賽馬林
在討論 Xamarin 時,需要評估兩種不同的方法。 對於他們最跨平台的方法,有 Xamarin.Forms。 儘管該技術與 React Native 非常不同,但從概念上講,它提供了類似的方法,因為它抽象了原生控件。 同樣,它在定制方面也有類似的缺點。
其次,有很多術語 Xamarin-classic。 這種方法獨立使用 Xamarin 的 iOS 和 Android 產品來構建特定於平台的功能,就像直接使用 Apple/Android 原生時一樣,在 Xamarin 情況下僅使用 C# 或 F#。 Xamarin 的好處是可以共享非平台特定代碼,例如網絡、數據訪問、Web 服務等。
與這些替代方案不同,Flutter 試圖為開發人員提供更完整的跨平台解決方案,具有代碼重用、高性能、流暢的用戶界面和出色的工具。
Flutter 應用概述
創建應用程序
安裝完 Flutter 後,用 Flutter 創建一個 App 很簡單,要么打開命令行輸入flutter create [app_name]
,在 VS Code 中選擇“Flutter: New Project”命令,或者在 Android 中選擇“Start a new Flutter Project”工作室或 IntelliJ。
無論您選擇使用 IDE 還是命令行以及您喜歡的編輯器,新的 Flutter 應用程序模板都為您提供了一個良好的應用程序起點。
該應用程序引入了flutter
/ material.dart
包來為應用程序提供一些基本的腳手架,例如標題欄、材質圖標和主題。 它還設置了一個有狀態的小部件來演示如何在應用程序狀態更改時更新用戶界面。
工具選項
Flutter 在工具方面提供了令人難以置信的靈活性。 應用程序可以很容易地從命令行與任何編輯器一起開發,因為它們可以來自受支持的 IDE,如 VS Code、Android Studio 或 IntelliJ。 採取的方法很大程度上取決於開發人員的偏好。
Android Studio 提供了最多的功能,例如 Flutter Inspector 來分析正在運行的應用程序的小部件並監控應用程序的性能。 它還提供了一些重構,在開發小部件層次結構時很方便。
VS Code 提供了更輕鬆的開發體驗,因為它的啟動速度往往比 Android Studio/IntelliJ 快。 每個 IDE 都提供內置的編輯助手,例如代碼完成,允許探索各種 API 以及良好的調試支持。
命令行也通過flutter
命令得到很好的支持,這使得創建、更新和啟動應用程序變得容易,除了編輯器之外沒有任何其他工具依賴。
熱重載
無論使用何種工具,Flutter 都對應用程序的熱重載提供了出色的支持。 這允許在許多情況下修改正在運行的應用程序,維護狀態,而無需停止應用程序、重建和重新部署。
通過允許更快的迭代,熱重載顯著提高了開發效率。 它確實使平台成為一種樂趣。
測試
Flutter 包含一個WidgetTester
實用程序,用於與測試中的小部件進行交互。 新的應用程序模板包含一個示例測試,以演示如何在編寫測試時使用它,如下所示:
// Test included with the new Flutter application template import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:myapp/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget(new MyApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsNothing); // Tap the '+' icon and trigger a frame. await tester.tap(find.byIcon(Icons.add)); await tester.pump(); // Verify that our counter has incremented. expect(find.text('0'), findsNothing); expect(find.text('1'), findsOneWidget); }); }
使用包和插件
Flutter 剛剛開始,但已經有一個豐富的開發者生態系統:大量的包和插件已經可供開發者使用。
要添加包或插件,只需在應用程序根目錄的pubspec.yaml文件中包含依賴項即可。 然後從命令行或通過 IDE 運行flutter packages get
,Flutter 的工具將引入所有必需的依賴項。
例如,要使用流行的 Flutter 圖像選擇器插件, pubspec.yaml只需將其列為依賴項,如下所示:
dependencies: image_picker: "^0.4.1"
然後運行flutter packages get
會帶入你使用它所需的一切,之後可以在 Dart 中導入和使用它:
import 'package:image_picker/image_picker.dart';
小部件
Flutter 中的一切都是一個小部件。 這包括用戶界面元素,例如ListView
、 TextBox
和Image
,以及框架的其他部分,包括佈局、動畫、手勢識別和主題,僅舉幾例。
通過讓一切都成為一個小部件,整個應用程序,順便說一下也是一個小部件,可以在小部件層次結構中表示。 擁有一個一切都是小部件的架構可以清楚地表明應用於應用程序一部分的某些屬性和行為來自何處。 這與大多數其他應用程序框架不同,後者不一致地關聯屬性和行為,有時將它們從層次結構中的其他組件附加到控件本身上。
簡單的 UI 小部件示例
Flutter 應用程序的入口點是 main 函數。 要將用戶界面元素的小部件放在屏幕上,請在main()
中調用runApp()
並將作為小部件層次結構根的小部件傳遞給它。
import 'package:flutter/material.dart'; void main() { runApp( Container(color: Colors.lightBlue) ); }
這會產生一個填充屏幕的淺藍色Container
小部件:
無狀態與有狀態小部件
小部件有兩種形式:無狀態和有狀態。 無狀態小部件在創建和初始化後不會更改其內容,而有狀態小部件維護一些在運行應用程序時可以更改的狀態,例如響應用戶交互。
在此示例中,將FlatButton
小部件和Text
小部件繪製到屏幕上。 Text
小部件以一些默認String
作為其狀態開始。 按下按鈕會導致狀態更改,這將導致Text
小部件被更新,顯示一個新的String
。
要封裝一個小部件,請創建一個派生自StatelessWidget
或StatefulWidget
的類。 例如,淺藍色的Container
可以這樣寫:
class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Container(color: Colors.lightBlue); } }
Flutter 在將其插入到小部件樹時會調用小部件的 build 方法,以便可以渲染這部分 UI。
對於有狀態的小部件,從StatefulWidget
派生:
class MyStatefulWidget extends StatefulWidget { MyStatefulWidget(); @override State createState() { return MyWidgetState(); } }
class MyStatefulWidget extends StatefulWidget { MyStatefulWidget(); @override State createState() { return MyWidgetState(); } }
有狀態的小部件返回一個負責為給定狀態構建小部件樹的State
類。 當狀態改變時,widget 樹的相關部分被重建。
在以下代碼中,當單擊按鈕時, State
類會更新String
:
class MyWidgetState extends State { String text = "some text"; @override Widget build(BuildContext context) { return Container( color: Colors.lightBlue, child: Padding( padding: const EdgeInsets.all(50.0), child: Directionality( textDirection: TextDirection.ltr, child: Column( children: [ FlatButton( child: Text('Set State'), onPressed: () { setState(() { text = "some new text"; }); }, ), Text( text, style: TextStyle(fontSize: 20.0)), ], ) ) ) ); } }
class MyWidgetState extends State { String text = "some text"; @override Widget build(BuildContext context) { return Container( color: Colors.lightBlue, child: Padding( padding: const EdgeInsets.all(50.0), child: Directionality( textDirection: TextDirection.ltr, child: Column( children: [ FlatButton( child: Text('Set State'), onPressed: () { setState(() { text = "some new text"; }); }, ), Text( text, style: TextStyle(fontSize: 20.0)), ], ) ) ) ); } }
狀態在傳遞給setState()
的函數中更新。 調用setState()
時,該函數可以設置任何內部狀態,例如本例中的字符串。 然後,將調用build
方法,更新有狀態小部件的樹。
另請注意,使用Directionality
小部件為其子樹中需要它的任何小部件(例如Text
小部件)設置文本方向。 這裡的示例是從頭開始構建代碼,因此在小部件層次結構的某個地方需要Directionality
。 但是,使用MaterialApp
小部件(例如使用默認應用程序模板)會隱式設置文本方向。
佈局
runApp
函數默認填充小部件以填充屏幕。 為了控制小部件佈局,Flutter 提供了多種佈局小部件。 有一些小部件可以執行垂直或水平對齊子小部件的佈局,擴展小部件以填充特定空間,將小部件限制在特定區域,在屏幕上居中,並允許小部件相互重疊。
兩個常用的小部件是Row
和Column
。 這些小部件執行佈局以水平(行)或垂直(列)顯示其子小部件。
使用這些佈局小部件只需將它們包裝在一個子小部件列表中。 mainAxisAlignment
控制小部件如何沿佈局軸定位,居中、在開始、結束或使用各種間距選項。
以下代碼顯示瞭如何對齊Row
或Column
中的多個子小部件:
class MyStatelessWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Row( //change to Column for vertical layout mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.android, size: 30.0), Icon(Icons.pets, size: 10.0), Icon(Icons.stars, size: 75.0), Icon(Icons.rowing, size: 25.0), ], ); } }
響應觸摸
觸摸交互是通過封裝在GestureDetector
類中的手勢來處理的。 由於它也是一個小部件,因此添加手勢識別就像將子小部件包裝在GestureDetector
中一樣簡單。
例如,要將觸摸處理添加到Icon
,使其成為GestureDetector
的子項,並設置檢測器的處理程序以捕獲所需的手勢。
class MyStatelessWidget extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( onTap: () => print('you tapped the star'), onDoubleTap: () => print('you double tapped the star'), onLongPress: () => print('you long pressed the star'), child: Icon(Icons.stars, size: 200.0), ); } }
在這種情況下,當對圖標執行點擊、雙擊或長按時,將打印相關的文本:
To hot reload your app on the fly, press "r". To restart the app entirely, press "R". An Observatory debugger and profiler on iPhone X is available at: https://127.0.0.1:8100/ For a more detailed help message, press "h". To quit, press "q". flutter: you tapped the star flutter: you double tapped the star flutter: you long pressed the star
除了簡單的點擊手勢外,還有大量的識別器,從平移和縮放到拖動。 這些使得構建交互式應用程序變得非常容易。
繪畫
Flutter 還提供了各種用於繪畫的小部件,包括修改不透明度、設置剪切路徑和應用裝飾的小部件。 它甚至支持通過CustomPaint
小部件以及關聯的CustomPainter
和Canvas
類進行自定義繪畫。
繪畫小部件的一個示例是DecoratedBox
,它可以將BoxDecoration
到屏幕上。 下面的例子展示瞭如何使用它來用漸變填充來填充屏幕:
class MyStatelessWidget extends StatelessWidget { @override Widget build(BuildContext context) { return new DecoratedBox( child: Icon(Icons.stars, size: 200.0), decoration: new BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.red, Colors.blue, Colors.green], tileMode: TileMode.mirror ), ), ); } }
動畫
Flutter 包含一個AnimationController
類,它控制動畫隨時間的播放,包括啟動和停止動畫,以及改變動畫的值。 此外,還有一個AnimatedBuilder
小部件,它允許與 AnimationController 一起構建AnimationController
。
任何小部件,例如前面顯示的裝飾星,都可以對其屬性進行動畫處理。 例如,將代碼重構為StatefulWidget
,因為動畫是一種狀態變化,並且將AnimationController
傳遞給State
類允許在構建小部件時使用動畫值。
class StarWidget extends StatefulWidget { @override State createState() { return StarState(); } } class StarState extends State with SingleTickerProviderStateMixin { AnimationController _ac; final double _starSize = 300.0; @override void initState() { super.initState(); _ac = new AnimationController( duration: Duration(milliseconds: 750), vsync: this, ); _ac.forward(); } @override Widget build(BuildContext context) { return new AnimatedBuilder( animation: _ac, builder: (BuildContext context, Widget child) { return DecoratedBox( child: Icon(Icons.stars, size: _ac.value * _starSize), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.red, Colors.blue, Colors.green], tileMode: TileMode.mirror ), ), ); } ); } }
class StarWidget extends StatefulWidget { @override State createState() { return StarState(); } } class StarState extends State with SingleTickerProviderStateMixin { AnimationController _ac; final double _starSize = 300.0; @override void initState() { super.initState(); _ac = new AnimationController( duration: Duration(milliseconds: 750), vsync: this, ); _ac.forward(); } @override Widget build(BuildContext context) { return new AnimatedBuilder( animation: _ac, builder: (BuildContext context, Widget child) { return DecoratedBox( child: Icon(Icons.stars, size: _ac.value * _starSize), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.red, Colors.blue, Colors.green], tileMode: TileMode.mirror ), ), ); } ); } }
class StarWidget extends StatefulWidget { @override State createState() { return StarState(); } } class StarState extends State with SingleTickerProviderStateMixin { AnimationController _ac; final double _starSize = 300.0; @override void initState() { super.initState(); _ac = new AnimationController( duration: Duration(milliseconds: 750), vsync: this, ); _ac.forward(); } @override Widget build(BuildContext context) { return new AnimatedBuilder( animation: _ac, builder: (BuildContext context, Widget child) { return DecoratedBox( child: Icon(Icons.stars, size: _ac.value * _starSize), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.red, Colors.blue, Colors.green], tileMode: TileMode.mirror ), ), ); } ); } }
在這種情況下,該值用於改變小部件的大小。 每當動畫值發生變化時,都會調用builder
函數,從而導致星形的大小在 750 毫秒內變化,從而產生效果比例:
使用本機功能
平台渠道
為了在 Android 和 iOS 上提供對原生平台 API 的訪問,Flutter 應用程序可以使用平台通道。 這些允許 Flutter Dart 代碼將消息發送到託管的 iOS 或 Android 應用程序。 許多可用的開源插件都是使用平台通道上的消息傳遞構建的。 要了解如何使用平台通道,Flutter 文檔包含一個很好的文檔,該文檔演示瞭如何訪問本機電池 API。
結論
即使在 beta 階段,Flutter 也為構建跨平台應用程序提供了很好的解決方案。 憑藉其出色的工具和熱重載,它帶來了非常愉快的開發體驗。 豐富的開源軟件包和優秀的文檔使其易於上手。 展望未來,除 iOS 和 Android 之外,Flutter 開發人員將能夠針對 Fuchsia。 考慮到引擎架構的可擴展性,看到 Flutter 也登陸各種其他平台並不讓我感到驚訝。 隨著社區的發展,現在是加入的好時機。
下一步
- 安裝顫振
- 飛鏢語言之旅
- Flutter 代碼實驗室
- Flutter Udacity 課程
- 文章源代碼