【譯】使用 Flutter 實現跨平臺移動端開發

作者: Mike Bluestein   | 譯:孫印鳳

原文地址:[https://www.smashingmagazine.com/2018/06/google-flutter-mobile-development/]

【譯者注:鏈接序號對應下面索引列表,另外可以點擊閱讀原文查看詳細的鏈接文章】

Flutter 是一款由 Google 開發的開源、跨平臺移動端開發框架。它允許使用同一個代碼庫構建高性能、漂亮的 iOS 和 Android 應用,同時它也是 Google 即將推出的 Fuchsia 操作系統的開發平臺。此外,通過自定義的 Flutter 引擎可以將其嵌入到其他平臺。

Flutter 爲什麼會出現?爲什麼要使用它呢?

一直以來,跨平臺工具採用以下兩種方法之一:

  • 在原生應用程序中嵌入 web view ,像構建網站一樣構建應用程序。
  • 封裝原生平臺裏的控件併爲它們提供一些跨平臺的參數。

爲了使移動端開發變得更好,Flutter 嘗試了一種不同的方法。它提供了開發人員工作的框架應用程序和能夠託管應用程序的可移植運行時的引擎。該框架依託 Skia 圖形庫而構建,提供了實際渲染時用到的 widgets,而不僅僅是原生應用控件的包裝器。就像 web 包裝器選項提供的那樣,該方法可以靈活的以完全自定義的方式構建跨平臺應用程序,同時還會提供流暢的性能體驗。與此同時,Flutter 自帶的豐富的 widget 庫以及一些開源的 widgets 使其成爲一個功能豐富的平臺。簡言之,Flutter 目前是移動端開發者接觸到的最接近跨平臺開發的東西。

Dart

Flutter 應用程序使用 Dart 編寫,Dart 是最初由 Google 開發的一種編程語言。它是一種支持預編譯和實時編譯的面嚮對象語言,所以比較適合開發原生應用程序,配合 Flutter 的熱加載可以提供高效的開發工作流程。Flutter 最近也轉向使用 Dart 2.0 版。Dart 語言提供了許多其他編程語言具有的功能,包括垃圾收集、異步等待、強類型、泛型以及豐富的標準庫等等。這些功能對於各種編程語言的開發者們來說都比較熟悉,例如 C#、JavaScript、F#、Swift 和 Java。此外,Dart 可以編譯爲 Javascript,與 Flutter 結合可以在 web 和移動平臺實現代碼共享。

事件歷史時間表

  • 2015.04

在 Dart 開發者峯會 [1] 上提出 Flutter(最初命名爲 Sky )。

  • 2015.11

Sky 重新命名爲 Flutter。

  • 2018.02

在2018年世界移動通信大會上,Flutter beta 1 [2] 版本發佈。

  • 2018.04

Flutter beta 2 [3] 版本發佈

  • 2018.05

Flutter beta 3 [4] 版本在 Google I/O 上發佈。Google 宣佈 Flutter 可以用於開發應用程序。

與其他開發平臺比較

APPLE/ANDROID NATIVE

原生應用程序在使用新功能時帶來的困擾是最少的。由於應用程序是使用平臺供應商自己(Apple 或 Google)的控件構建,爲了讓用戶體驗更加符合給定的平臺,因此他們通常遵循這些供應商制定的設計指南。大多數情況下,原生的應用將會比那些跨平臺構建的應用性能要好一些,儘管在很多情況下兩者的差異可以忽略不計,不過具體還要取決於底層跨平臺技術。原生應用的一大優勢是:當需要時,他們可以立即採用 Apple 和 Google 在測試版中開發的新技術而不用等待第三方的集成。構建原生應用的主要缺點是缺乏跨平臺的代碼複用,如果同時開發 iOS 和 Android 應用,那麼開發成本可能會很高。

REACT NATIVE

React Native 允許原生應用使用 JavaScript 構建。應用中用到的控件實際上都是原生平臺裏的控件,所以用戶使用起來感覺和原生應用一樣。對於那些 React Native 沒有提供的需要自定義的應用,仍然需要使用原生開發。當需要定製的模塊比較多時,某些情況下,在 React Native 中開發不如使用原生開發更合適。

XAMARIN

當談到 Xamarin 時,有兩種不同的方法將會被提及。跨平臺方法:Xamarin.Forms。該方法不同於 React Native,但是從概念上講是相似的,因爲它也是抽象原生控件。同樣的,在定製方面它也有和 React Native 同樣的缺點。第二種方法:Xamarin-classic。該方法分開使用 Xamarin 的 iOS 和 Android 產品來構建適用於特定平臺的功能,就像直接使用 Apple/Android 原生功能一樣,只不過在 Xamarin 中需要使用 C# 或 F# 。使用 Xamarin 的好處是可以共享非平臺特定的代碼,例如網絡、數據訪問、Web 服務等。

與上面的替代方法不同,Flutter 試圖給開發者一個更加完整的跨平臺解決方案,包括代碼複用、高性能、流暢的用戶界面和出色的工具。

一個 Flutter 應用概述

創建一個應用程序

安裝 Flutter [5] 之後,使用 Flutter 創建應用程序則非常簡單:打開命令行,輸入 flutter create[app_name] , 在 VS Code 中選擇 Flutter:NewProject ;在 Android Studio 或 IntelliJ 中選擇 StartanewFlutterProject 。無論你是選擇使用 IDE 還是使用首選編輯器裏的命令行,新的 Flutter 應用程序模板都爲你提供了一個良好的應用起點。

該應用程序引入了 flutter/material.dart 包,它爲應用程序提供了一些基本的元素,例如標題欄、icons 和主題。它還設置了一個帶有狀態的 widget 來演示當應用程序裏的 state 發生變化時用戶界面是如何更新的。下面是 Flutter 應用運行在 iOS 和 Android 上的圖片:

工具選項

Flutter 在工具方面提供了令人難以置信的靈活性。就像可以從支持的 IDE( 比如 VSCode AndroidStudio 、或 IntelliJ )中進行開發一樣,應用程序可以簡單的在任何編輯器的命令行中進行開發。使用何種開發工具很大程度上取決於開發者的喜好。 AndroidStudio 提供了大部分的功能,比如用於分析正在運行應用的 widgets 以及監控應用程序性能的 Flutter 檢查器。它還提供了一些重構模板,在開發帶有層次結構的 widget 時用起來將會很方便; VSCode 提供了更加輕快的開發體驗,因爲它啓動的速度比 AndroidStudio IntelliJ 都要快,而且每個 IDE 都內置了編輯助手,例如代碼補全、各種 API 處理以及良好的調試支持;命令行也很好的支持了 Flutter 命令,這使得創建、更新和發佈應用都變得簡單 ,除了編輯器外不再依賴於其他工具。以下是在各種環境中使用 Flutter 的情景:

熱加載

無論使用哪種工具,Flutter 都可以很好的支持熱加載。這樣在許多情況下就可以修改正在運行的應用程序、維護其狀態,而不必停止運行、重新構建和部署了。通過快速迭代,熱加載可以極大的提升開發效率。這樣也使得這個平臺使用起來更友好。

測試

Flutter 包含 WidgetTester 實用程序,用於和測試中的 widgets 交互。新的應用程序模板包含一個示例測試,用來演示在編寫測試時如何使用它,如下所示:

// 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 剛宣佈可以使用,但已經有一個豐富的開發者生態系統可以使用:A plethora of packages and plugins [6]。當添加一個包或者插件時,只需要在應用程序根目錄下的 pubspec.yaml 文件中添加依賴即可。然後通過命令行或 IDE 運行 flutter packagesget , Flutter 就會引入所需的全部依賴。例如,在 Flutter 中使用比較流行的 image picker 插件,則只需要在 pubspec.yaml 文件中添加依賴,如下所示:

dependencies:
image_picker: "^0.4.1"

接着運行 flutter packagesget 命令就會引入使用時所需的一切東西,然後在 Dart 中導入和使用:

import 'package:image_picker/image_picker.dart';

Widgets

在 Flutter 中一切皆 widget 。widget 包括用戶界面元素,例如 ListView , TextBox Image 以及框架的其他部分,例如佈局、動畫、手勢識別和主題等等。widget 化的設計使得整個應用程序也可以嵌入帶有層次結構的其他 widget 中(整個應用程序也是一個 widget)。widget 化的體系結構可以清楚的追蹤應用程序中一部分的屬性和行爲,這與其他大部分應用程序框架不同,大多數的框架是將屬性和行爲不一致的關聯起來,有時將它們與層次結構中的其他組件相關聯,有時將其與控件本身相關聯。

簡單的 UI WIDGET 示例

一個 Flutter 應用的入口是其主要功能。如下所示,在用戶界面元素中放入一個 widget,在函數 main() 中調用 runApp() 。

import 'package:flutter/material.dart';

void main() {
  runApp(
    Container(color: Colors.lightBlue)
  );
}

結果是一個淡藍色的 Container widget 鋪滿了屏幕。

無狀態和有狀態 widgets

widgets 分爲兩種:無狀態的 widgets 和有狀態的 widgets 。無狀態的 widgets 在他們創建和初始化之後內容不再改變,而有狀態的 widgets 當應用程序在運行中時允許改變某些狀態,例如與用戶之間的交互。舉個例子,在應用中同時引用了 FlatButtonwidget Textwidget Textwidget 的 state 設置了默認的 String。當按下按鈕時導致 state 值改變,這會引起 Textwidget 更新從而顯示一個新的 String 值。如果要對 widget 封裝則需要創建一個派生自 StatelessWidget StatefulWidget 的類。例如,淡藍色的 Container 可像下面這樣寫:

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(color: Colors.lightBlue);
  }
}

