5分鐘學會Flutter開發

百度Geek說發表於2021-09-02

導讀:Flutter是Google開源的構建使用者介面(UI)工具包,幫助開發者通過一套程式碼庫高效構建多平臺應用,支援移動、Web、桌面和嵌入式平臺。Flutter使用Dart為開發語言,利用Skia繪圖引擎,直接通過CPU、GPU進行繪製,不需要依賴任何原生的控制元件,相比React Native(依賴中間者JSCore引擎)效能更高。

全文3560字,預計閱讀時間 14分鐘。

目前Flutter混合棧技術成熟,基礎建設完善,百度貼吧、網盤、地圖、閱讀、輸入法等均已接入Flutter,一套程式碼雙端執行,約節省50%人力。

一、環境配置:

1.1 下載Flutter SDK

git clone https://github.com/flutter/flutter.git
複製程式碼

1.2 配置環境變數

編輯~/.bash_profile,將環境變數新增至末尾。(如終端安裝了zsh外掛,則新增環境變數至 ~/.zshrc)

# FLUTTER_HOME為下載的Flutter資料夾路徑
export FLUTTER_HOME=/Users/.../flutter
export PATH=$PATH:$FLUTTER_HOME/bin
export PATH=$PATH:$FLUTTER_HOME/bin/cache/dart-sdk/bin
複製程式碼

1.3 重新整理環境變數

source ~/.bash_profile
source ~/.zshrc(如安裝zsh外掛)
複製程式碼

1.4 開發工具

1. Xcode + Android Studio(推薦)

2. Visual Studio Code

以 Xcode + Android Studio為例,配置Android Studio外掛:

1.4.1 安裝 Flutter,Dart外掛

Android Studio - Preferences - Plugins - Marketplace

1.4.2 安裝最新 Android SDK Command

Android Studio - Preferences - SystemSettings - Android SDK - SDK Tools - 勾選Android SDK Command-line Tools

1.4.3 執行 flutter doctor

圖片

*報錯:Unable to find bundled Java version on Flutter

cd /Applications/Android\ Studio.app/Contents/jre
ln -s ../jre jdk
ln -s "/Library/Internet Plug-Ins/JavaAppletPlugin.plugin" jdk
flutter doctor
複製程式碼

二、工程建立

2.1 建立Flutter專案

flutter create xxx
複製程式碼

2.2 建立Flutter模組(用於原生整合Flutter)

create --template module xxx
複製程式碼

2.3 工程結構

bd_flutter
  .dart_tool.............記錄依賴庫資訊
  .idea..................當前專案配置
  android................Android工程目錄
  iOS....................iOS工程目錄
  lib....................Flutter程式碼目錄
  test...................單元測試目錄
  web....................Web工程目錄
  pubspec.yaml...........Pub第三方依賴配置檔案,類似Cocoapods、Gradle
複製程式碼

三、程式設計正規化

在 Flutter 中介面佈局使用 Dart 語言宣告式程式設計正規化,更易於開發與閱讀。

3.1 指令式程式設計

命令“機器”如何去做事情(注重 how) 。

3.2 宣告式程式設計

告訴“機器”你想要的是什麼(注重 what) 。

2009年開始Vue、React、SwiftUI、Flutter以宣告式程式設計為主,正逐步成為大前端的一種程式設計趨勢。

3.3 我們舉一個栗子,來幫我我們理解這兩者的區別

3.3.1 點選按鈕修改文字(OC、Java版本)

圖片

3.3.2 點選按鈕修改文字(Flutter、SwiftUI版本)

圖片

在宣告式程式設計中,首先程式碼是結構化的;其次,開發者無需關注Label/Text控制元件更新,引擎會自動根據num值的改變修改引用控制元件的值。

圖片

四、基礎架構

圖片

Flutter被設計為一個可擴充套件的分層系統。它可以被看作是各個獨立的元件的系列合集,上層元件各自依賴下層元件。元件無法越權訪問更底層的內容,並且框架層中的各個部分都是可選且可替代的。從下到上分為三層,依次為:Embedder、Engine、Framework。

4.1 Embedder

Embedder是作業系統適配層,實現了渲染 Surface 設定,執行緒設定等。

4.2 Engine

