目录

  • Flutter端
  • 原生端
  • 创建视图工厂并返回原生视图
  • 注册视图工厂对象
  • 修改项目配置
  • 运行结果


在我们开发Flutter应用时,时常会遇到以下问题:

  1. Flutter内置(或者第三方)提供的Widget不足以实现复杂交互
  2. 已经以原生方式实现了复杂的界面交互,只是想在Flutter应用中嵌套原生写好的视图

此时就需要在Flutter的Widget树种直接嵌入原生视图。本文以iOS为例,来讲解如何实现。

Flutter端

创建一个新的Flutter应用项目,在lib/main.dart中修改_MyHomePageState类的build方法,在界面中嵌入UiKitView,代码如下:

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          Expanded(
            flex: 1,
            child: Container(
              color: Colors.blue,
            ),
          ),
          Expanded(
            flex: 2,
            child: getMyPatformView(),
          ),
        ],
      ),
    );
  }

  Widget getMyPatformView() {
    if (defaultTargetPlatform == TargetPlatform.android) {
      return AndroidView(
        viewType: 'MyUiKitView',
      );
    } else if (defaultTargetPlatform == TargetPlatform.iOS) {
      return UiKitView(
        viewType: 'MyUiKitView',
      );
    }

    return Text('$defaultTargetPlatform is not yet supported by this plugin');
  }
}

注意到代码中的UiKitView,它用来嵌入iOS原生视图。根据Flutter官方文档,UiKitView控件会自动填所有可用区域,因此其父控件必须具有明确的区域尺寸。这里其宽度和屏幕宽度相同,高度为屏幕的2/3。其带有一个viewType属性,其值用于唯一对应某一类型的原生视图。Flutter端代码就是这些,下面切换到原生代码端。

原生端

原生端只需要做以下两件事:

  1. 创建视图工厂对象,并在工厂方法中返回需要的原生视图对象。
  2. 在AppDelegate对象的App启动方法中,使用viewType属性值注册视图工厂对象。

创建视图工厂并返回原生视图

打开Flutter项目根目录下的ios文件夹,双击Runner.xcworkspace打开原生项目。创建一个新的视图工厂类MyPlatformViewFactory,其需要继承自NSObject类,并遵从FlutterPlatformViewFactory协议,代码如下:
MyPlatformViewFactory.h:

#import <Foundation/Foundation.h>
#import <Flutter/Flutter.h>

NS_ASSUME_NONNULL_BEGIN

@interface MyPlatformViewFactory : NSObject <FlutterPlatformViewFactory>

@end

NS_ASSUME_NONNULL_END

MyPlatformViewFactory.m:

#import "MyPlatformViewFactory.h"
#import "MyPlatformViewObject.h"

@implementation MyPlatformViewFactory
- (NSObject <FlutterPlatformView> *)createWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id _Nullable)args
{
    MyPlatformViewObject *myPlatformViewObject = [[MyPlatformViewObject alloc] initWithFrame:frame viewId:viewId args:args];
    return myPlatformViewObject;
}

@end

FlutterPlatformViewFactory协议中的工厂方法createWithFrame:viewIdentifier:arguments:是一个必须实现的方法,该方法生成一个遵从FlutterPlatformView协议的对象并返回。FlutterPlatformView协议用于将原生视图嵌入到Flutter的widget树中,代码如下:
MyPlatformViewObject.h:

#import <Foundation/Foundation.h>
#import <Flutter/Flutter.h>

@interface MyPlatformViewObject : NSObject <FlutterPlatformView>
- (id)initWithFrame:(CGRect)frame viewId:(int64_t)viewId args:(id)args;
@end

MyPlatformViewObject.m:

#import <CoreGraphics/CoreGraphics.h>
#import "MyPlatformViewObject.h"

@implementation MyPlatformViewObject
{
    CGRect _frame;
    int64_t _viewId;
    id _args;
    UILabel *_subLabel;
}

- (id)initWithFrame:(CGRect)frame viewId:(int64_t)viewId args:(id)args
{
    if (self = [super init])
    {
        _frame = frame;
        _viewId = viewId;
        _args = args;
    }
    return self;
}

- (UIView *)view
{
    UIView *myNativeView = [[UIView alloc] initWithFrame:_frame];
    myNativeView.backgroundColor = [UIColor purpleColor];

    _subLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 20, 480, 44)];
    _subLabel.text = @"我是原生Label!!";
    _subLabel.textColor = [UIColor whiteColor];
    _subLabel.numberOfLines = 0;
    [myNativeView addSubview:_subLabel];

    UIButton *subBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    [subBtn setFrame:CGRectMake(20, 120, 200, 200)];
    [subBtn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
    [subBtn setTitle:@"点我试试!" forState:UIControlStateNormal];
    subBtn.titleLabel.font = [UIFont boldSystemFontOfSize:25.0];
    [subBtn addTarget:self action:@selector(subBtnClicked:) forControlEvents:UIControlEventTouchUpInside];
    [myNativeView addSubview:subBtn];

    return myNativeView;
}

- (void)subBtnClicked:(id)sender
{
    _subLabel.text = [NSString stringWithFormat:@"%@如果你觉得这篇文章有用,请给我点个赞吧!!", _subLabel.text];
}

@end

FlutterPlatformView协议中有一个必须实现的方法view,该方法用于真正生成原生视图树并返回根视图,这里创建了一个紫色背景的myNativeView,并添加了UILabel、UIButton及对应点击处理。

注册视图工厂对象

在AppDelegate的application:didFinishLaunchingWithOptions:方法内,创建MyPlatformViewFactory视图工厂对象并注册,代码如下:

#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
#import "MyPlatformViewFactory.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [GeneratedPluginRegistrant registerWithRegistry:self];
    MyPlatformViewFactory *myPlatformViewFactory = [[MyPlatformViewFactory alloc] init];
    [[self registrarForPlugin:@"Lipuzhi"] registerViewFactory:myPlatformViewFactory withId:@"MyUiKitView"];
    // Override point for customization after application launch.
    return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end

App启动后,调用registrarForPlugin:方法注册插件,要求插件名字唯一。然后调用registerViewFactory:withId:方法注册视图工厂,传入对应的工厂对象和Flutter端对应的viewType值即可。

修改项目配置

此时如果直接运行Flutter端,发现App无法显示原生视图内容,同时控制台报错:

Trying to embed a platform view but the PrerollContext does not support embedding.

因为在Flutter中嵌入原生视图的开销很大,默认情况下是不开放此功能的,因此需要手动修改info.plist文件。在Xcode中右键单击info.plist -> Open As -> Source Code,这样会以源代码方式打开:

flutter 显示ios原生页面 flutter 原生view_Flutter


在<dict>和</dict>区块内添加以下代码:

<key>io.flutter.embedded_views_preview</key>
    <true/>

保存后即可。

运行结果

回到Flutter端并运行,实际效果如图。

flutter 显示ios原生页面 flutter 原生view_#import_02


Demo源码地址:点击下载Demo

Android原生视图的嵌入,稍后会补充。