Flutter 中嵌入 Native 组件的解决方案
在 Native 到 Flutter 漫长的混合技术过渡期间,如果想要平滑过渡,使用 Flutter 中更完善的控件将是一个不错的选择。本文希望向大家介绍Androi dView的使用以及在此基础上扩展的双端嵌入Native组件的解决方案。
1。用户培训
1.1。 DemoRun
这种嵌入地图的场景在很多app中都可以存在,但是目前的地图SDK并没有提供Flutter库,自己开发一套地图显然是不可能的。现实。在这种情况下,使用混合堆栈是更好的选择。我们可以直接将地图插入到Native的绘图树中,但是该解决方案中嵌入的视图并不在Flutter的绘图树中。这是一种比较暴力和不优雅的方法,而且使用起来也非常费力。
此时,更优雅的解决方案是使用Flutter官方提供的Androi dView。这是一个嵌入在高德地图中的简单演示。下面我们就按照这个应用场景,来看看Androi dView的使用和实现原理。 ?您必须传递 viewType
。该字符串将用于唯一标识该小部件,并用于与Native的视图建立关联。
第二步:在原页面添加代码,编写PlatformViewFactory。 PlatformViewFactory的主要任务是在create()
方法中创建一个视图并发送给Flutter(这句话不太准确,但是我们可以这样理解,后面会解释)
第三步:使用registerViewFactory()
方法注册刚刚编写的PlatformViewFactory。该方法需要提交两个参数,第一个参数必须与之前在Flutter页面上写的viewType
匹配。第二个参数是刚才输入的PlatformViewFactory。
配置Amap地图的部分这里就省略了。官方有更详细的文档,可以在高德开发者平台查阅。
以上就是使用Androi dView的全部操作。整体看起来比较简单,但是到了实际使用的时候,还是有两个问题不能忽视:
- View的最终显示尺寸是谁决定的?
- 如何处理触摸事件?
让小闲鱼为你一一解答。
这是我的iOS开发交流群:519832104,无论你是初学者还是专家,都欢迎你加入。您可以分享经验、讨论技术、共同学习、成长!
还附上朋友收集的大公司面试题清单。他们需要 iOS 开发培训材料和真实的面试问题。您可以加入群组并自行下载!
2。原理解释
为了解决以上两个问题,我们首先要明白所谓“传递视觉”的本质是什么?
。 “过眼云烟”的本质是什么?
要解决这个问题,就不可避免地要阅读源码,从更深层次上看整个传输过程。我们可以整理出这样一个流程图:
我们可以看到Flutter最终收到的是原层返回的纹理ID。根据原生知识,这个纹理ID就是原始页面上已经渲染好的视图的绘制数据对应的ID。通过这个ID,可以直接在GPU中找到对应的绘图数据并使用。那么Flutter是如何使用这个ID的呢?羊毛布?
在之前深入了解Flutter界面开发时,我也给大家介绍了Flutter的绘制流程。这里我也给大家简单总结一下
Flutter的Framework层最终会向Engine层提交一个layerTree。层树中的每个叶子节点都会在管道中被遍历。每个叶子节点最终都会调用Skia引擎来完成界面元素。绘制,遍历完成后,调用glPresentRenderBuffer(IOS)或glSwapBuffer(Android)完成显示操作。
Layer有多种类型,Androi dView使用其中的TextureLayer。 TextureLayer在之前的《Flutter外接纹理》已经有比较详细的介绍了,这里不再赘述。当遍历TextureLayer时,会调用一个引擎层方法SceneBuilder::addTexture()
,并传入textureId作为参数。最后,当你绘制的时候,skia会根据textureId直接在GPU中找到对应的绘制数据并绘制到屏幕上。
那么谁拿到这个ID就可以进行这样的操作呢?答案当然是否定的。纹理数据存储在创建它的EGLContext对应的线程中,因此如果在其他线程中进行操作,则无法获取对应的数据。这里需要介绍几个概念:
- 显示对象(Display):提供有关屏幕像素密度和尺寸的合理信息
- 呈现:它给了Android相应的上下文(Context)和显示对象。在(屏幕)上绘图,通常用于两个屏幕的差异显示。
我们不会在这里解释演示。我们只需要了解Flutter是通过presentation来实现外部纹理的。创建Presentation时,会传入FlutterView对应的上下文以及创建的虚拟视图对象,这样Flutter就可以通过其ID直接找到。并使用Native创建的纹理数据。
2.2。谁决定视图的最终显示尺寸?
通过上面的过程,大家应该能够想象到,屏幕大小似乎是由两部分决定的:AndroidView的大小和Android端View的大小。那么谁真正决定呢?我们来做个实验吧?
直接新建一个Flutter项目,将中心改为Androi dView。
//Flutter
class _MyHomePageState extends State<MyHomePage> {
double size = 200.0;
void _changeSize() {
setState(() {
size =
});
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(),
),
body: Container(
color: Color(0xff0000ff),
child: SizedBox(
width: size,
height: size,
child: AndroidView(
viewType: 'testView',
),
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _changeSize,
child: new Icon(),
),
);
}
}
Android端也必须添加相应的代码。为了更好的看到裁剪效果,这里使用了ImageView。
//Android
@Override
public PlatformView create(final Context context, int i, Object o) {
final ImageView imageView = new ImageView(context);
(new ViewGroup.LayoutParams(500,500));
(().getDrawable());
return new PlatformView() {
@Override
public View getView() {
return imageView;
}
@Override
public void dispose() {
}
};
}
先看Androi的dView。AndroidView对应的RenderObject是RenderAndroi dView。有两种方法可以确定 RenderObject 的最终大小。一种是由父节点指定,另一种是由父节点指定。根据自己所在区域的情况确定尺寸。打开对应的源码可以看到有一个很重要的属性sizedByParent = true
,这意味着Androi dView的大小是由其父节点决定的。我们可以使用Container、SizedBox等控件来控制它。AndroidView的大小。
Androi dView的绘图数据是由Native层提供的,那么当Native渲染的View的实际像素大小大于Androi dView的大小时会发生什么情况呢?通常只有两种选择来处理这种情况,一种是裁剪,另一种是缩放。Flutter保持了一贯的做法。所有超出边界的小部件都以裁剪的方式显示。上述情况被认为是一种越界。
当这个view的实际像素大小小于Androi dView时,你会发现view并不会相应缩小(容器的背景色不会显露出来),没有内容的区域会被填充白色的 。原因是在 SingleViewPresentation::onCreate 中使用了 FrameLayout 作为 rootView。
。如何传递触摸事件
大家应该都熟悉Android的事件流程,就是自上而下的传递,自下而上的处理或者回流。 Flutter 也使用了这个规则,不过 Androi dView 通过两个类来处理动作:
MotionEventsDispatcher:负责将事件封装在 Native 的事件中,并传递给 Native;
Androi dViewGestureRecognizer:负责识别对应的手势,有两个属性:
所以总结一下,这部分过程其实很简单:从Native到Flutter的事件初始阶段不在本文的讨论范围之内。 Flutter 根据自己的规则处理事件。如果Androi dView赢得了该事件,则该事件将被封装在Native端相应的事件中,并通过方法通道发送回Native,Native将根据自己的事件处理规则进行处理。 ?这是新生态必须面对的主要矛盾。要解决这个问题,最简单的办法当然是让开发者使用旧生态系统中已经非常成熟的控件。当然,这可以暂时解决Flutter生态开发不完整的问题,但使用这种解决方案不可避免地需要编写双重代码(甚至iOS还没有相应的控件,但将来肯定会更新)。并且它不可能是完全横向的。 将事情放在上下文中:此解决方案存在性能错误。在AndroidView类的第三条评论中,官方已经提到这是一个避免使用Flutter控件的相对昂贵的解决方案。如果可能的话使用它。如果你之前看过《Flutter外接纹理》这篇文章,你应该知道,在Flutter实现外部纹理的方案中,从GPU->CPU->GPU对数据的处理是比较昂贵的,这在高使用场景下会造成明显的问题。性能错误。我们通过一些手段绕过了中间的CPU步骤,并将该技术实现在APP中进行图像资源处理。 ?按照Flutter的规则来存储资源当然是可以的,但是必然会增加包的大小,并且难以管理。 面对这个问题,我们的解决方案是学习Androi的dView使用Texture的思想并进行优化。 Native和Flutter的图片资源已经标准化。除了加载位于Native资源目录中的本地图像之外,Native的图像库还可以用于加载网络图像。cachedEvents
和forwardedPointers。只有当PointerEvent的指针属性在forwardedPointers中时才会部署,否则会存储在cacheEvents中。这里的实现主要是解决一些事件的冲突,比如滑动事件,可以通过gestureRecognizers来处理。你可以参考这里的官方评论。
/// For example, with the following setup vertical drags will not be dispatched to the Android view as the vertical drag gesture is claimed by the parent [GestureDetector].
///
/// GestureDetector(
/// onVerticalDragStart: (DragStartDetails d) {},
/// child: AndroidView(
/// viewType: 'webview',
/// gestureRecognizers: <OneSequenceGestureRecognizer>[],
/// ),
/// )
///
/// To get the [AndroidView] to claim the vertical drag gestures we can pass a vertical drag gesture recognizer in [gestureRecognizers] e.g:
///
/// GestureDetector(
/// onVerticalDragStart: (DragStartDetails d) {},
/// child: SizedBox(
/// width: 200.0,
/// height: 100.0,
/// child: AndroidView(
/// viewType: 'webview',
/// gestureRecognizers: <OneSequenceGestureRecognizer>[ new VerticalDragGestureRecognizer() ],
/// ),
/// ),
/// )
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。