当前位置:   article > 正文

Flutter Platform View 使用及原理简析

platformview使用


奇技指南

本文介绍以下几部分内容:

  1. 什么是 Flutter Platform View

  2. 如何使用

  3. 需要注意的点

  4. Flutter Framework 层代码简析

     本文来自公众号奇舞移动技术


什么是platform view ?

由于 Flutter 诞生于 Android 、iOS 非常成熟的时代背景,为了能让一些现有的 native 控件直接引用到 Flutter app 中,Flutter 团队提供了 AndroidView 、UIKitView 两个 widget 来满足需求,比如说 Flutter 中的 Webview、MapView,暂时无需使用 Flutter 重新开发一套。

其实 platform view 就是 AndroidView 和 UIKitView 的总称,允许将 native view 嵌入到了 flutter widget 体系中,完成 Dart 代码对 native view 的控制。



简单使用

此处仅是简单使用,有很多不合理的代码,目的仅是让初学者能完成展示,后面会有具体的 framework 代码分析,及官方维护的 platform view 的分析。

先看一下效

640?wx_fmt=jpeg

640?wx_fmt=png

存在与 native 交互的代码,建议用一个 plugin 来实现内部逻辑。

Plugin: exposing an Android or iOS API for developers


Flutter侧

创建 Flutter plugin (建议使用 Android Studio),如果使用命令行,可以执行如下命令:

flutter create --org net.loosash --template = plugin share_platform_plugin
接下来在我们的插件工程里创建一个 widget 用来包裹 platform view。 便于使用
  1. import 'package:flutter/cupertino.dart';
  2. import 'package:flutter/foundation.dart';
  3. import 'package:flutter/services.dart';
  4. /// 这里使用了 statelessWidget
  5. class PlatformTextWidget extends StatelessWidget {
  6. PlatformTextWidget({this.text});
  7. final String text;
  8. @override
  9. Widget build(BuildContext context) {
  10. // 根据运行平台判断执行代码
  11. if (defaultTargetPlatform == TargetPlatform.android) {
  12. return AndroidView(
  13. // 在 native 中的唯一标识符,需要与 native 侧的值相同
  14. viewType: "platform_text_view",
  15. // 在创建 AndroidView 的同时,可以传递参数
  16. creationParams: <String, dynamic>{"text": text},
  17. // 用来编码 creationParams 的形式,可选 [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec]
  18. // 如果存在 creationParams,则该值不能为null
  19. creationParamsCodec: const StandardMessageCodec(),
  20. );
  21. } else if (defaultTargetPlatform == TargetPlatform.iOS) {
  22. return UiKitView(
  23. viewType: "platform_text_view",
  24. creationParams: <String, dynamic>{"text": text},
  25. creationParamsCodec: const StandardMessageCodec(),
  26. );
  27. } else {
  28. return Text("不支持的平台");
  29. }
  30. }
  31. }


iOS 侧

在编辑Xcode中的iOS平台代码之前,首先确保代码至少已构建过一次。在创建的 plugin/example 目录下执行 build,如下:

  1. cd share_platform_plugin/example
  2. flutter build ios --no-codesign
  3. 或者执行 pod install
然后使用 Xcode 打开 shareplatformplugin/example/ios/Runner.xcworkspace,plugin 相关的代码目录很深,在 Pods/Development Pods/shareplatformplugin 内部,具体找到 SharePlatformPlugin.h 与 SharePlatformPlugin.m 目录即位我们操作的目录。

接下来我们先创建需要展示的 View ,这里仅以一个 UILabel 为例。

IOSTextView.h

  1. #import <Foundation/Foundation.h>
  2. #import <Flutter/Flutter.h>
  3. NS_ASSUME_NONNULL_BEGIN
  4. @interface IOSTextView : NSObject<FlutterPlatformView>
  5. - (instancetype)initWithFrame:(CGRect)frame
  6. viewIdentifier:(int64_t)viewId
  7. arguments:(id _Nullable)args
  8. binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;
  9. @end
  10. NS_ASSUME_NONNULL_END