Engine層是 Flutter 的核心,它主要使用 C++ 編寫,並提供了 Flutter 應用所需的原語。當需要繪製新一幀的內容時,引擎將負責對需要合成的場景進行柵格化。它提供了 Flutter 核心 API 的底層實現,包括圖形(通過 Skia 連結:skia.org/)、文字佈局、檔案及網… IO、輔助功能支援、外掛架構和 Dart 執行環境及編譯環境的工具鏈。

4.3 Framework

Framework 層是一個用 Dart 實現的 UI SDK,包含了動畫、圖形繪製和手勢識別等功能。開發者可以通過 Flutter 框架層與 Flutter 互動,該框架提供了以 Dart 語言編寫的現代響應式框架。它包括由一系列層組成的一組豐富的平臺,佈局和基礎庫。從下層到上層,依次有:

1、基礎的Foundation 類及一些基層之上的構建塊服務,如 animation、 painting 和 gestures,它們可以提供上層常用的抽象。

2、渲染層用於提供操作佈局的抽象。有了渲染層,你可以構建一棵可渲染物件的樹。在你動態更新這些物件時,渲染樹也會自動根據你的變更來更新佈局。

3、widget層是一種組合的抽象。每一個渲染層中的渲染物件,都在 widgets 層中有一個對應的類。此外,widgets 層讓你可以自由組合你需要複用的各種類。響應式程式設計模型就在該層級中被引入。

4、Material 和 Cupertino 庫提供了全面的 widgets 層的原語組合,這套組合分別實現了 Material 和 iOS 設計規範。

五、檢視渲染

5.1 Widget

Flutter中沒有Controller、Activity概念,只有一種控制元件Widget(相當於View),一切皆 Widget。

Widget 是 Flutter 功能的抽象描述,是檢視的配置資訊,同樣也是資料的對映,是 Flutter 開發框架中最基本的概念。

兩個比較重要的Widget:StatelessWidget和StatefulWidget。

5.2 渲染過程

5.2.1 Flutter引擎不會直接渲染widget樹,因為widget是特別不穩定的,會頻繁的呼叫build方法,widget又相互依賴,一旦呼叫build,後面的widget都會重新建立,直接去解析widget的話會非常消耗效能,佈局需要重新計算。由此引出了Element,RenderObject的概念,Flutter引擎解析的是RenderObject樹,並非widget。

圖片

5.2.2 Widget會轉化成RenderObject,但並不是所有的widget都會轉成RenderObject。

非RenderObject轉化:

//Text -> StatelessWidget -> Widget

class Text extends StatelessWidget {
}
abstract class StatelessWidget extends Widget {
StatelessElement createElement() => StatelessElement(this);
}
abstract class Widget extends DiagnosticableTree {
Element createElement(); // 建立element抽象方法
}
複製程式碼

RenderObject轉化:

//Column -> Flex -> MultiChildRenderObjectWidget - > RenderObjectWidget -> Widget
class Column extends Flex {
}
class Flex extends MultiChildRenderObjectWidget {
// ⽅法實現
RenderFlex createRenderObject(BuildContext context) {
//返回RenderFlex
return RenderFlex -> RenderBox -> RenderObject
} }
abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {
}
abstract class RenderObjectWidget extends Widget {
RenderObjectElement createElement();
RenderObject createRenderObject(BuildContext context); // 抽象⽅法-建立RenderObject
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) {
}
void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
abstract class Widget extends DiagnosticableTree {
Element createElement(); // 抽象⽅法-建立element
}
class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox,
FlexParentData>,
 RenderBoxContainerDefaultsMixin<RenderBox,
FlexParentData>,
 DebugOverflowIndicatorMixin {
}
abstract class RenderBox extends RenderObject {
}
複製程式碼

六、混合開發

6.1 Flutter呼叫原生方法

1.Platform channels

2.Pigeon

3.pub.dev/ 中搜尋第三方開源包

以Platform channels為例:Flutter呼叫原生獲取UDID

/*
Flutter程式碼
*/
static const platform = const MethodChannel("leo.com/getudid");
void getUDID() async {
final result = await platform.invokeMethod("nativeGetUDID"); // 要調⽤的⽅法
// final result = await platform.invokeMethod("nativeGetUDID",["flutter引數"]);
setState(() {
_udid = result;
 });
}

