目录
- Flutter端
- 原生端
- 创建视图工厂并返回原生视图
- 注册视图工厂对象
- 修改项目配置
- 运行结果
在我们开发Flutter应用时,时常会遇到以下问题:
- Flutter内置(或者第三方)提供的Widget不足以实现复杂交互
- 已经以原生方式实现了复杂的界面交互,只是想在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端代码就是这些,下面切换到原生代码端。
原生端
原生端只需要做以下两件事:
- 创建视图工厂对象,并在工厂方法中返回需要的原生视图对象。
- 在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,这样会以源代码方式打开:
在<dict>和</dict>区块内添加以下代码:
<key>io.flutter.embedded_views_preview</key>
<true/>
保存后即可。
运行结果
回到Flutter端并运行,实际效果如图。
Demo源码地址:点击下载Demo
Android原生视图的嵌入,稍后会补充。