IOSTextView.m
  1. #import <Foundation/Foundation.h>
  2. #import "IOSTextView.h"
  3. @implementation IOSTextView{
  4. int64_t _viewId;
  5. FlutterMethodChannel* _channel;
  6. UILabel * _uiLabel;
  7. }
  8. - (instancetype)initWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args binaryMessenger:(NSObject<FlutterBinaryMessenger> *)messenger{
  9. NSString *text = @"iOS端UILabel";
  10. if ([args isKindOfClass:[NSDictionary class]]) {
  11. NSDictionary *params = (NSDictionary *)args;
  12. if([[params allKeys] containsObject:@"text"]){
  13. if ([[params valueForKey:@"text"] isKindOfClass:[NSString class]]) {
  14. text= [params valueForKey:@"text"];
  15. }
  16. }
  17. }
  18. _uiLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
  19. _uiLabel.textAlignment = NSTextAlignmentCenter;
  20. _uiLabel.text = text;
  21. _uiLabel.font = [UIFont systemFontOfSize:30];
  22. return self;
  23. }
  24. -(UIView *)view{
  25. return _uiLabel;
  26. }
  27. @end
然后创建 FlutterPlatformViewFactory

SharePlatformViewFactory.h

  1. #import <Foundation/Foundation.h>
  2. #import <Flutter/Flutter.h>
  3. NS_ASSUME_NONNULL_BEGIN
  4. @interface SharePlatformViewFactory : NSObject<FlutterPlatformViewFactory>
  5. - (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messager;
  6. -(NSObject<FlutterMessageCodec> *)createArgsCodec;
  7. -(NSObject<FlutterPlatformView> *)createWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args;
  8. @end
  9. NS_ASSUME_NONNULL_END
SharePlatformViewFactory.m
  1. #import "SharePlatformViewFactory.h"
  2. #import "IOSTextView.h"
  3. @implementation SharePlatformViewFactory{
  4. NSObject<FlutterBinaryMessenger>*_messenger;
  5. }
  6. - (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger> *)messager{
  7. self = [super init];
  8. if (self) {
  9. _messenger = messager;
  10. }
  11. return self;
  12. }
  13. -(NSObject<FlutterMessageCodec> *)createArgsCodec{
  14. return [FlutterStandardMessageCodec sharedInstance];
  15. }
  16. -(NSObject<FlutterPlatformView> *)createWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args{
  17. IOSTextView *iosTextView = [[IOSTextView alloc] initWithFrame:frame viewIdentifier:viewId arguments:args binaryMessenger:_messenger];
  18. return iosTextView;
  19. }
  20. @end
接下来在 SharePlatformPlugin.m 中添加我们创建 SharePlatformViewFactory 的注册。
  1. #import "SharePlatformPlugin.h"
  2. #import "SharePlatformViewFactory.h"
  3. @implementation SharePlatformPlugin
  4. + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
  5. FlutterMethodChannel* channel = [FlutterMethodChannel
  6. methodChannelWithName:@"share_platform_plugin"
  7. binaryMessenger:[registrar messenger]];
  8. SharePlatformPlugin* instance = [[SharePlatformPlugin alloc] init];
  9. [registrar addMethodCallDelegate:instance channel:channel];
  10. // 添加注册我们创建的 view ,注意这里的 withId 需要和 flutter 侧的值相同
  11. [registrar registerViewFactory:[[SharePlatformViewFactory alloc] initWithMessenger:registrar.messenger] withId:@"platform_text_view"];
  12. }
  13. - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
  14. if ([@"getPlatformVersion" isEqualToString:call.method]) {
  15. result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]);
  16. } else {
  17. result(FlutterMethodNotImplemented);
  18. }
  19. }
  20. @end
最后,还需要在 Flutter 项目中的 ios/Runner/info.plist 中增加,就是运行 flutter 的项目
  1. <key>io.flutter.embedded_views_preview</key>
  2. <true/>

