赞
踩
Flutter 项目 调用一些原生的功能!用的比较多的就是第三方插件,因为比较简单
官方 《Flutter实战》
原生项目中部分页面使用Flutter,这种也是比较常见的。
FLutter本身定位的是开发一个完整的App应用。所以要是只让其做成一个页面的话有些功能是不支持的。Flutter本身有自己的渲染引擎,如果是小项目用Flutter就不划算,只有非常大型的项目将其部分或者全部页面用Flutter来实现。
详细代码参见Demo
创建出来的工程看一下
打开文件路径可以去看一下,是隐藏文件
隐藏的目的是,官方不希望我们对这些文件进行操作。我们开发的是一个Flutter页面是能运行的,这里的ios 和 Android 文件只是为了让我们做测试用的。当我们把这个Flutter写好之后,是要集成到原生项目中使用的,并不需要那些隐藏的内容。
打开终端输入命令:(由于系统不一样,有可能无效,可以自行上网查阅)
1、显示隐藏文件/文件夹
$ defaults write com.apple.finder AppleShowAllFiles -boolean true ; killall Finder
2、隐藏隐藏文件/文件夹
$ defaults write com.apple.finder AppleShowAllFiles -boolean false ; killall Finder
要让Flutter和我们的iOS项目产生管理,使用pod进行管理
先生成Podfiile 文件,直接打开终端,cd 打开iOS项目路径。pod init ,pod install。之后就可以再Xcode 里面查看了。
liujilou@liujiloudeMacBook-Pro ~ % cd /Users/liujilou/Desktop/code/AiOSFlutterModule/NativeDemo
liujilou@liujiloudeMacBook-Pro NativeDemo % pod init
liujilou@liujiloudeMacBook-Pro NativeDemo % pod install
然后用Xcode 打开工程,编辑Podfile 文件。这里的引用格式的Flutter官网提供的,可以去查阅
flutter_application_path = '../flutter_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')platform :ios, '9.0'
target 'NativeDemo' do
install_all_flutter_pods(flutter_application_path)
use_frameworks!
end
注意:这个Flutter 的路径是相对路径,如果修改改了路径,这里记得要改变
重新 pod install
这样就关联成功了,一些 Flutter 的内容就安装进去了
这个时候我们也可以到ViewController 里面试一下是否成功了。
#import <Flutter/Flutter.h> 头文件能导入就说明成功了
iOS里面先创建2个按钮,然后去打开Flutter页面
- - (void)viewDidLoad {
- [super viewDidLoad];
- self.view.backgroundColor = [UIColor whiteColor];
-
- CGSize viewSize = self.view.frame.size;
-
- UIButton * button1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
- button1.frame = CGRectMake((viewSize.width-100)/2, 100, 100, 40);
- button1.backgroundColor = [UIColor orangeColor];
- button1.tag = 1001;
- [button1 setTitle:@"按钮一" forState:(UIControlStateNormal)];
- [button1 addTarget:self action:@selector(pushFlutter:) forControlEvents:(UIControlEventTouchUpInside)];
- [self.view addSubview:button1];
-
-
- UIButton * button2 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
- button2.frame = CGRectMake((viewSize.width-100)/2, 200, 100, 40);
- button2.backgroundColor = [UIColor greenColor];
- button2.tag = 1002;
- [button2 setTitle:@"按钮二" forState:(UIControlStateNormal)];
- [button2 addTarget:self action:@selector(pushFlutter:) forControlEvents:(UIControlEventTouchUpInside)];
- [self.view addSubview:button2];
-
- }
-
- -(void)pushFlutter:(UIButton *)btn
- {
- NSString * pageIndex = @"one";
- NSString * page = @"one_page";
- if (btn.tag == 1002) {
- pageIndex = @"two";
- page = @"two_page";
- }
- // 不要每次都去alloc init 一个新的FLutter,这样非常消耗性能
- FlutterViewController * vc = [[FlutterViewController alloc] init];
- [vc setInitialRoute:pageIndex];//初始化传给Flutter的值
- [self presentViewController:vc animated:YES completion:nil];
- }
我们这里用的是跟iOS一样的调用方式,每次点击按钮的时候去重新创建Flutter页面。
如果全屏显示Flutter页面,在Flutter中要做回退。我们先不做全屏显示,先演示后面做
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
-
- void main() => runApp(MyApp(
- //window 需要导入import 'dart:ui';
- //window.defaultRouteName 拿到的就是 iOS中写的 [vc setInitialRoute:@"one"];带过来的值 one
- pageIndex: window.defaultRouteName));
-
- // ----------------------------------------------------
- class MyApp extends StatelessWidget {
- final String pageIndex;
-
- const MyApp({Key key, this.pageIndex}) : super(key: key);
-
- @override
- Widget build(BuildContext context) {
- return MaterialApp(
- title: 'Flutter Demo',
- theme: ThemeData(
- primarySwatch: Colors.blue,
- ),
- home: rootPage(pageIndex),
- );
- }
-
- rootPage(String pageIndex) {
- switch (pageIndex) {
- case 'one':
- return Scaffold(
- appBar: AppBar(title: Text(pageIndex)),
- body: Center(
- child: RaisedButton(
- onPressed: () {
- // 直接退出页面了,一般不会这么做。
- MethodChannel('one_page').invokeMapMethod('exit');
- },
- child: Text(pageIndex),
- )));
- case 'two':
- return Scaffold(
- appBar: AppBar(title: Text(pageIndex)),
- body: Center(
- child: RaisedButton(
- onPressed: () {
- MethodChannel('two_page').invokeMapMethod('exit');
- },
- child: Text(pageIndex),
- )),
- );
- }
- }
- }
但是Flutter 创建之后是一直存在在内存中的,而且非常大
通过上图可以看到,一个空的Flutter都非常大。而且每次创建的话Flutter页面的数据不能保存。因为Flutter要有自己的渲染引擎,不能像iOS的页面一样这样创建,所以建议全局建一个 Flutter 引擎。
- @property (nonatomic, strong) FlutterEngine * flutterEngine;
- @property (nonatomic, strong) FlutterViewController * flutterVC;
Flutter 引擎
- - (FlutterEngine *)flutterEngine
- {
- if (!_flutterEngine) {
- //这里不要直接用 _flutterEngine ,然后 _flutterEngine.run 因为在页面将要显示的时候才去执行运行,那么Flutter的页面显示的会非常慢
- //_flutterEngine = [[FlutterEngine alloc] initWithName:@"liujilou"];
- //定义一个局部变量,判断一下如果这个flutterEngine已经运行起来了,那么我们的全局_flutterEngine就等于这个 flutterEngine 。失败的话就返回nil
-
- FlutterEngine * flutterEngine = [[FlutterEngine alloc] initWithName:@"liujilou"];
- if (flutterEngine.run) {//Flutter 运行运行起来了
- _flutterEngine = flutterEngine;
- }
- }
- return _flutterEngine;
- }
- - (void)viewDidLoad {
- [super viewDidLoad];
- self.view.backgroundColor = [UIColor whiteColor];
-
- self.flutterVC = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil];
- self.flutterVC.modalPresentationStyle = UIModalPresentationFullScreen;//模态展示风格(全屏显示)
- -(void)pushFlutter:(UIButton *)btn
- {
- NSString * pageIndex = @"one";
- NSString * page = @"one_page";
- if (btn.tag == 1002) {
- pageIndex = @"two";
- page = @"two_page";
- }
-
- [self presentViewController:self.flutterVC animated:YES completion:nil];
- }
// 直接这样写就会出错,崩溃.
window.defaultRouteName 是空的
为什么是空的呢?因为在run的时候就去运行了Flutter,但是这一个时候并没有去传值。
可以通过单独运行Flutter 代码看一下值,和报错信息
调试
因为 [vc setInitialRoute:pageIndex]; 传值是在初始化的时候传的,我们现在不每次都去创建Flutter了所以需要改一下。Flutter也不能通过 window.defaultRouteName 来取值了。
修改Flutter代码
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
-
- void main() => runApp(_MyApp());
-
- // ----------------------------------------
- class _MyApp extends StatefulWidget {
- @override
- __MyAppState createState() => __MyAppState();
- }
-
- class __MyAppState extends State<_MyApp> {
- String pageIndex = 'one';
-
- // 这里是解码器,互相调用
- final MethodChannel _oneChannel = MethodChannel('one_page');
- final MethodChannel _twoChannel = MethodChannel('two_page');
-
- @override
- void initState() {
- // 调用方法一次接收信息
- _oneChannel.setMethodCallHandler((call) {
- setState(() {
- pageIndex = call.method;
- });
- return null;
- });
-
- _twoChannel.setMethodCallHandler((call) {
- setState(() {
- pageIndex = call.method;
- });
- return null;
- });
- }
-
- @override
- Widget build(BuildContext context) {
- return MaterialApp(
- title: 'Flutter Demo',
- theme: ThemeData(
- primarySwatch: Colors.blue,
- ),
- home: rootPage(pageIndex),
- );
- }
-
- rootPage(String pageIndex) {
- switch (pageIndex) {
- case 'one':
- return Scaffold(
- appBar: AppBar(title: Text(pageIndex)),
- body: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: <Widget>[
- RaisedButton(
- onPressed: () {
- // 直接退出页面了,一般不会这么做。
- MethodChannel('one_page').invokeMapMethod('exit');
- },
- child: Text(pageIndex),
- ),
- ],
- ));
- case 'two':
- return Scaffold(
- appBar: AppBar(title: Text(pageIndex)),
- body: Center(
- child: RaisedButton(
- onPressed: () {
- MethodChannel('two_page').invokeMapMethod('exit');
- },
- child: Text(pageIndex),
- )),
- );
- }
- }
- }
1、FlutterMethodChannel 调用方法(method invocation)一次通讯
//下面的这里两种都是持续通讯的
2、 FlutterBasicMessageChannel :传递字符串&半结构化的信息
3、FlutterEventChannel :用于数据流(stream)的通讯
完整的代码
- // ViewController.m
- // NativeDemo
- //
- // Created by liujilou on 2020/6/28.
- // Copyright © 2020 liujilou. All rights reserved.
- //
-
-
- // FlutterMethodChannel 调用方法(method invocation)一次通讯
- //下面的这里两种都是持续通讯的
- // FlutterBasicMessageChannel :传递字符串&半结构化的信息
- // FlutterEventChannel :用于数据流(stream)的通讯
-
- #import "ViewController.h"
- #import <Flutter/Flutter.h>
-
- @interface ViewController ()
-
- @property (nonatomic, strong) FlutterEngine * flutterEngine;
- @property (nonatomic, strong) FlutterViewController * flutterVC;
- @property (nonatomic, strong) FlutterBasicMessageChannel * msgChannel;
-
- @end
-
- @implementation ViewController
-
- - (FlutterEngine *)flutterEngine
- {
- if (!_flutterEngine) {
- //这里不要直接用 _flutterEngine ,然后 _flutterEngine.run 因为在页面将要显示的时候才去执行运行,那么Flutter的页面显示的会非常慢
- //_flutterEngine = [[FlutterEngine alloc] initWithName:@"liujilou"];
- //定义一个局部变量,判断一下如果这个flutterEngine已经运行起来了,那么我们的全局_flutterEngine就等于这个 flutterEngine 。失败的话就返回nil
-
- FlutterEngine * flutterEngine = [[FlutterEngine alloc] initWithName:@"liujilou"];
- if (flutterEngine.run) {//Flutter 运行运行起来了
- _flutterEngine = flutterEngine;
- }
- }
- return _flutterEngine;
- }
-
- - (void)viewDidLoad {
- [super viewDidLoad];
- self.view.backgroundColor = [UIColor whiteColor];
-
- self.flutterVC = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil];
- self.flutterVC.modalPresentationStyle = UIModalPresentationFullScreen;//模态展示风格(全屏显示)
-
- // 接收Flutter 的数据
- // 这里因为messenger 需要 FlutterBinaryMessenger 类型所以报警告
- self.msgChannel = [FlutterBasicMessageChannel messageChannelWithName:@"messageChannel" binaryMessenger:self.flutterVC];
-
- [self.msgChannel setMessageHandler:^(id _Nullable message, FlutterReply _Nonnull callback) {
- NSLog(@"收到Flutter 的%@",message);
- }];
-
-
- CGSize viewSize = self.view.frame.size;
-
- UIButton * button1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
- button1.frame = CGRectMake((viewSize.width-100)/2, 100, 100, 40);
- button1.backgroundColor = [UIColor orangeColor];
- button1.tag = 1001;
- [button1 setTitle:@"按钮一" forState:(UIControlStateNormal)];
- [button1 addTarget:self action:@selector(pushFlutter:) forControlEvents:(UIControlEventTouchUpInside)];
- [self.view addSubview:button1];
-
-
- UIButton * button2 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
- button2.frame = CGRectMake((viewSize.width-100)/2, 200, 100, 40);
- button2.backgroundColor = [UIColor greenColor];
- button2.tag = 1002;
- [button2 setTitle:@"按钮二" forState:(UIControlStateNormal)];
- [button2 addTarget:self action:@selector(pushFlutter:) forControlEvents:(UIControlEventTouchUpInside)];
- [self.view addSubview:button2];
-
- }
-
-
- -(void)pushFlutter:(UIButton *)btn
- {
- NSString * pageIndex = @"one";
- NSString * page = @"one_page";
- if (btn.tag == 1002) {
- pageIndex = @"two";
- page = @"two_page";
- }
- // 不要每次都去alloc init 一个新的FLutter,这样非常消耗性能
- // FlutterViewController * vc = [[FlutterViewController alloc] init];
-
-
- // 直接这样写就会出错,奔溃.
- // window.defaultRouteName 是空的
- // FlutterViewController * vc = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil];
- //
- // vc.modalPresentationStyle = UIModalPresentationFullScreen;//模态跳转页面全屏显示
-
- // 问题就出来 Route,Route在run之后,所以Flutter收不到初始化的数据,这里就不用Route 了。
- // 因为Route本身就是在初始化的时候传值用的,我们既然不想让每次都去重新创建,那就不会每次都初始化所以这里用Route 就不合适了
- // [vc setInitialRoute:pageIndex];//初始化的时候带过去的值
-
- // [self presentViewController:vc animated:YES completion:nil];
-
- // 使用Channel 通道传值
- FlutterMethodChannel * methodChannel = [FlutterMethodChannel methodChannelWithName:page binaryMessenger:self.flutterVC];
- // 发送消息
- [methodChannel invokeMethod:pageIndex arguments:nil];
-
- [self presentViewController:self.flutterVC animated:YES completion:nil];
-
- // 监听 Flutter 回调回来的参数
- [methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
- if ([call.method isEqualToString:@"exit"]) {
- [self.flutterVC dismissViewControllerAnimated:YES completion:nil];
- }
- }];
- }
-
- - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
- {
- static int a = 0;
- [self.msgChannel sendMessage:[NSString stringWithFormat:@"%d",a++]];
- }
-
- @end
做持续通讯
iOS点击屏幕,就向Flutter发送消息。将a 值传过去
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
-
- void main() => runApp(_MyApp()
- // MyApp(
- // window 需要导入import 'dart:ui';
- // window.defaultRouteName 拿到的就是 iOS中写的 [vc setInitialRoute:@"one"];带过来的值 one
- // pageIndex: window.defaultRouteName
- // )
- );
-
- // ----------------------------------------------------
- class MyApp extends StatelessWidget {
- final String pageIndex;
-
- const MyApp({Key key, this.pageIndex}) : super(key: key);
-
- @override
- Widget build(BuildContext context) {
- return MaterialApp(
- title: 'Flutter Demo',
- theme: ThemeData(
- primarySwatch: Colors.blue,
- ),
- home: rootPage(pageIndex),
- );
- }
-
- rootPage(String pageIndex) {
- switch (pageIndex) {
- case 'one':
- return Scaffold(
- appBar: AppBar(title: Text(pageIndex)),
- body: Center(
- child: RaisedButton(
- onPressed: () {
- // 直接退出页面了,一般不会这么做。
- MethodChannel('one_page').invokeMapMethod('exit');
- },
- child: Text(pageIndex),
- )));
- case 'two':
- return Scaffold(
- appBar: AppBar(title: Text(pageIndex)),
- body: Center(
- child: RaisedButton(
- onPressed: () {
- MethodChannel('two_page').invokeMapMethod('exit');
- },
- child: Text(pageIndex),
- )),
- );
- }
- }
- }
-
- // ----------------------------------------
- class _MyApp extends StatefulWidget {
- @override
- __MyAppState createState() => __MyAppState();
- }
-
- class __MyAppState extends State<_MyApp> {
- String pageIndex = 'one';
-
- // 这里是解码器,互相调用
- final MethodChannel _oneChannel = MethodChannel('one_page');
- final MethodChannel _twoChannel = MethodChannel('two_page');
- // 这个是通讯,也需要一个解码器
- final BasicMessageChannel _messageChannel =
- BasicMessageChannel('messageChannel', StandardMessageCodec());
-
- @override
- void initState() {
- // 可以持续接收信息
- _messageChannel.setMessageHandler((message) {
- print('收到了来自iOS的:$message');
- return null;
- });
-
- // 调用方法一次接收信息
- _oneChannel.setMethodCallHandler((call) {
- setState(() {
- pageIndex = call.method;
- });
- return null;
- });
-
- _twoChannel.setMethodCallHandler((call) {
- setState(() {
- pageIndex = call.method;
- });
- return null;
- });
- }
-
- @override
- Widget build(BuildContext context) {
- return MaterialApp(
- title: 'Flutter Demo',
- theme: ThemeData(
- primarySwatch: Colors.blue,
- ),
- home: rootPage(pageIndex),
- );
- }
-
- rootPage(String pageIndex) {
- switch (pageIndex) {
- case 'one':
- return Scaffold(
- appBar: AppBar(title: Text(pageIndex)),
- body: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: <Widget>[
- RaisedButton(
- onPressed: () {
- // 直接退出页面了,一般不会这么做。
- MethodChannel('one_page').invokeMapMethod('exit');
- },
- child: Text(pageIndex),
- ),
- TextField(
- // 输入框写数据,向iOS发送数据
- onChanged: (String str) {
- _messageChannel.send(str);
- },
- )
- ],
- ));
- case 'two':
- return Scaffold(
- appBar: AppBar(title: Text(pageIndex)),
- body: Center(
- child: RaisedButton(
- onPressed: () {
- MethodChannel('two_page').invokeMapMethod('exit');
- },
- child: Text(pageIndex),
- )),
- );
- }
- }
- }
Flutter 和原生页面不要频繁来回切换,内存消耗会非常大。
同时Flutter 销毁是不会完全销毁的,所以就不要去销毁了,就整体保存一份引擎避免重复创建。
混合开发
可以利用FLutter 作为项目的主要开发框架
也可以如上,将FLutter作为一个业务,iOS做框架。但是还是那句话不要频繁的来回切换。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。