项目目录
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'), ), ); } } 复制代码