iOS侧就完成了。


Android 侧

直接使用 Android Studio 打开 plugin 中的 android 目录,shareplatformplugin/android

接下来我们先创建需要展示的 View ,这里仅以一个 TextView 为例。

AndroidTextView.kt

  1. class AndroidTextView(context: Context,
  2. messenger: BinaryMessenger,
  3. id: Int?,
  4. params: Map<String, Any>?) : PlatformView {
  5. private val mAndroidTextView: TextView = TextView(context)
  6. init {
  7. val text = params?.get("text") as CharSequence?
  8. mAndroidTextView.text = if (text == null) {
  9. text
  10. } else {
  11. "android端TextView"
  12. }
  13. mAndroidTextView.textSize = 30f
  14. }
  15. override fun getView(): View = mAndroidTextView
  16. override fun dispose() {}
  17. }
创建 SharePlatformViewFactory.kt
  1. class SharePlatformViewFactory(private val messenger: BinaryMessenger)
  2. : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
  3. override fun create(context: Context, id: Int, args: Any?): PlatformView {
  4. val params = args?.let { args as Map<String, Any> }
  5. return AndroidTextView(context, messenger, id, params)
  6. }
  7. }
最后,在 SharePlatformPlugin 中添加我们创建 SharePlatformViewFactory 的注册。
  1. class SharePlatformPlugin: MethodCallHandler {
  2. companion object {
  3. @JvmStatic
  4. fun registerWith(registrar: Registrar) {
  5. val channel = MethodChannel(registrar.messenger(), "share_platform_plugin")
  6. channel.setMethodCallHandler(SharePlatformPlugin())
  7. // 添加注册我们创建的 view ,注意这里的 withId 需要和 flutter 侧的值相同
  8. registrar.platformViewRegistry().registerViewFactory("platform_text_view", SharePlatformViewFactory(registrar.messenger()))
  9. }
  10. }
  11. override fun onMethodCall(call: MethodCall, result: Result) {
  12. if (call.method == "getPlatformVersion") {
  13. result.success("Android ${android.os.Build.VERSION.RELEASE}")
  14. } else {
  15. result.notImplemented()
  16. }
  17. }
  18. }

这样 Andorid 侧的代码就完成了。


Flutter项目中的使用

在 Flutter 工程中 pubspec.yaml 引入该 plugin 。

  1. dependencies:
  2. flutter:
  3. sdk: flutter
  4. cupertino_icons: ^0.1.2
  5. # 下面是对我们新建插件的依赖
  6. share_platform_plugin:
  7. path: ../share_platform_plugin
执行 Packages get
flutter package get
在需要展示的地方和正常的 widget 一样使用我们自己创建的 PlatformTextWidget
  1. import 'package:flutter/foundation.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter/services.dart';
  4. import 'package:share_platform_plugin/widget/platform_text_widget.dart';
  5. class TextPage extends StatelessWidget {
  6. @override
  7. Widget build(BuildContext context) {
  8. return Scaffold(
  9. appBar: AppBar(
  10. title: Text("native text"),
  11. ),
  12. body: SafeArea(
  13. child: Column(
  14. children: <Widget>[
  15. Text("这里是flutter的Text"),
  16. Expanded(
  17. child: PlatformTextWidget(text:"123"),
  18. ),
  19. Text("这里是flutter的Text"),
  20. ],
  21. ),
  22. ),
  23. );
  24. }
  25. }



发现问题

如果上面的代码你自己写一遍,你就会发现存在很多的问题。

1.id 在对应的端上没有被使用,可以用来做什么?

2.这里的 PlatformTextWidget 被 Expanded 包裹着,如果不包裹就会出现超出边界的错误,那么这个 Widget 的大小是怎么控制的呢?

3.platform view 的绘制是在 native 侧完成的还是在 flutter 侧完成的呢?

带着问题,我们看一遍源码,看看是否能找到相关的答案。