當創建的 widget 插入到 widget 樹中後,Flutter 將會調用 widget 的 build 方法,所以 UI 會被渲染。對於一個有狀態的 widget 應該是派生自 StatefulWidget 類:

class MyStatefulWidget extends StatefulWidget {

  MyStatefulWidget();

  @override
  State createState() {
    return MyWidgetState();
  }
}

有狀態的 widget 將會返回一個 State 類,該類負責爲給定的 state 構建 widget 樹。當 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)),
            ],
          )
        )
      )
    );
  }
}

通過 setState() 可以更新 state 值。當 setState() 被調用時,這個函數可以重置任何內部的 state 值,像上述例子中的 String;然後調用 build 方法,更新狀態 widget 樹。

還要注意可以使用 Directionality widget 爲其子樹中需要它的 widget 設置文本方向,比如 Text widgets。這裏的示例是從頭開始構建代碼,所以在 widget 層次結構的一些地方是需要使用 Directionality 。然而,使用 MaterialApp widget 會隱式設置文本方向(例如使用默認應用程序模板)。

佈局

默認情況下,runApp 函數會將 widget 放大至鋪滿整個屏幕。爲了控制 widget 的佈局,Flutter 提供了很多佈局 widgets。這些 widgets 允許垂直或水平對齊子 widgets、將 widget 放大以鋪滿某個特定區域、將 widget 限制在某個區域中、將其置於屏幕中心以及允許 widgets 之間相互重疊。比較常用的兩個 widgets 是 Row Column 。它們可以水平(Row)或者垂直(Column)顯示其子 widgets。使用這些佈局 widgets 只需將它們包裝在子 widgets 的列表中, mainAxisAlignment 會將 widgets 控制在佈局軸的位置,不論是居中、開始、結束還是使用各種間距選項。下面的代碼展示了怎樣在 Row 或者 Column 中對齊子 widgets。

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 類中。由於它也是個 widget,添加手勢識別和在 GestureDetector 中封裝子 widgets 一樣簡單。例如,在一個 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: http://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 還提供了很多繪畫相關的 widgets,包括修改不透明度、設置剪切路徑和應用設置。通過使用 CustomPaint widget、 CustomPainter 和 Canvas 類的結合,它還支持普通的繪畫功能。繪畫 widget 的一個示例是 DecoratedBox ,可以在屏幕中畫一個 BoxDecoration 。下面的例子說明了如何使用 DecoratedBox 和漸變填充填充屏幕。

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 的 widget 允許和 AnimationController 一起使用構建動畫。任何的 widget 都包含它的動畫屬性,像前面展示的裝飾星星。例如,將代碼重構爲一個 StatefulWidget ,因爲動畫也是 state 變化並且將 AnimationController 傳遞給 State 類允許在構建 widget 時使用動畫值。

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
            ),
          ),
        );
      }
   );
  }
}