複製程式碼
/*
iOS程式碼
*/
// 1.獲取FlutterViewController
let controller: FlutterViewController = window.rootViewController as!
FlutterViewController;
// 2.建立FlutterMethodChannel,跟controller⼆進位制訊息通訊
let channel = FlutterMethodChannel(name: "leo.com/getudid", binaryMessenger:
controller.binaryMessenger);
// 3.監聽channel調⽤⽅法,當flutter調⽤nativeGetUDID的時候調⽤
channel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping
FlutterResult) in
// 1.判斷當前是否是nativeGetUDID
guard call.method == "nativeGetUDID" else {
result(FlutterMethodNotImplemented); // 報⼀個沒有⽅法的錯誤
return;
 }
call.arguments; //傳遞過來的引數
// 2.獲取UDID
let udid = "xxxx-xxxx-xxxx-xxxx"
result(udid) //回撥值
}
複製程式碼
/*
Android程式碼
*/
private val CHANNEL = "leo.com/getudid"
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
// 1.建立MethodChannel物件
val methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger,
CHANNEL)
// 2.新增調⽤⽅法的回撥
methodChannel.setMethodCallHandler {
// Note: this method is invoked on the main thread.
call, result ->
// 2.1.如果調⽤的⽅法是nativeGetUDID,那麼正常執⾏
if (call.method == "nativeGetUDID") {
// 2.1.1.調⽤另外⼀個⾃定義⽅法回去電量資訊
val udid = "xxxx-xxxx-xxxx-xxxx";
result.success(udid)
 } else {
//⽅法找不到,回撥notImplemented
result.notImplemented()
 }
 }
}
複製程式碼

6.2 原⽣整合Flutter建立Flutter模組

create --template module native_add_flutter
複製程式碼

6.2.1 iOS整合Flutter

// CocoaPods整合
flutter_application_path = '../native_add_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb’)
複製程式碼
// 初始化Flutter引擎 , 為引擎起名為leo
let flutterEngine:FlutterEngine = FlutterEngine(name: "leo");
// 啟動flutter引擎,預設函式⼊⼝為main
flutterEngine.run();
let flutterVC = FlutterViewController(engine: engine, nibName: nil, bundle: nil);
flutterVC.modalPresentationStyle = .fullScreen;
self.present(flutterVC, animated: true, completion: nil);
複製程式碼

6.2.2 Android整合Flutter

// 在gradle進⾏配置
// 建立Android項⽬、新增相關的依賴
// 1、修改Android項⽬settings.gradle
setBinding(new Binding([gradle: this])) // new
evaluate(new File( // new
settingsDir.parentFile, // new
'native_add_flutter/.android/include_flutter.groovy' // new
))
include ':native_add_flutter'
project(':native_add_flutter').projectDir = new File('../native_add_flutter')
// 2、配置Android項⽬的build.gradle
dependencies {
 ...
implementation project(':flutter') //增加flutter依賴
}
// 3、AndroidManifest.xml配置
<activity android:name="io.flutter.embedding.android.FlutterActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDi
rection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"
/>
複製程式碼
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startActivity(
FlutterActivity.createDefaultIntent(this)
 );
 }
}
複製程式碼

七、案例講解 - 計數器

flutter create flutterdemo

main.dart

// 導⼊類
import 'package:flutter/material.dart';
//⼊⼝函式,程式載入時調⽤
void main() {
runApp(MyApp()); //調⽤runApp⽅法,並初始化MyApp
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) { //初始化會調⽤build⽅法
return MaterialApp( //Material為Google的⼀種UI⻛格,MaterialApp可為項⽬配置App標題、主題
等
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
 ),
home: MyHomePage(title: 'Flutter Demo Home Page'), //設定主⻚為MyHomePage
 );
 }
}
//由於點選需要更改Text顯示,所以此處繼承StatefulWidget
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
//下的按鈕的點選事件
void _incrementCounter() {
// setState會標記需要重新整理UI
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++; //點選按鈕時候,counter+1, 並⾃動更新UI顯示
 });
 }
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar( //配置導航
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
 ),
body: Center( //配置佈局顯示在中⼼
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column( //⼀種豎向佈局⽅式,相當於listview
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ //返回多個widget陣列,
Text(
'You have pushed the button this many times:',
 ),
Text( '$_counter',//顯示_counter的值
style: Theme.of(context).textTheme.headline4,//顯示樣式,使⽤主題的headline4
顯示
 ),
 ],
 ),
 ),
floatingActionButton: FloatingActionButton( //⼀個可點選的按鈕,固定在右下⻆
onPressed: _incrementCounter, //點選事件
tooltip: 'Increment',
child: Icon(Icons.add), //按鈕顯示為內部⾃帶的add圖⽚
 ), // This trailing comma makes auto-formatting nicer for build methods.
  );
 }
}
複製程式碼

