Flutter学习资源汇总持续更新中....
常用三方类库
flutter_lints - Flutter团队推荐的Flutter相关规则集
cupertino_icons - 加载苹果风格的图标
ios_platform_images - share images between Flutter and iOS
flutter_boost - 新一代Flutter-Native混合解决方案
flustars - Flutter常用工具类库,包括日期、时间、正则、log日志、json转换、屏幕相关
shared_preferences - 本地数据存取插件 ,在Android上它是基于 SharePreferences的,在iOS上它是基于 NSUserDefaults
flutter_boost
随着Flutter的发展,国内越来越多的App开始使用Flutter。为了降低风险,大部分App采用渐进式方式引入Flutter,在App里选几个页面用Flutter来编写,但都碰到了相同的问题,在原生页面和Flutter页面共存的情况下,如何管理路由? 官方没有提供这样的解决方案,而FlutterBoost就是为了解决这个问题而生。
FlutterBoost的使命是让开发者非常简单的在原生App中开发Flutter页面。
很多Flutter开发者只会一端,只会Android 或者只会IOS,但他需要接入双端,所以双端统一能降低他的 学习成本和接入成本。FlutterBoost3.0,在设计上 Android和IOS都做了对齐,特别接口上做到了参数级的对齐。
新一代Flutter-Native混合解决方案。 FlutterBoost是一个Flutter插件,它可以轻松地为现有原生应用程序提供Flutter混合集成方案。FlutterBoost的理念是将Flutter像Webview那样来使用。在现有应用程序中同时管理Native页面和Flutter页面并非易事。 FlutterBoost帮你处理页面的映射和跳转,你只需关心页面的名字和参数即可(通常可以是URL)。
将FlutterBoost添加到你的Flutter工程依赖中
flutter_boost:
git:
url: 'https://github.com/alibaba/flutter_boost.git'
ref: 'v3.0-release.2'
之后在flutter工程下运行flutter pub get dart端就集成完毕了
将FlutterBoost集成到Android部分和iOS部分
将FlutterBoost添加到你的iOS工程依赖中
首先到自己的iOS目录下,执行一次pod install
yushuhuideMacBook-Pro:wargame_cloudgame_ios yushuhui$ pod install
Analyzing dependencies
Downloading dependencies
Installing FlutterPluginRegistrant 0.0.1
Installing flutter_boost (0.0.2)
Installing ios_platform_images (0.0.1)
Installing path_provider_ios (0.0.1)
Installing shared_preferences_ios (0.0.1)
Generating Pods project
Integrating client project
Pod installation complete! There are 31 dependencies from the Podfile and 45 total pods installed.
进行准备工作创建FlutterBoostDelegate。 这里面的内容是完全可以自定义的,在您了解各个API的含义时,你可以完全自定义这里面每个方法的代码,下面只是给出大多数场景的默认解法
import flutter_boost
class BoostDelegate: NSObject,FlutterBoostDelegate {
static let shared = BoostDelegate()
///您用来push的导航栏
var navigationController:UINavigationController?
var flutterVC:FBFlutterViewContainer?
///用来存返回flutter侧返回结果的表
var resultTable:Dictionary<String,([AnyHashable:Any]?)->Void> = [:];
func pushNativeRoute(_ pageName: String!, arguments: [AnyHashable : Any]!) {
//可以用参数来控制是push还是pop
let isPresent = arguments["isPresent"] as? Bool ?? false
let isAnimated = arguments["isAnimated"] as? Bool ?? true
//这里根据pageName来判断生成哪个vc,这里给个默认的了
var targetViewController = UIViewController()
if(isPresent){
self.navigationController?.present(targetViewController, animated: isAnimated, completion: nil)
}else{
self.navigationController?.pushViewController(targetViewController, animated: isAnimated)
}
}
func pushFlutterRoute(_ options: FlutterBoostRouteOptions!) {
let vc:FBFlutterViewContainer = FBFlutterViewContainer()
vc.setName(options.pageName, uniqueId: options.uniqueId, params: options.arguments,opaque: options.opaque)
self.flutterVC = vc
//用参数来控制是push还是pop
let isPresent = (options.arguments?["isPresent"] as? Bool) ?? false
let isAnimated = (options.arguments?["isAnimated"] as? Bool) ?? true
//对这个页面设置结果
resultTable[options.pageName] = options.onPageFinished;
//如果是present模式 ,或者要不透明模式,那么就需要以present模式打开页面
if(isPresent || !options.opaque){
self.navigationController?.present(vc, animated: isAnimated, completion: nil)
}else{
self.navigationController?.pushViewController(vc, animated: isAnimated)
}
}
func popRoute(_ options: FlutterBoostRouteOptions!) {
//如果当前被present的vc是container,那么就执行dismiss逻辑
if let vc = self.navigationController?.presentedViewController as? FBFlutterViewContainer,vc.uniqueIDString() == options.uniqueId{
//这里分为两种情况,由于UIModalPresentationOverFullScreen下,生命周期显示会有问题
//所以需要手动调用的场景,从而使下面底部的vc调用viewAppear相关逻辑
if vc.modalPresentationStyle == .overFullScreen {
//这里手动beginAppearanceTransition触发页面生命周期
self.navigationController?.topViewController?.beginAppearanceTransition(true, animated: false)
vc.dismiss(animated: true) {
self.navigationController?.topViewController?.endAppearanceTransition()
}
}else{
//正常场景,直接dismiss
vc.dismiss(animated: true, completion: nil)
}
}else{
self.navigationController?.popViewController(animated: true)
}
//否则直接执行pop逻辑
//这里在pop的时候将参数带出,并且从结果表中移除
if let onPageFinshed = resultTable[options.pageName] {
onPageFinshed(options.arguments)
resultTable.removeValue(forKey: options.pageName)
}
}
}
在AppDelegate的didFinishLaunchingWithOptions方法中进行初始化
//创建代理,做初始化操作
import flutter_boost
let flutterBoostDelegate = BoostDelegate()
FlutterBoost.instance().setup(application, delegate: flutterBoostDelegate, callback: { engine in
})
iOS原生跳转到flutter模块的settingPage页面
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let flutterBtn = UIButton()
flutterBtn.backgroundColor = ColorConst.color_e8f4
flutterBtn.layer.cornerRadius = 5
flutterBtn.layer.masksToBounds = true
flutterBtn.setTitle("toFlutterVC", for: .normal)
flutterBtn.setTitleColor(ColorConst.color_28, for: .normal)
flutterBtn.addTarget(self, action: #selector(clickToSettingVC), for: .touchUpInside)
flutterBtn.frame = CGRect(x: 100, y: 100, width: 100, height: 100)
view.addSubview(flutterBtn)
}
@objc func clickToSettingVC() {
// 创建options,进行open操作的构建
let options = FlutterBoostRouteOptions()
options.pageName = "settingPage"
options.arguments = ["data": "settingPage", "isPresent": false, "isAnimated": true]
// 这个是push操作完成的回调,而不是页面关闭的回调!!!!
options.completion = { _ in
print("open operation is completed")
}
// 这个是页面关闭并且返回数据的回调,回调实际需要根据您的Delegate中的popRoute来调用
options.onPageFinished = { [weak self] dic in
if let data = dic?["data"] as? String {
print("settingPage return data is: \(data)")
}
}
BoostDelegate.shared.navigationController = UIViewController.current.navigationController
BoostDelegate.shared.pushFlutterRoute(options)
}
}
flutter run 配置iOS证书
vim ~/.flutter_settings
{
"ios-signing-cert": "Apple Development: XXXX@gmail.com (XXXXXXXX)",
"enable-macos-desktop": true
}
单独调试某个flutter页面示例,无需经过集成到原生工程
Widget appBuilder(Widget home) {
return const MaterialApp(
home: MySettingPage(), // 这里是需要调试的页面的名称
// home: home,
debugShowCheckedModeBanner: false,
///必须加上builder参数,否则showDialog等会出问题
// builder: (_, __) {
// return home;
// },
);
将FlutterBoost添加到你的android工程依赖中,原生和flutter通信的各种情况如下:
-
Android 原生携带参数打开flutter页面
HashMap<String, Object> map = new HashMap<>();//传递给flutter的参数map map.put("data","1"); map.put("UserName","2"); map.put("HeaderUrl","3"); map.put("CurrUserId","4"); FlutterBoostRouteOptions options = new FlutterBoostRouteOptions.Builder() .pageName("settingPage") //settingPage打开的flutter页面的名称 .arguments(map) .requestCode(1111) .build(); FlutterBoost.instance().open(options);
flutter接收传递的数据:
Map<String, FlutterBoostRouteFactory> routerMap = { 'settingPage': (settings, uniqueId) { return CupertinoPageRoute( settings: settings, builder: (_) { Map<String, Object> map = settings.arguments as Map<String, Object>; print("收到原生传递的消息:"+ map.toString()); String data = map['data'] as String; print("收到原生传递的消息 data:"+ data.toString()); return MySettingPage( data: data, ); }); }, };
-
flutter携带参数打开Android原生页面
var user = {
'data' : 'number',
'age': 'turtledoves'
};
MainActivity:打开Android原生的目标页面。user传递的参数
Android原生接收:
先发送消息到原生,原生收到消息后,进行分发打开原生界面:
EventListener listener = (key, args) -> {
Log.d("EventListener", "onCreate: args ----key是:" + key+ "--------args是: "+ args);
if (key.equals("event")){
// Map<Object, Object> args
if (args.get("data").equals("Logout")){
startActivity(new Intent(this, MainActivity.class));
}
}
}
};
remover = FlutterBoost.instance().addEventListener("event", listener);
-
flutter向Android原生发送消息
发送的key是data,value是一个map集合 BoostChannel.instance.sendEventToNative("event", {'data': "UserInfo"});
Android原生接收消息:
EventListener listener = (key, args) -> {Log.d("EventListener", "onCreate: args ----key是:" + key+ "--------args是: "+ args); };
ListenerRemover remover = FlutterBoost.instance().addEventListener("event", listener);
注意://最后在清理的时候移除监听(比如onDestroy中) remover.remove();
-
Android向Flutter发送消息
Map<Object,Object> map = new HashMap<>(); map.put("key","value"); FlutterBoost.instance().sendEventToFlutter("eventToFlutter",map);
flutter端接收:
```
///声明一个用来存回调的对象
VoidCallback removeListener;
///添加事件响应者,监听native发往flutter端的事件
removeListener = BoostChannel.instance.addEventListener("yourEventKey", (key, arguments) {
///deal with your event here
return;
});
///然后在退出的时候(比如dispose中)移除监听者
removeListener?.call();
```