Flutter 与原生交互的实现方式

395 阅读3分钟

这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战

Flutter 框架既提供了与原生交互的接口,也支持原生项目嵌入 Flutter。虽然支持,但是 Flutter 其实不建议在原生项目中嵌入 Flutter,因为 Flutter 需要渲染引擎的支持,会比较重量级,不像嵌入 Web 页这么轻便。下面以 Flutter 调起原生相册为例,我们介绍一下 Flutter 调起原生的方式。

image.png

自己实现 Flutter 调起原生相册

  • Flutter 代码
MethodChannel _methodChannel = MethodChannel('mine_page/method');
File? _avatarFile;

void initState() {
    super.initState();
    _methodChannel.setMethodCallHandler((call) {
      if (call.method == 'imagePath') {
        setState(() {
          //获取图片本地路径并进行截取
          String imagePath = call.arguments.toString().substring(7);
          _avatarFile = File(imagePath);
        });
      }
      return Future((){});
    });
GestureDetector(
              onTap: () {
                _methodChannel.invokeMapMethod('picture');
              },
              child: Container(
                width: 70,
                height: 70,
                // 设置圆角属性
                decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(10),
                    image: DecorationImage(
                        image: (_avatarFile == null)
                            ? AssetImage('images/ChenXi.JPG') as ImageProvider : FileImage(_avatarFile ?? File('')),
                    )
                ),
              ),
            ),
  • iOS 代码实现
@interface AppDelegate () <UINavigationControllerDelegate, UIImagePickerControllerDelegate>

/// methodChannel
@property (nonatomic, strong, nullable) FlutterMethodChannel *methodChannel;

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [GeneratedPluginRegistrant registerWithRegistry:self];
    
    FlutterViewController *vc = (FlutterViewController *)self.window.rootViewController;
    self.methodChannel = [FlutterMethodChannel methodChannelWithName:@"mine_page/method" binaryMessenger:vc];
    UIImagePickerController *pickerVc = [[UIImagePickerController alloc] init];
    pickerVc.delegate = self;
    [self.methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {        if ([call.method isEqualToString:@"picture"]) {
            [vc presentViewController:pickerVc animated:YES completion:nil];
        }
    }];
    
    // Override point for customization after application launch.
    return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *)info {
    [picker dismissViewControllerAnimated:YES completion:^{        NSString *imagePath = [NSString stringWithFormat:@"%@",info[@"UIImagePickerControllerImageURL"]];
        [self.methodChannel invokeMethod:@"imagePath" arguments:imagePath];
    }];
}

当我们想用 Flutter 调起原生相册,并且更换头像的话我们需要经过以下几个步骤:

  1. Flutter 与原生交互的时候借助一个类 MethodChannel,所以 Flutter 代码中首先我们定义了一个变量 _methodChannel,并传入字符串 mine_page/method,就是一个标识
  2. 为头像添加点击实现,并调用 _methodChannelinvokeMapMethod 方法,传入 picturepicture 就是作为打开相册的标识,可以我们自己随意定义。
  3. 同样在 oc 代码中我们也要定义一个 FlutterMethodChannel 类型的属性 methodChannel,调用 methodChannelWithName 方法创建 methodChannel 对象,传入的第一个参数要与 Flutter 中的字符保持一致,第二个参数我们传的是 window 的根控制器。
  4. 实现 setMethodCallHandler 方法,当 Flutter 中头像点击事件执行的时候就会调用 setMethodCallHandler 中的回调,这里我们判断 call.method 是否为 picture
  5. block 中调起 UIImagePickerController,并把 pickerVc 的代理设置为 self
  6. 实现选中图片的代理方法,并在代理方法中执行 [self.methodChannel invokeMethod:@"imagePath" arguments:imagePath],这里 imagePath 为图片的本地路径。这里原生也是调用 invokeMethod 方法与 Flutter 进行通讯。
  7. Flutter 代码中实现 setMethodCallHandler 方法,当第 6 步执行完后就会执行闭包,在这里可以获取到图片的路径,把路径传给 File 类,创建变量 _avatarFile。最后调用 setState 方法,刷新页面。
  8. 头像部件中 image 属性进行判断,当 _avatarFile 有值的时候就显示本地相册选中图片,否则就使用默认图片。

使用三方组件实现调起相册功能

image.png

Flutter 官方为我们提供了一个三方组件 image_picker 来实现调起相册的功能,这里我们用 image_picker 来实现一下。

// 头像
            GestureDetector(
              onTap: () {
                _pickImage();
              },
void _pickImage() async {
    try {
      XFile? file = await ImagePicker().pickImage(source: ImageSource.gallery);
      setState(() {
        _avatarFile = File(file?.path ?? '');
      });
    }catch (e) {
      print(e.toString());
      setState(() {
        _avatarFile = null;
      });
    }
  }

使用三方组件的话我们只需要实现 _pickImage 方法中的这些代码就可以实现相册选择的功能,而且原生工程不需要改代码,但是需要注意的是 iOS 原生项目需要配置 info.plist 中的相册权限。使用三方库的好处就是运行项目的时候会先执行 pod install,把原生相关的代码给下载到工程,三方库中不光有 dart 代码,而且也有原生相关的代码。