源码分析

就来看看 AndroidView 吧
  1. // 继承了 StatefulWidget
  2. class AndroidView extends StatefulWidget {
  3. const AndroidView({
  4. Key key,
  5. @required this.viewType,
  6. this.onPlatformViewCreated,
  7. this.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
  8. this.layoutDirection,
  9. this.gestureRecognizers,
  10. this.creationParams,
  11. this.creationParamsCodec,
  12. }) : assert(viewType != null),
  13. assert(hitTestBehavior != null),
  14. assert(creationParams == null || creationParamsCodec != null),
  15. super(key: key);
  16. /// 嵌入Android视图类型的唯一标识符
  17. final String viewType;
  18. /// platform view 创建完成的回调
  19. final PlatformViewCreatedCallback onPlatformViewCreated;
  20. /// hit测试期间的行为
  21. final PlatformViewHitTestBehavior hitTestBehavior;
  22. /// 视图的文本方向
  23. final TextDirection layoutDirection;
  24. /// 用于处理事件冲突,对事件进行分发管理相关操作
  25. final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
  26. /// 传给 Android 视图的参数,在 Android 视图构造的时候使用
  27. final dynamic creationParams;
  28. /// 对 creationParams 参数传递时进行的编码规则,如果 creationParams 不为 null,该值必须不为 null
  29. final MessageCodec<dynamic> creationParamsCodec;
  30. @override
  31. State<AndroidView> createState() => _AndroidViewState();
  32. }
有一些需要注意的点
  • AndroidView 仅支持 Android API 20 及以上;

  • 在Flutter 中使用 AndroidView 对性能的开销比较大,应该尽可能的避免使用;

  • 可以把它当作一个 Flutter 的 wedget 一样的使用。

接下来我们看一下这个 State 对象,关于生命周期的知识,这里给出链接:

Flutter State的生命周期
https://www.jianshu.com/p/f39cf2f7ad78

  1. class _AndroidViewState extends State<AndroidView> {
  2. // 用于区分不同的 View 来接收不同的操作指令,可以说不同的 id 代表着不同的 view
  3. // 在_createNewAndroidView方法中被赋值
  4. // 触发条件:1、在 didChangeDependencies 生命周期中第一次初始化触发
  5. // 2、didUpdateWidget 生命周期中 传入的viewType 发生改变时触发
  6. int _id;
  7. // AndroidView的控制器,和 _id 的赋值场景相同
  8. AndroidViewController _controller;
  9. // 布局方向,widget 传入
  10. TextDirection _layoutDirection;
  11. // 被初始化的标识,保证_createNewAndroidView()操作以及_focusNode被操作一次
  12. bool _initialized = false;
  13. // 获取键盘焦点及事件的相关类
  14. FocusNode _focusNode;
  15. // 创建一个空的set集合,如果没有传入gestureRecognizers,则使用该空集合
  16. static final Set<Factory<OneSequenceGestureRecognizer>> _emptyRecognizersSet =
  17. <Factory<OneSequenceGestureRecognizer>>{};
  18. // build 方法,包裹了一层 Focus 用来处理焦点的问题,内部真实使用的是 _AndroidPlatformView,后面单独分析_AndroidPlatformView
  19. @override
  20. Widget build(BuildContext context) {
  21. return Focus(
  22. focusNode: _focusNode,
  23. onFocusChange: _onFocusChange,
  24. child: _AndroidPlatformView(
  25. controller: _controller,
  26. hitTestBehavior: widget.hitTestBehavior,
  27. gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet,
  28. ),
  29. );
  30. }
  31. // 保证操作仅执行一次
  32. void _initializeOnce() {
  33. if (_initialized) {
  34. return;
  35. }
  36. _initialized = true;
  37. _createNewAndroidView();
  38. _focusNode = FocusNode(debugLabel: 'AndroidView(id: $_id)');
  39. }
  40. // didChangeDependencies 生命周期回调
  41. @override
  42. void didChangeDependencies() {
  43. super.didChangeDependencies();
  44. final TextDirection newLayoutDirection = _findLayoutDirection();
  45. // 布局方向调教,是否有改变
  46. final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
  47. _layoutDirection = newLayoutDirection;
  48. // 会多次回调该生命周期,但是保证关键操作仅执行一次
  49. _initializeOnce();
  50. // 根据条件判断是否需要重制布局方向
  51. if (didChangeLayoutDirection) {
  52. _controller.setLayoutDirection(_layoutDirection);
  53. }
  54. }
  55. // didUpdateWidget 生命周期回调
  56. @override
  57. void didUpdateWidget(AndroidView oldWidget) {
  58. super.didUpdateWidget(oldWidget);
  59. final TextDirection newLayoutDirection = _findLayoutDirection();
  60. final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
  61. _layoutDirection = newLayoutDirection;
  62. // 根据viewType是否相同来确定是否需要重新创建 AndroidView,生成新的id
  63. if (widget.viewType != oldWidget.viewType) {
  64. _controller.dispose();
  65. _createNewAndroidView();
  66. return;
  67. }
  68. // 布局方向相关
  69. if (didChangeLayoutDirection) {
  70. _controller.setLayoutDirection(_layoutDirection);
  71. }
  72. }
  73. TextDirection _findLayoutDirection() {
  74. assert(widget.layoutDirection != null || debugCheckHasDirectionality(context));
  75. return widget.layoutDirection ?? Directionality.of(context);
  76. }
  77. // 回收资源
  78. @override
  79. void dispose() {
  80. _controller.dispose();
  81. super.dispose();
  82. }
  83. // 关键的方法,生成 _id 及 _controller,用于传递给_AndroidPlatformView
  84. void _createNewAndroidView() {
  85. // 每次对 _id 进行自增,保证唯一性。
  86. _id = platformViewsRegistry.getNextPlatformViewId();
  87. // initAndroidView 构造了一个 _controller,将参数端都交给 _controller 保管
  88. _controller = PlatformViewsService.initAndroidView(
  89. id: _id,
  90. viewType: widget.viewType,
  91. layoutDirection: _layoutDirection,
  92. creationParams: widget.creationParams,
  93. creationParamsCodec: widget.creationParamsCodec,
  94. onFocus: () {
  95. _focusNode.requestFocus();
  96. }
  97. );
  98. // 添加回调,给开发者使用
  99. if (widget.onPlatformViewCreated != null) {
  100. _controller.addOnPlatformViewCreatedListener(widget.onPlatformViewCreated);
  101. }
  102. }
  103. // 焦点变更
  104. void _onFocusChange(bool isFocused) {
  105. if (!_controller.isCreated) {
  106. return;
  107. }
  108. if (!isFocused) {
  109. _controller.clearFocus().catchError((dynamic e) {
  110. if (e is MissingPluginException) {
  111. return;
  112. }
  113. });
  114. return;
  115. }
  116. // 通过 flutter engin 来实实现焦点变更对 native view 的处理
  117. SystemChannels.textInput.invokeMethod<void>(
  118. 'TextInput.setPlatformViewClient',
  119. _id,
  120. ).catchError((dynamic e) {
  121. if (e is MissingPluginException) {
  122. return;
  123. }
  124. });
  125. }
  126. }


小结一下

我们解决解决了如下问题:

这里我们发现了 id 的作用,当创建的时候,分配一个 id,在 viewType 改变的时候从新分配,其实就是对应 native 侧创建 view 的时候,所以可以通过 id 来保证通过 channel 来和不同 view 进行通信,解决 view 的区分处理。

我们又遇到了新的问题:

AndroidViewController、_AndroidPlatformView都做了什么?

我们先来分析一下 AndroidViewController 会对我们上面的问题和 _AndroidPlatformView 的分析有帮助。

这里会有 Texture 纹理相关的知识,这里不做分析,有兴趣可以查看一下相关文章

