專案目錄
flutter_app
├── android # 安卓目錄
├── build # 構建目錄
├── ios # iOS 目錄
├── lib # 開發目錄(相當於 src 目錄)
| ├── main.dart # 入口檔案(相當於 index.js)
├── test # 測試目錄
├── .gitignore # Git 提交時,設定忽略檔案內容
├── pubspec.lock # 專案依賴鎖定資訊(相當於 npm 中的 package-lock.json)
└── pubspec.yaml # 專案依賴配置(相當於 npm 中的 package.json)
複製程式碼
入口檔案
Flutter 專案的入口檔案是 lib/main.dart
, 該檔案中有一個入口方法。
入口方法
// 入口方法
void main() {
// 具體內容
}
複製程式碼
根函式
void main() {
runApp(
// 具體內容
);
}
複製程式碼
runApp 函式接收一個元件,並使其成為元件樹的根,框架會強制根元件覆蓋整個螢幕。
UI庫 Material
import 'package:flutter/material.dart';
複製程式碼
Mater 是一種標準的移動端和 Web 端的 UI 框架,是一套 Google 的設計規範,Flutter 專案以 Material 為 UI 基礎。
- 官網:www.material.io
- 中文網:material-io.cn
Widget(元件)
Flutter 中的一切內容都是元件,在 Flutter 當作元件一般分為以下兩類。
-
StatelessWidget
無狀態元件,狀態不可改變的 Widget
-
StatefulWidget
有狀態元件,持有的狀態,可能在 Widget 生命週期改變,如果我們想改變頁面中的資料,就需要用到 StatefulWidget 。
自定義元件
為了增加程式碼的可讀性,我們可以將部分程式碼分離出去,寫成獨立的 Widget。我們自定義的 Widget 需要繼承 Flutter 提供的元件,繼承的元件中有一個 build 方法,需要將我們實現的程式碼放到 build 方法中。
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text(
'Hello Flutter',
textDirection: TextDirection.ltr,
),
),
);
}
}
複製程式碼
MaterialApp
欄位 | 型別 |
---|---|
navigatorKey(導航主鍵) | GlobalKey |
home(起始頁) | Widget |
routes(路由列表) | Map<String, WidgetBuilder> |
initialRoute(初始路由名稱) | String |
onGenerateRoute(生成路由) | RouteFactory |
onUnknownRoute(未知路由) | RouteFactory |
navigatorObservers(導航觀察器) | List |
builder(構造器) | TransitionBuilder |
title(應用標題) | String |
onGenerateTitle(生成應用標題) | GenerateAppTitle |
color(顏色) | Color |
theme(主題配置) | ThemeData |
locale(本地化) | Locale |
localizationsDelegates(本地化委託代理) | Iterable |
localeResolutionCallback(本地化分辨回撥) | LocaleResolutionCallback |
supportedLocales(應用支援區域) | Iterable |
debugShowMaterialGrid(是否顯示 Material 網格) | bool |
showPerformanceOverlay(顯示效能監控疊層) | bool |
checkerboardRasterCacheImages(棋盤格光柵快取影像) | bool |
checkerboardOffscreenLayers(棋盤格層) | bool |
showSemanticsDebugger(顯示語義偵錯程式) | bool |
debugShowCheckedModeBanner(是否顯示 DEBUG 橫幅) | bool |
Scaffold
Scaffold 是 Flutter 應用的腳手架,用來搭建 Flutter 專案的基本佈局結構。
-
appBar
顯示在介面頂部的一個 Appbar,也就是 Android 中的 ActionBar、Toolbar
-
body
當前介面的主體 Widget
-
floatingActionButton
紙墨設計中所定義的 FAB,介面的主要功能按鈕
-
......
App 結構
常用文字元件
- Container
- child(宣告子元件)
- padding/margin
- EdgeInsets.all()
- EdgeInsets.fromLTRB()
- EdgeInsets.only()
- decoration
- BoxDecoration(邊框、圓角、漸變、陰影、背景色、背景圖片)
- alignment
- Alignment (內容對齊)
- transform
- Matrix4(平移,旋轉,縮放,斜切)
- Column
- Column 中的主軸方向是垂直佈局
- mainAxisAlignment:主軸對齊方式
- crossAxisAlignment:交叉軸對齊方式
- Row
- Row 中的主軸方向是水平方向,其他屬性和 Column 一致
- Text(用來顯示文字的元件,它是最常用的元件)
- TextDirection(文字方向)
- TextStyle(文字樣式)
- Colors(文字顏色)
- FontWeight(字型粗細)
- FontStyles(字型樣式)
- TextAlign(文字對齊)
- TextOverflow(文字溢位)
- maxLines(指定顯示的行數)
- RichText
- 如果一段文字需要顯示不同的樣式,
Text
元件無法滿足我們的需求,這個時候需要使用RichText
。
- 如果一段文字需要顯示不同的樣式,
- TextSpan
- 類似 html 中的 span 標籤,TextSpan 和 RichText 結合使用可以實現不同的樣式佈局。
第三方元件
dio
-
dio 是一個強大的 Dart Http 請求庫(類似axios)
-
使用步驟
- 在pubsepc.yaml 中新增 dio 依賴
- 安裝依賴 (pub get | flutter packages get | vs code 中儲存配置,自動下載)
- 引入
import ‘package:do/dio.dart’;
- 使用:pub.dev/documentati…
Flutter_swiper
- Flutter 中最好的輪播元件,適配 Android 和 iOS
- 使用步驟
- 在 pubsepc.yaml 中新增 flutter_swiper 依賴
- 安裝依賴 (pub get | flutter packages get | vs code 中儲存配置,自動下載)
- 引入
import ‘package:flutter_swiper/flutter_swiper.dart’;
- 使用
生命週期
- initState() 元件物件插入到元素樹中時
- didChangeDependencies() 當前狀態物件的依賴改變時
- build() 元件渲染時
- setState() 元件物件的內部狀態改變時候
- didUpdateWidget() 元件配置更新時
- deactivate() 元件物件在元素樹中暫時移除時
- dispose() 元件物件在元素樹中永遠移除時
路由與導航
路由簡介
-
Route
一個路由是一個螢幕或頁面的抽象
-
Navigator
- 管理路由的元件,Navigator 可以通過路由入棧和出棧來實現頁面之間的跳轉
- 常用屬性
- initalRoute:初始化路由,既預設的頁面
- onGenerateRoter:根據規則匹配動態路由
- onUnknownRoute:未知路由,也就是404
- routes:路由集合
匿名路由
-
Navigator
-
push(跳轉到指定元件)
Navigator.push( context, MaterialPageRoute( builder: (context) => Demo(), ), ); 複製程式碼
-
pop(回退)
Navigator.pop(context); 複製程式碼
-
命名路由
-
宣告路由
- routes 路由表(map型別)
- initalRoute(初始路由)
- onUnknownRoute(未知路由)
-
跳轉到命名路由
Navigator.pushNamed(context,'路由名稱'); 複製程式碼
-
配置如下
return MaterialApp( title: 'Flutter Demo', routes: { 'home':(context)=>Home(), 'demo': (context) => Demo(), }, // initialRoute: 'home', onUnknownRoute: (RouteSettings setting) => MaterialPageRoute( builder: (context) => UnknownPage(), ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); 複製程式碼
動態路由
-
動態路由是指通過 onGenerateRoute 屬性指定的路由,它的原理是通過傳過來的路由進行動態的匹配。
MaterialApp( title: 'Flutter Demo', onGenerateRoute: (RouteSettings setting) { print('當前路徑:' + setting.name); if (setting.name == '/') { return MaterialPageRoute(builder: (context) => Home()); } if (setting.name == 'demo') { return MaterialPageRoute(builder: (context) => Demo()); } var uri = Uri.parse(setting.name); print(uri.pathSegments); if (uri.pathSegments.length == 2 && uri.pathSegments.first == 'demo') { var id = uri.pathSegments[1]; return MaterialPageRoute(builder: (context) => Demo(id: id)); } return MaterialPageRoute(builder: (context) => UnknownPage()); } ); 複製程式碼
螢幕適配
-
螢幕適配
- 螢幕尺寸五花八門,要保證一個應用在不同的終端上表現一致
-
flutter_screenutil
-
適配原理
- 設計稿尺寸(例如:iphone6 的尺寸是 750*1334)
- 終端尺寸(通過 window 或 MediaQuery.of(context) 獲得)
- 將設計稿尺寸,在終端上進行放大或縮小
-
設計尺寸
- designWidth:750px
- designHeight:1334px
-
終端尺寸(動態獲取)
- deviceWidth:1080px
- deviceHeight:1920px
-
縮放比例
- scaleWidth = deviceWidth / designWidth
- scaleHeight = deviceHeight / designHeight
-
安裝
-
初始化設計尺寸
ScreenUtilInit(designSize: Size(375, 667),...); 複製程式碼
-
設定適配尺寸
- Flutter 1.2 之前
- width: ScreenUtil().setWidth(100);
- Height:ScreenUtil().setHeight(100);
- Flutter 1.2 之後
- width: 100.w
- Height: 100.h
- Flutter 1.2 之前
混合開發
嵌入原生 View
-
在 Runner 目錄下建立 iOS View,此 View 繼承 FlutterPlatformView ,返回一個簡單的 UILabel :
// // MyFlutterView.swift // Runner // // Created by 悟空 on 2021/5/31. // import Foundation import Flutter class MyFlutterView: NSObject,FlutterPlatformView { let label = UILabel() init(_ frame: CGRect,viewID: Int64,args :Any?,messenger :FlutterBinaryMessenger) { super.init() if(args is NSDictionary){ let dict = args as! NSDictionary label.text = dict.value(forKey: "text") as! String } } func view() -> UIView { return label } } 複製程式碼
-
建立 MyFlutterViewFactory 類
// // MyFlutterViewFactory.swift // Runner // // Created by 悟空 on 2021/5/31. // import Foundation import Flutter class MyFlutterViewFactory: NSObject,FlutterPlatformViewFactory { var messenger:FlutterBinaryMessenger init(messenger:FlutterBinaryMessenger) { self.messenger = messenger super.init() } func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView { return MyFlutterView(frame,viewID: viewId,args: args,messenger: messenger) } func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol { return FlutterStandardMessageCodec.sharedInstance() } } 複製程式碼
-
在 AppDelegate 中註冊
import UIKit import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) let registrar:FlutterPluginRegistrar = self.registrar(forPlugin: "plugins.flutter.io/custom_platform_view_plugin")! let factory = MyFlutterViewFactory(messenger: registrar.messenger()) registrar.register(factory, withId: "plugins.flutter.io/custom_platform_view") return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } 複製程式碼
-
在 Flutter/lib 目錄下新建 PlatformViewDemo 類
import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class PlatformViewDemo extends StatelessWidget { @override Widget build(BuildContext context) { Widget platformView() { if (defaultTargetPlatform == TargetPlatform.iOS) { return UiKitView( viewType: 'plugins.flutter.io/custom_platform_view', creationParams: {'text': '我是 IOS 原生 View'}, creationParamsCodec: StandardMessageCodec(), ); } return null; } return Scaffold( appBar: AppBar(), body: Center( child: platformView(), ), ); } } 複製程式碼
與原生通訊
Flutter 與 Native 端通訊一共有以下三種方式:
- MethodChannel:Flutter 與 Native 端相互呼叫,呼叫後可以返回結果,可以 Native 端主動呼叫,也可以Flutter主動呼叫,屬於雙向通訊。此方式為最常用的方式, Native 端呼叫需要在主執行緒中執行。
- BasicMessageChannel:用於使用指定的編解碼器對訊息進行編碼和解碼,屬於雙向通訊,可以 Native 端主動呼叫,也可以Flutter主動呼叫。
- EventChannel:用於資料流(event streams)的通訊, Native 端主動傳送資料給 Flutter,通常用於狀態的監聽,比如網路變化、感測器資料等。
MethodChannel
Flutter 端建立 MethodChannel 通道,用於與原生端通訊:
// com.flutter.guide.MethodChannel 是 MethodChannel 的名稱,原生端要與之對應。
var channel = MethodChannel('com.flutter.guide.MethodChannel');
複製程式碼
傳送訊息:
var result = await channel.invokeMethod('sendData',{'name': 'xiazanzhang', 'age': 18})
複製程式碼
- 第一個參數列示method,方法名稱,原生端會解析此引數。
- 第二個參數列示引數,型別任意,多個引數通常使用Map。
- 返回 Future,原生端返回的資料。
使用如下:
-
在 Runner 目錄下建立 MethodChannelDemo 類,內容如下
// // MethodChannelDemo.swift // Runner // // Created by 悟空 on 2021/5/31. // import Flutter import UIKit public class MethodChannelDemo { var count = 0 var channel:FlutterMethodChannel init(messenger: FlutterBinaryMessenger) { channel = FlutterMethodChannel(name: "com.flutter.guide.MethodChannel", binaryMessenger: messenger) channel.setMethodCallHandler { (call:FlutterMethodCall, result:@escaping FlutterResult) in if (call.method == "sendData") { if let dict = call.arguments as? Dictionary<String, Any> { let name:String = dict["name"] as? String ?? "" let age:Int = dict["age"] as? Int ?? -1 result(["name":"hello,\(name)","age":age]) } } } startTimer() } func startTimer() { var timer = Timer.scheduledTimer(timeInterval:1, target: self, selector:#selector(self.tickDown),userInfo:nil,repeats: true) } @objc func tickDown(){ count += 1 var args = ["count":count] channel.invokeMethod("timer", arguments:args) } } 複製程式碼
-
修改 App Delegate 類
import UIKit import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) // 嵌入原生 view let registrar:FlutterPluginRegistrar = self.registrar(forPlugin: "plugins.flutter.io/custom_platform_view_plugin")! // Flutter 向 iOS View 傳送訊息 let factory = MyFlutterViewFactory(messenger: registrar.messenger()) registrar.register(factory, withId: "plugins.flutter.io/custom_platform_view") // MethodChannel:與原生通訊 let controller : FlutterViewController = window?.rootViewController as! FlutterViewController MethodChannelDemo(messenger: controller.binaryMessenger) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } 複製程式碼
-
在 Flutter 專案中新建 MethodChannelDemo 類
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class MethodChannelDemo extends StatefulWidget { @override _MethodChannelDemoState createState() => _MethodChannelDemoState(); } class _MethodChannelDemoState extends State<MethodChannelDemo> { var channel = MethodChannel('com.flutter.guide.MethodChannel'); var _data; var _count; @override void initState() { super.initState(); channel.setMethodCallHandler((call) { setState(() { _count = call.arguments['count']; }); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: Column( children: [ SizedBox( height: 50, ), ElevatedButton( child: Text('傳送資料到原生'), onPressed: () async { var result = await channel .invokeMethod('sendData', {'name': 'xiazanzhang', 'age': 18}); var name = result['name']; var age = result['age']; setState(() { _data = '$name,$age'; }); }, ), Text('原生返回資料:$_data'), Text('接收原生主動傳送資料:$_count'), ], ), ); } } 複製程式碼
BasicMessageChannel
Flutter 端建立 MethodChannel 通道,用於與原生端通訊:
// com.flutter.guide.BasicMessageChannel 是 BasicMessageChannel 的名稱,原生端要與之對應。
var channel = BasicMessageChannel('com.flutter.guide.BasicMessageChannel',StandardMessageCodec());
複製程式碼
傳送訊息:
var result = await channel.send({'name': 'xiazanzhang', 'age': 18});
複製程式碼
- 引數型別任意,多個引數通常使用Map。
- 返回 Future,原生端返回的資料。
使用如下:
-
在 Runner 目錄下建立 MethodChannelDemo 類,內容如下
// // BasicMessageChannelDemo.swift // Runner // // Created by 悟空 on 2021/5/31. // import Flutter import UIKit public class BasicMessageChannelDemo { var channel:FlutterBasicMessageChannel init(messenger: FlutterBinaryMessenger) { channel = FlutterBasicMessageChannel(name: "com.flutter.guide.BasicMessageChannel", binaryMessenger: messenger) channel.setMessageHandler { (message, reply) in if let dict = message as? Dictionary<String, Any> { let name:String = dict["name"] as? String ?? "" let age:Int = dict["age"] as? Int ?? -1 reply(["name":"hello,\(name)","age":age]) } } } } 複製程式碼
-
修改 AppDelegate 類,在 application 方法中新增如下程式碼
// BasicMessageChannel: 與 Flutter 通訊 BasicMessageChannelDemo(messenger: controller.binaryMessenger) 複製程式碼
-
在 Flutter 專案中新建 BasicMessageChannelDemo 類
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class BasicMessageChannelDemo extends StatefulWidget { @override _BasicMessageChannelDemoState createState() => _BasicMessageChannelDemoState(); } class _BasicMessageChannelDemoState extends State<BasicMessageChannelDemo> { var channel = BasicMessageChannel( 'com.flutter.guide.BasicMessageChannel', StandardMessageCodec()); var _data; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: Column( children: [ SizedBox( height: 50, ), ElevatedButton( child: Text('傳送資料到原生'), onPressed: () async { var data = {'name': 'xiazanzhang', 'age': 18}; var result = await channel.send(data) as Map; var name = result['name']; var age = result['age']; setState(() { _data = '$name,$age'; }); }, ), Text('原生返回資料:$_data'), ], ), ); } } 複製程式碼
EventChannel
Flutter 端建立 EventChannel 通道,用於與原生端通訊:
// com.flutter.guide.EventChannel 是 EventChannel 的名稱,原生端要與之對應。
var _eventChannel = EventChannel('com.flutter.guide.EventChannel');
複製程式碼
監聽原生端傳送的訊息:
var _data;
@override
void initState() {
super.initState();
_eventChannel.receiveBroadcastStream().listen(_onData);
}
_onData(event){
setState(() {
_data = event;
});
}
複製程式碼
使用如下:
-
在 Runner 目錄下建立 MethodChannelDemo 類,內容如下
// // EventChannelDemo.swift // Runner // // Created by 悟空 on 2021/5/31. // import Flutter import UIKit public class EventChannelDemo:NSObject, FlutterStreamHandler{ var channel:FlutterEventChannel? var count = 0 var events:FlutterEventSink? public override init() { super.init() } convenience init(messenger: FlutterBinaryMessenger) { self.init() channel = FlutterEventChannel(name: "com.flutter.guide.EventChannel", binaryMessenger: messenger) channel?.setStreamHandler(self) startTimer() } func startTimer() { let timer = Timer.scheduledTimer(timeInterval:1, target: self, selector:#selector(self.tickDown),userInfo:nil,repeats: true) } @objc func tickDown(){ count += 1 let args = ["count":count] if(events != nil){ events!(args) } } public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { self.events = events return nil; } public func onCancel(withArguments arguments: Any?) -> FlutterError? { self.events = nil return nil; } } 複製程式碼
-
修改 AppDelegate 類,在 application 方法中新增如下程式碼
// EventChannel: 與 Flutter 通訊 EventChannelDemo(messenger: controller.binaryMessenger) 複製程式碼
-
在 Flutter 專案中新建 BasicMessageChannelDemo 類
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class EventChannelDemo extends StatefulWidget { @override _EventChannelDemoState createState() => _EventChannelDemoState(); } class _EventChannelDemoState extends State<EventChannelDemo> { var _eventChannel = EventChannel('com.flutter.guide.EventChannel'); var _data; @override void initState() { super.initState(); _eventChannel.receiveBroadcastStream().listen(_onData); } _onData(event) { setState(() { _data = event; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: Center( child: Text('監聽原生返回的資料:$_data'), ), ); } } 複製程式碼