八、附:近期重要更新

1.2版本 2019/02/26
支援java1.8
增加了JavaScript與Dart的通訊通道
增加了對Android App Bundles的支援
通過減少呼叫建構函式和靜態方法,提升AOT(預編譯)10%-20%的效能

1.5版本 2019/05/07
新增了整合測試
提高熱過載效能
決定刪除動態更新計劃
新增對Linux和Windows的Flutter執行支援

1.7版本 2019/07/09
優化Flutter tools支援
支援32位和64位Android bundles
開始支援web端和實驗性支援桌面端

1.9版本 2019/09/10

完善Web平臺支援
桌面平臺實驗性支援
新增24種語言環境支援
支援macOS Catalina和iOS 13
Android增加對構建AAR的支援

1.12版本 2019/12/11
支援Android 10
支援iOS13暗黑模式
可以將Flutter模組整合到Android或iOS應用中
將Web支援從開發版轉變為beta版;將MacOS支援納入開發版本
推出新工具DartPad(DartPad 是一個可以讓你在任何現代化的瀏覽器中體驗 Dart 程式語言線上工具)

1.17版本 2020/05/06
減少18.5%應用體積
提升了20%-37%導航效能
降低了40% iOS動畫CPU/GPU使用率
增加對谷歌字型的支援:fonts.google.com
完成對Type Scale部分的重構,符合 2018 Material 設計規範
提升了iOS 50%渲染速度(iPhone5s+、iOS10+支援Metal 渲染);不完全支援扔使用OpenGL渲染

1.20版本 2020/08/05
增強了UTF-8解碼
pubspec.yaml外掛不再支援舊格式
在Visual Studio Code中預覽嵌入式Dart DevTools
引入新的混編外掛-Pigeon,可以在Dart方法中直接呼叫Java/Objective-C/Kotlin/Swift方法並傳遞非原始資料物件。

1.22版本 2020/10/01
增加應用體積分析工具
提供了國際化和本地化工具,並實現了熱過載支援
支援Android 11;支援新的螢幕型別 (如挖孔屏和瀑布屏),以及同步Android 11動畫效果
支援iOS 14、Xcode 12新圖示以及對新iOS 14 App Clips功能的預覽支援;預設模板版本從8.0升級到9.0
可正式使用的 Google Maps 和 WebView 外掛,將 Android 和 iOS 系統的原生介面元件託管在 Flutter 應用中

2.0版本 2021/03/03
Web支援從測試版轉變為穩定版
除了HTML渲染,增加了CanvasKit渲染,桌面端瀏覽器會預設呼叫CanvasKit版本,移動端的瀏覽器會呼叫HTML版本。
混合開發多flutter例項(經測試iOS平臺存在記憶體問題)
桌面平臺的支援(beta)
Google Mobile Ads(Beta)
Dart 2.12 增加了空安全

2.2版本 2021/05/18
更好的iOS、Android、Web跨平臺支援
Dart 2.13 更新,引入Type aliases
Flutter Web 提升穩定性
優化iOS端渲染動畫幀時間、實現了增量iOS安裝,縮短更新安裝時間。
Android中引入延遲元件,允許Flutter應用在執行時下載包含提前編譯的程式碼模組,減少初始安裝大小。

招聘資訊

短視訊研發部,負責好看視訊、全民小視訊以及多款創新APP的孵化研發工作。是公司級戰略產品,承擔百度系產品矩陣短視訊內容供給任務,重點支援百度搜尋和資訊流視訊化,肩負百度內容生態視訊化轉型使命。僅用兩年的時間就實現使用者規模從零到億級增長,日活數千萬。擁有百億級流量,億級資料量,豐富新奇和全面的產品玩法,多型別的技術系統和領先的技術架構。

歡迎加入短視訊研發部,社招,實習,校招都要哦

簡歷投遞郵箱:geektalk@baidu.com (投遞備註【短視訊】)

推薦閱讀

百度信譽認證中臺架構解析

圖資料庫在百度漢語中的應用

|從lowcode看下一代前端應用框架

---------- END ----------

百度Geek說

百度官方技術公眾號上線啦!

技術乾貨 · 行業資訊 · 線上沙龍 · 行業大會

招聘資訊 · 內推資訊 · 技術書籍 · 百度周邊

歡迎各位同學關注

相關文章