Flutter外接纹理
https://zhuanlan.zhihu.com/p/42566807

  1. // AndroidViewController 是通过 PlatformViewsService.initAndroidView 方法创建的,上面有的分析过程里有
  2. class AndroidViewController {
  3. AndroidViewController._(
  4. this.id,
  5. String viewType,
  6. dynamic creationParams,
  7. MessageCodec<dynamic> creationParamsCodec,
  8. TextDirection layoutDirection,
  9. ) : assert(id != null),
  10. assert(viewType != null),
  11. assert(layoutDirection != null),
  12. assert(creationParams == null || creationParamsCodec != null),
  13. _viewType = viewType,
  14. _creationParams = creationParams,
  15. _creationParamsCodec = creationParamsCodec,
  16. _layoutDirection = layoutDirection,
  17. _state = _AndroidViewState.waitingForSize;
  18. // 对应了很多 android 中的点击事件 MotionEvent 相关
  19. // [MotionEvent.ACTION_DOWN]
  20. static const int kActionDown = 0;
  21. // [MotionEvent.ACTION_UP]
  22. static const int kActionUp = 1;
  23. // [MotionEvent.ACTION_MOVE]
  24. static const int kActionMove = 2;
  25. // [MotionEvent.ACTION_CANCEL]
  26. static const int kActionCancel = 3;
  27. // [MotionEvent.ACTION_POINTER_DOWN]
  28. static const int kActionPointerDown = 5;
  29. // [MotionEvent.ACTION_POINTER_UP]
  30. static const int kActionPointerUp = 6;
  31. // 布局方向相关
  32. // [View.LAYOUT_DIRECTION_LTR]
  33. static const int kAndroidLayoutDirectionLtr = 0;
  34. // [View.LAYOUT_DIRECTION_RTL]
  35. static const int kAndroidLayoutDirectionRtl = 1;
  36. // 标识 id 上面已经分析过了
  37. final int id;
  38. // native 侧注册的 viewType 字段
  39. final String _viewType;
  40. // 在创建 andorid 端 view 的时候(下文 _create方法),返回_textureId。
  41. // 该 id 是在 native 侧渲染完成后绘图数据对应的id,可以直接在GPU中找到并直接使用
  42. // Flutter 的 Framework 层最后会递交给 Engine 层一个 layerTree ,包含了此处的 _textureId,最终在绘制的时候,skia 会直接在 GPU 中根据 textureId 找到相应的绘制数据,并将其绘制到屏幕上。
  43. int _textureId;
  44. // _textureId 的 get 方法
  45. int get textureId => _textureId;
  46. TextDirection _layoutDirection;
  47. // 枚举状态
  48. _AndroidViewState _state;
  49. // 参数
  50. dynamic _creationParams;
  51. // 编码类
  52. MessageCodec<dynamic> _creationParamsCodec;
  53. // 回调集合
  54. final List<PlatformViewCreatedCallback> _platformViewCreatedCallbacks = <PlatformViewCreatedCallback>[];
  55. /// 获取 view 的 create 状态
  56. bool get isCreated => _state == _AndroidViewState.created;
  57. void addOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) {
  58. assert(listener != null);
  59. assert(_state != _AndroidViewState.disposed);
  60. _platformViewCreatedCallbacks.add(listener);
  61. }
  62. void removeOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) {
  63. assert(_state != _AndroidViewState.disposed);
  64. _platformViewCreatedCallbacks.remove(listener);
  65. }
  66. // Disposes the Android view.
  67. // 通过 engine 调用了 native 的 dispose 方法、清空回调集合、disposed 当前 widget 的 state
  68. Future<void> dispose() async {
  69. if (_state == _AndroidViewState.creating || _state == _AndroidViewState.created)
  70. await SystemChannels.platform_views.invokeMethod<void>('dispose', id);
  71. _platformViewCreatedCallbacks.clear();
  72. _state = _AndroidViewState.disposed;
  73. }
  74. // 设置 Android View 的大小,通过 engine 调用了 native 的 resize 方法
  75. Future<void> setSize(Size size) async {
  76. assert(_state != _AndroidViewState.disposed, 'trying to size a disposed Android View. View id: $id');
  77. assert(size != null);
  78. assert(!size.isEmpty);
  79. if (_state == _AndroidViewState.waitingForSize)
  80. return _create(size);
  81. await SystemChannels.platform_views.invokeMethod<void>('resize', <String, dynamic>{
  82. 'id': id,
  83. 'width': size.width,
  84. 'height': size.height,
  85. });
  86. }
  87. // 通过 engine 调用了 native 的 setDirection 方法,设置 Android view 方向
  88. Future<void> setLayoutDirection(TextDirection layoutDirection) async {
  89. assert(_state != _AndroidViewState.disposed,'trying to set a layout direction for a disposed UIView. View id: $id');
  90. if (layoutDirection == _layoutDirection)
  91. return;
  92. assert(layoutDirection != null);
  93. _layoutDirection = layoutDirection;
  94. if (_state == _AndroidViewState.waitingForSize)
  95. return;
  96. await SystemChannels.platform_views.invokeMethod<void>('setDirection', <String, dynamic>{
  97. 'id': id,
  98. 'direction': _getAndroidDirection(layoutDirection),
  99. });
  100. }
  101. // 通过 engine 调用了 native 的 clearFocus 方法,清除焦点
  102. Future<void> clearFocus() {
  103. if (_state != _AndroidViewState.created) {
  104. return null;
  105. }
  106. return SystemChannels.platform_views.invokeMethod<void>('clearFocus', id);
  107. }
  108. // 获得布局方向
  109. static int _getAndroidDirection(TextDirection direction) {
  110. assert(direction != null);
  111. switch (direction) {
  112. case TextDirection.ltr:
  113. return kAndroidLayoutDirectionLtr;
  114. case TextDirection.rtl:
  115. return kAndroidLayoutDirectionRtl;
  116. }
  117. return null;
  118. }
  119. // 通过 engine 调用 native 的 touch 方法,将事件发送给 android view 处理
  120. Future<void> sendMotionEvent(AndroidMotionEvent event) async {
  121. await SystemChannels.platform_views.invokeMethod<dynamic>(
  122. 'touch',
  123. event._asList(id),
  124. );
  125. }
  126. /// Creates a masked Android MotionEvent action value for an indexed pointer.
  127. static int pointerAction(int pointerId, int action) {
  128. return ((pointerId << 8) & 0xff00) | (action & 0xff);
  129. }
  130. // 真正创建 view 的方法,也是通过 engine 调用 native 的 create 方法,传入了 width 和 height
  131. Future<void> _create(Size size) async {
  132. final Map<String, dynamic> args = <String, dynamic>{
  133. 'id': id,
  134. 'viewType': _viewType,
  135. 'width': size.width,
  136. 'height': size.height,
  137. 'direction': _getAndroidDirection(_layoutDirection),
  138. };
  139. if (_creationParams != null) {
  140. final ByteData paramsByteData = _creationParamsCodec.encodeMessage(_creationParams);
  141. args['params'] = Uint8List.view(
  142. paramsByteData.buffer,
  143. 0,
  144. paramsByteData.lengthInBytes,
  145. );
  146. }
  147. _textureId = await SystemChannels.platform_views.invokeMethod('create', args);
  148. _state = _AndroidViewState.created;
  149. for (PlatformViewCreatedCallback callback in _platformViewCreatedCallbacks) {
  150. // 遍历、回调
  151. callback(id);
  152. }
  153. }
  154. }