在這種情況下,該值可以用於改變此 widget 的大小。只要動畫值發生變化就會調用構建器函數,從而導致當裝飾星星的大小變化超過750毫秒時產生了規模效應。

使用原生的功能

平臺通道

爲了給 Android 和 iOS 上的原生平臺 APIs 提供支持,Flutter 應用可以使用平臺通道。這將允許 Flutter Dart 代碼向託管的 iOS 或 Android 應用程序發送消息。許多可用的開源插件都是使用平臺通道上的消息傳遞構建的。學習如何使用平臺通道,Flutter 文檔 [7] 包含了一個訪問原生電池 APIs 的好文檔。

總結

即使是 beta 版本,Flutter 也提供了一個很好的構建跨平臺應用程序的解決方案。憑藉其出色的工具和熱加載,給用戶帶來了非常好的開發體驗;豐富的開源軟件包和詳細的文檔讓你可以輕鬆入門。接下來,除了 iOS 和 Android,Flutter 的開發者將目標指向了 Fuchsia。考慮到 Flutter 引擎架構的可擴展性,看到它應用在其他各種平臺上也不會讓我感到驚訝。隨着社區的發展,現在正是加入 Flutter 的好時機。

擴展閱讀

  • 安裝 Flutter: https://flutter.io/get-started/install/
  • Dart 語言之旅: https://www.dartlang.org/guides/language/language-tour
  • Flutter Codelabs: https://flutter.io/codelabs/
  • Flutter Udacity 課程: https://www.udacity.com/course/build-native-mobile-apps-with-flutter–ud905
  • 文章源代碼: https://gist.github.com/mikebluestein/3350443df4689ddac115b68d1598d18e

索引列表

[1] https://www.youtube.com/watch?v=PnIWl33YMwA&feature=youtu.be

[2] https://medium.com/flutter-io/announcing-flutter-beta-1-build-beautiful-native-apps-dc142aea74c0

[3] https://medium.com/flutter-io/https-medium-com-flutter-io-announcing-flutters-beta-2-c85ba1557d5e

[4] https://developers.googleblog.com/2018/05/ready-for-production-apps-flutter-beta-3.html

[5] https://flutter.io/get-started/install/

[6] https://pub.dartlang.org/flutter

[7] https://flutter.io/platform-channels/

文章轉自公衆號”全棧探索”,歡迎關注:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章