Flutter 插件开发
在Flutter中,有时需要嵌入原生的视图组件。这可以通过PlatformView来实现。以下是一个创建平台视图的示例。
Dart端代码
首先,定义一个TestView widget,用于根据平台加载不同的原生视图。
class TestView extends StatefulWidget {
final String titleStr;
final TestViewCreatedCallback onCreated;
TestView({Key key, this.titleStr, this.onCreated}) : super(key: key);
@override
_TestViewState createState() => _TestViewState();
}
class _TestViewState extends State<TestView> {
@override
Widget build(BuildContext context) {
if (Platform.isAndroid) {
return AndroidView(
viewType: 'testView',
onPlatformViewCreated: onPlatformViewCreated,
creationParams: <String, dynamic>{
'titleStr': widget.titleStr,
},
creationParamsCodec: const StandardMessageCodec(),
);
} else if (Platform.isIOS) {
return UiKitView(
viewType: 'testView',
onPlatformViewCreated: onPlatformViewCreated,
creationParams: <String, dynamic>{
'titleStr': widget.titleStr,
},
creationParamsCodec: const StandardMessageCodec(),
);
} else {
return Text('当前平台不支持');
}
}
Future onPlatformViewCreated(int id) async {
if (widget.onCreated == null) {
return;
}
widget.onCreated(TestFlutterPluginDemo.init(id));
}
}
上面的代码根据运行平台选择使用AndroidView或UiKitView。viewType是视图标识符,必须与原生端保持一致。creationParams用于向原生视图传递初始化参数。onPlatformViewCreated回调在原生视图创建成功后触发,用于初始化通信控制器。
接下来,在主页面main.dart中加载这个视图。
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
var testFlutterPluginDemo;
@override
Widget build(BuildContext context) {
TestView testView = TestView(
onCreated: onTestViewCreated,
);
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Plugin example app')),
body: Column(
children: [
Container(
height: 200,
width: 400,
child: testView,
)
],
),
),
);
}
void onTestViewCreated(controller) {
this.testFlutterPluginDemo = controller;
}
}
至此,Dart端用于显示原生视图的主要代码已完成。
iOS原生端代码 (Objective-C)
首先,需要在Xcode项目的Development Pods目录下的Classes文件夹中进行开发。创建一个工厂类TestFlutterPluginViewFactory和一个视图类TestFlutterPluginView。
TestFlutterPluginViewFactory.h
#import <Foundation/Foundation.h>
#import <Flutter/Flutter.h>
@interface TestFlutterPluginViewFactory : NSObject<FlutterPlatformViewFactory>
- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;
@end
TestFlutterPluginViewFactory.m
#import "TestFlutterPluginViewFactory.h"
#import "TestFlutterPluginView.h"
@interface TestFlutterPluginViewFactory ()
@property(nonatomic, strong) NSObject<FlutterBinaryMessenger>* messenger;
@end
@implementation TestFlutterPluginViewFactory
- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger {
self = [super init];
if (self) {
self.messenger = messenger;
}
return self;
}
#pragma mark - FlutterPlatformViewFactory Delegate
- (NSObject<FlutterMessageCodec>*)createArgsCodec {
return [FlutterStandardMessageCodec sharedInstance];
}
- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
viewIdentifier:(int64_t)viewId
arguments:(id _Nullable)args {
TestFlutterPluginView *view = [[TestFlutterPluginView alloc] initWithFrame:frame
viewId:viewId
args:args
messenger:self.messenger];
return view;
}
@end
工厂类负责创建原生视图实例。
TestFlutterPluginView.h
#import <Foundation/Foundation.h>
#import <Flutter/Flutter.h>
@interface TestFlutterPluginView : NSObject<FlutterPlatformView>
- (instancetype)initWithFrame:(CGRect)frame
viewId:(int64_t)viewId
args:(id _Nullable)args
messenger:(NSObject<FlutterBinaryMessenger>*)messenger;
@end
TestFlutterPluginView.m
#import "TestFlutterPluginView.h"
@interface TestFlutterPluginView ()
@property (nonatomic, strong) FlutterMethodChannel *channel;
@end
@implementation TestFlutterPluginView {
CGRect _frame;
int64_t _viewId;
id _args;
NSObject<FlutterBinaryMessenger>* _messenger;
}
- (instancetype)initWithFrame:(CGRect)frame
viewId:(int64_t)viewId
args:(id _Nullable)args
messenger:(NSObject<FlutterBinaryMessenger>*)messenger {
if (self = [super init]) {
_frame = frame;
_viewId = viewId;
_args = args;
_messenger = messenger;
_channel = [FlutterMethodChannel methodChannelWithName:@"test_flutter_plugin_demo"
binaryMessenger:messenger];
__weak typeof(self) weakSelf = self;
[_channel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
[weakSelf onMethodCall:call result:result];
}];
}
return self;
}
- (UIView*)view {
UIView *nativeView = [[UIView alloc] initWithFrame:_frame];
nativeView.backgroundColor = [UIColor redColor];
return nativeView;
}
#pragma mark - Flutter交互监听
- (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
// 处理来自Flutter的方法调用
}
@end
视图类TestFlutterPluginView负责创建并返回实际的UIView,并通过FlutterMethodChannel建立与Flutter的通信。
最后,在插件的主类TestFlutterPluginDemoPlugin.m中注册这个视图工厂。
#import "TestFlutterPluginDemoPlugin.h"
#import "TestFlutterPluginViewFactory.h"
@implementation TestFlutterPluginDemoPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
TestFlutterPluginViewFactory *factory = [[TestFlutterPluginViewFactory alloc] initWithMessenger:registrar.messenger];
[registrar registerViewFactory:factory withId:@"testView"];
}
@end
注意,这里注册的视图ID @"testView" 必须与Dart端PlatformView的viewType参数保持一致。
此外,为了在iOS上允许使用FlutterPlatformViews,需要在Info.plist文件中添加以下配置:
<key>io.flutter.embedded_views_preview</key>
<true/>
完成以上步骤后运行应用,即可在Flutter界面中成功显示一个红色的原生iOS视图。
Flutter与原生交互
视图显示成功后,下一步是实现双向通信。首先在Dart端的控制器类TestFlutterPluginDemo中添加方法。
class TestFlutterPluginDemo {
MethodChannel _channel;
TestFlutterPluginDemo.init(int id) {
_channel = MethodChannel('test_flutter_plugin_demo');
_channel.setMethodCallHandler(platformCallHandler);
}
// Flutter 调用原生方法
Future<void> changeNativeTitle(String str) async {
return _channel.invokeMethod('changeNativeTitle', str);
}
// 监听原生方法的回调
Future<dynamic> platformCallHandler(MethodCall call) async {
switch (call.method) {
case "clickAction":
print('收到原生回调 ---- ${call.arguments}');
break;
}
}
}
然后,可以在main.dart中添加一个按钮来触发对原生方法的调用。
// 在 _MyAppState 的 build 方法中,body的Column里添加一个按钮
Column(
children: [
Container(
height: 200,
width: 400,
child: testView,
),
ElevatedButton(
onPressed: () {
if (testFlutterPluginDemo != null) {
testFlutterPluginDemo.changeNativeTitle('来自Flutter的新标题');
}
},
child: Text('调用原生方法'),
),
],
)
相应地,在iOS原生端的TestFlutterPluginView.m中,需要实现changeNativeTitle方法的处理,并可以主动调用Flutter。
// 在 onMethodCall:result: 方法中添加 case
- (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if ([call.method isEqualToString:@"changeNativeTitle"]) {
NSString *title = call.arguments;
NSLog(@"收到来自Flutter的标题: %@", title);
// 可以在这里更新原生视图,例如改变背景色
UIView *v = [self view];
v.backgroundColor = [UIColor blueColor];
result(@(YES)); // 返回结果给Flutter
} else {
result(FlutterMethodNotImplemented);
}
}
// 示例:原生主动调用Flutter的方法(可以在视图的某个触发事件中调用)
- (void)triggerFlutterMethod {
if (_channel) {
[_channel invokeMethod:@"clickAction" arguments:@"这是来自iOS原生的消息"];
}
}
通过以上步骤,就完成了Flutter插件的基本开发流程,包括原生视图的嵌入和双向通信。