我们看到了 view 的大小由 _create 方法传入,那传入的值是怎么获得的呢? 我们先把还没分析的 _AndroidPlatformView 看完再下结论。
  1. class _AndroidPlatformView extends LeafRenderObjectWidget {
  2. // 将 controller 传进来了,具体没做太多的操作,主要还是通过 controller 来实现的。
  3. const _AndroidPlatformView({
  4. Key key,
  5. @required this.controller,
  6. @required this.hitTestBehavior,
  7. @required this.gestureRecognizers,
  8. }) : assert(controller != null),
  9. assert(hitTestBehavior != null),
  10. assert(gestureRecognizers != null),
  11. super(key: key);
  12. final AndroidViewController controller;
  13. final PlatformViewHitTestBehavior hitTestBehavior;
  14. final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
  15. // 需要注意的是这两个方法,这里重写了 createRenderObject 方法。
  16. @override
  17. RenderObject createRenderObject(BuildContext context) =>
  18. RenderAndroidView(
  19. viewController: controller,
  20. hitTestBehavior: hitTestBehavior,
  21. gestureRecognizers: gestureRecognizers,
  22. );
  23. @override
  24. void updateRenderObject(BuildContext context, RenderAndroidView renderObject) {
  25. renderObject.viewController = controller;
  26. renderObject.hitTestBehavior = hitTestBehavior;
  27. renderObject.updateGestureRecognizers(gestureRecognizers);
  28. }
  29. }
