混合开发的方式

说到混合开发,我们知道无非是两种:

  1. Flutter调用原生的功能,比如调用相册呀之类的
  2. 原生项目的某一些功能或者页面使用Flutter(不太建议),不建议的原因是它不像Web页面可以轻量级的嵌入,Flutter会比较重,因为它的独立渲染引擎。

Flutter调用原生

我们用之前的微信Demo来做示例点击头像的时候,唤醒原生的相册。
image.png
之前的样式大概长这样,在点击的时候唤起原生相机

  1. GestureDetector(
  2. onTap: () {
  3. print('需要唤起原生的相机');
  4. _methodChannel.invokeMapMethod('takePhotos');
  5. },
  6. child: Container(
  7. width: 70,
  8. height: 70,
  9. decoration: BoxDecoration(
  10. image:
  11. DecorationImage(image: AssetImage('images/游戏2.png')),
  12. borderRadius: BorderRadius.circular(10)),
  13. ),
  14. ),

在Flutter和iOS中通讯提供了一个专门的类MethodChannel用来通信

  1. MethodChannel _methodChannel = MethodChannel('mine/method');

onTap的时候调用_methodChannel.invokeMapMethod('takePhotos');告诉iOS原生我向你发起了一个takePhotos的方法,iOS你在接收到这个标识的时候要给我调起你自己的相机哦~

此时我们来到iOS的项目中:

  1. 首先示例化FlutterMethodChannel这个类,该类的第一个参数是指明哪个路径下的方法
  2. 调用setMethodCallHandler来响应Flutter发出来的讯息
  3. 识别当前的方法针对处理 ```swift import UIKit import Flutter

@UIApplicationMain @objc class AppDelegate: FlutterAppDelegate {

  1. var pickVc = UIImagePickerController()
  2. var flutterChannel: FlutterMethodChannel!
  3. override func application(
  4. _ application: UIApplication,
  5. didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  6. ) -> Bool {
  7. GeneratedPluginRegistrant.register(with: self)
  8. let vc = self.window.rootViewController as? FlutterViewController
  9. pickVc.delegate = self
  10. // 在iOS端也初始话这个通讯的类
  11. flutterChannel = FlutterMethodChannel.init(name: "mine/method", binaryMessenger: vc as! FlutterBinaryMessenger)
  12. // 对Flutter端发过来的通讯处理
  13. flutterChannel.setMethodCallHandler { call, result in
  14. // 如果我识别了这个方法是`takePhotos` 我就唤起原生相机
  15. if call.method.isEqual("takePhotos"){
  16. print("方法来了");
  17. self.pickVc.delegate = self
  18. vc?.present(self.pickVc, animated: true, completion: nil)
  19. }
  20. }
  21. return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  22. }

}

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/21860540/1642065636921-3a30030e-289f-4734-8cf0-b1c5af67202c.png#clientId=u7083d3b4-9461-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=690&id=u8accf184&margin=%5Bobject%20Object%5D&name=image.png&originHeight=844&originWidth=399&originalType=binary&ratio=1&rotation=0&showTitle=false&size=291952&status=done&style=none&taskId=ud35ed226-c142-4d75-84da-fee1774e49f&title=&width=326)
  2. 4. 当然啦,在iOS中选中了照片,把照片中的信息再回传给Flutter中,调用`invokeMethod`回传
  3. ```swift
  4. extension AppDelegate: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
  5. func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
  6. if let imagePathURL = info[UIImagePickerController.InfoKey.imageURL] as? NSURL,
  7. let imagePath = imagePathURL.absoluteString {
  8. self.flutterChannel.invokeMethod("editedImage", arguments: imagePath)
  9. }
  10. picker.dismiss(animated: true, completion: nil)
  11. }
  12. func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
  13. picker.dismiss(animated: true, completion: nil)
  14. }
  15. }

5.Flutter中收到iOS原生端选择的图片之后,也响应setMethodCallHandler

  1. @override
  2. void initState() {
  3. // TODO: implement initState
  4. super.initState();
  5. _methodChannel.setMethodCallHandler((call) {
  6. if (call.method == 'editedImage') {
  7. print(call.arguments);
  8. }
  9. return call.arguments;
  10. });
  11. }

image.png
至此,两端的通讯已经完成了,我们可以使用拿到的照片路径替换当前的图片了。

  1. @override
  2. void initState() {
  3. // TODO: implement initState
  4. super.initState();
  5. _methodChannel.setMethodCallHandler((call) {
  6. if (call.method == 'editedImage') {
  7. print(call.arguments);
  8. setState(() {
  9. String subString = call.arguments.toString().substring(7);
  10. _avatarFile = File(subString);
  11. });
  12. }
  13. return call.arguments;
  14. });
  15. }
  16. BoxDecoration(
  17. image: DecorationImage(
  18. image: _avatarFile == null
  19. ? AssetImage('images/游戏2.png') as ImageProvider
  20. : FileImage(_avatarFile!),
  21. fit: BoxFit.cover),
  22. borderRadius: BorderRadius.circular(10)
  23. )

image.png

image_picker

还有一种方式唤醒相机,这里使用到了Flutter官方出品的一个库image_picker
image.png
我们安装之后可以参考一下用法,本文的使用还是比较简单的

  1. void _pickerImage() async {
  2. try {
  3. XFile? file = await _picker.pickImage(source: ImageSource.gallery);
  4. if (file != null && mounted) {
  5. setState(() {
  6. _avatarFile = File(file.path);
  7. });
  8. }
  9. } catch (e) {
  10. print(e.toString());
  11. setState(() {
  12. _avatarFile = null;
  13. });
  14. }
  15. }

其中这里有一个小bug,当我们选择模拟器中的第一个相片的时候;会报错,所以在此时加了异常处理
image.png
温馨提示:记得要在iOS的info.plist文件中配置相册的权限!