Flutter插件开发指南:实现原生视图与交互

Viewed 0

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));
  }
}

上面的代码根据运行平台选择使用AndroidViewUiKitViewviewType是视图标识符,必须与原生端保持一致。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端PlatformViewviewType参数保持一致。

此外,为了在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插件的基本开发流程,包括原生视图的嵌入和双向通信。

0 Answers