我这里先假设大家都知道 Widget、Element、RenderObject之间的关系,如果不是很清晰,这篇文章里有详细的介绍。

Flutter 从加载到显示
https://mp.weixin.qq.com/s/ncViI0KGikPUIZ7BlEHGOA

RenderObject 的最终大小的确定有两种情况,一个是由父节点所指定,一个是根据自己的情况确定。默认的 RenderObject 中有一个 sizedByParent 属性,默认为 false,即根据自身大小确定。这里指定了 RenderObject 为 RenderAndroidView ,我们来看一下这个类,这里就不一行一行的分析了,我们把重点提出来。

  1. @override
  2. bool get sizedByParent => true;

所以我们可以得出结论了。


再小结一下

我们解决解决了剩下的问题:

1、我们了解了 AndroidViewController、_AndroidPlatformView 都做了什么。

2、AndroidView 的大小是由父节点的大小去定的所以上面使用 Expanded 包裹则可以生效,如果不进行包裹,则大小为父控件大小,在 Column 中会出现问题。当 Widget size 小于 View size,Flutter 会进行裁剪。当 Widget size 大于 View size 时,多出来的位置会被背景填充。在 Android 侧,实现了 PlatformView 的 View 会被包裹在 FrameLayout 中,可以对 View 的绘制添加监听,打印出 View 的 parent;

3、platform view 是在 native 侧渲染的,返回给 Flutter 侧一个 _textureId ,通过这个 id Flutter 将 View 直接展示出来。这部分也说明了为什么 platform view 在 Flutter 中的性能开销比较大,整个过程数据需要从 GPU -> CPU -> GPU,这部分的代价是比较大的。



如何开发一个 platform view

其实 Flutter 官方维护了一些 plugin,链接如下:

https://github.com/flutter/plugins

其中的 webviewflutter 、googlemaps_flutter 就是通过 platform view,就是一个很好的 demo 。


本文 Demo 地址

https://github.com/loosaSH/flutter-PlatformView


参考资料

在Flutter中嵌入Native组件的正确姿势
https://yq.aliyun.com/articles/669831?utmcontent=m1000024586

github:Flutter Plugin
https://github.com/flutter/plugins

Flutter 从加载到显示
https://mp.weixin.qq.com/s/ncViI0KGikPUIZ7BlEHGOA

Flutter外接纹理
https://zhuanlan.zhihu.com/p/42566807

Flutter State的生命周期
https://www.jianshu.com/p/f39cf2f7ad78


640?wx_fmt=jpeg

奇舞移动技术

更多移动端技术知识,欢迎关注奇舞移动技术~~


界世的你当不

只做你的肩膀

640?wx_fmt=jpeg 640?wx_fmt=jpeg

 360官方技术公众号 

技术干货|一手资讯|精彩活动

空·

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/425693
推荐阅读
相关标签
  

闽ICP备14008679号