@TryLoveCatch
2021-08-26T06:20:47.000000Z
字数 20438
阅读 5109
flutter
Flutter之在Flutter布局中嵌入原生组件Android篇
MyPlatformView implements PlatformView {@Overridepublic View getView() {return new TextView();}@Overridepublic void dispose() {}}
这里我们返回一个TextView
MyPlatformViewFactory extends PlatformViewFactory {@Overridepublic PlatformView create(Context context, int id, Object args){returen new MyPlatformView(context);}
PlatformViewFactory的主要任务是,在create()方法中创建一个View并把它传给Flutter(这个说法并不准确,但是我们姑且可以这么理解,后续会进行解释)
返回就是第一步的PlatformView
public class MyPlugin {public static void registerWith(Registrar registrar) {if (registrar.activity() == null) {// When a background flutter view tries to register the plugin, the registrar has no activity.// We stop the registration process as this plugin is foreground only.return;}registrar.platformViewRegistry().registerViewFactory("plugins.test/my_view", new MyPlatformViewFactory(registrar));}}
"plugins.test/my_view",这是组件的注册名称,在Flutter调用时需要用到,你可以使用任意格式的字符串,但是两端必须统一。
注册了自己写的那个MyPlatformViewFactory
系统会自动生产两个类,MainActivity和GeneratedPluginRegistrant:
public class MainActivity extends FlutterActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);GeneratedPluginRegistrant.registerWith(this);}}
MainActivity里面会调用GeneratedPluginRegistrant里面的静态方法:
public final class GeneratedPluginRegistrant {public static void registerWith(PluginRegistry registry) {if (alreadyRegisteredWith(registry)) {return;}}private static boolean alreadyRegisteredWith(PluginRegistry registry) {final String key = GeneratedPluginRegistrant.class.getCanonicalName();if (registry.hasPlugin(key)) {return true;}registry.registrarFor(key);return false;}}
可以看到registerWith()什么也没做,我们需要注册咱们写的插件:
public static void registerWith(PluginRegistry registry) {if (alreadyRegisteredWith(registry)) {return;}// GoogleMapsPlugin.registerWith(registry.registrarFor("io.flutter.plugins.googlemaps.GoogleMapsPlugin"));MyPlugin.registerWith(registry);}
void main() => runApp(MyApp());class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: MyHomePage(title: 'Flutter Demo'),);}}class MyHomePage extends StatefulWidget {MyHomePage({Key key, this.title}) : super(key: key);final String title;@override_MyHomePageState createState() => _MyHomePageState();}class _MyHomePageState extends State<MyHomePage> {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(widget.title),),body: Center(// 主要是这里!!!!!!!child: AndroidView(viewType: 'plugins.test/my_view'),),);}}
在使用Android平台的view只需要创建AndroidView组件并告诉它组件的注册注册名称即可,这个名字,就是咱们在Android侧注册的名字。
如果你是双平台的实现,则可以通过引入package:flutter/foundation.dart包,并判断defaultTargetPlatform是TargetPlatform.android还是TargetPlatform.iOS来引入不同平台的实现。
class _MyHomePageState extends State<MyHomePage> {@overrideWidget build(BuildContext context) {print(defaultTargetPlatform);return Scaffold(appBar: AppBar(title: Text(widget.title),),body: Center(child: AndroidView(viewType: 'plugins.test/my_view',// 这里!!!!!!!creationParams: {"myContent": "通过参数传入的文本内容",},creationParamsCodec: const StandardMessageCodec(),),),);}}
public class MyPlatformView implements PlatformView {private final TextView myNativeView;MyPlatformView(Context context, BinaryMessenger messenger, int id, Map<String, Object> params) {TextView myNativeView = new TextView(context);myNativeView.setText("我是来自Android的原生TextView");this.myNativeView = myNativeView;if (params.containsKey("myContent")) {String myContent = (String) params.get("myContent");myNativeView.setText(myContent);}}...}
原生组件初始化的参数并不会随着setState重复赋值
public class MyPlatformView implements PlatformView, MethodChannel.MethodCallHandler {private final TextView myNativeView;MyPlatformView(Context context, BinaryMessenger messenger, int id, Map<String, Object> params) {...MethodChannel methodChannel = new MethodChannel(messenger, "plugins.test/my_channel_" + id);methodChannel.setMethodCallHandler(this);}@Overridepublic void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {// 在接口的回调方法中可以接收到来自Flutter的调用}...}
MethodChannel也需要一个唯一的标示,我们这里是用"plugins.test/my_channel_" + id来标示,那么这个id是什么呢,继续看
class _MyHomePageState extends State<MyHomePage> {@overrideWidget build(BuildContext context) {print(defaultTargetPlatform);return Scaffold(appBar: AppBar(title: Text(widget.title),),body: Center(child: AndroidView(viewType: 'plugins.test/my_view',creationParams: {"myContent": "通过参数传入的文本内容",},creationParamsCodec: const StandardMessageCodec(),onPlatformViewCreated: onMyViewCreated,),),);}MethodChannel _channel;void onMyViewCreated(int id) {_channel = new MethodChannel('plugins.test/my_channel_$id');setMyViewText();}Future<void> setMyViewText(String text) async {assert(text != null);return _channel.invokeMethod('setText', text);}}
- onPlatformViewCreated, 监听原始组件成功创建,并能够在回调方法的参数中拿到当前组件的id,这个id是系统随机分配的
- invokeMethod(),来调用native的方法
@Overridepublic void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {if ("setText".equals(methodCall.method)) {String text = (String) methodCall.arguments;myNativeView.setText(text);result.success(null);}}
在一个界面中实例化多个原生组件的情况对性能的影响非常的大,也不建议在实际开发中大量引入原生组件,因为除去地图/WebView等特殊情况,基本上原生能实现的UI效果Flutter的UI引擎都能实现。
注意:
在Flutter中嵌入Native组件的正确姿势
下面我们来说说原理性的东西,问几个问题:
自定义一个View,我们就直接继承TextView:
public class MyTextView extends TextView {public MyTextView(Context context) {super(context);}public MyTextView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);}public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);Log.e("xxxxx", "MyTextView onMeasure > width: " + getMeasuredWidth() + ", height: " + getMeasuredHeight());}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {super.onLayout(changed, left, top, right, bottom);Log.e("xxxxx", "MyTextView onLayout > left: " + left + ", top: " + top + ", right: " + right + ", bottom: " + bottom);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);Log.e("xxxxx", "MyTextView onDraw");}@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {Log.e("xxxxx", "MyTextView dispatchTouchEvent");return super.dispatchTouchEvent(event);}}
修改MyPlatformViewFactory:
MyPlatformViewFactory extends PlatformViewFactory {public MyPlatformViewFactory() {Log.e("xxxxx", "PlatformViewFactory init");}@Overridepublic PlatformView create(Context context, int id, Object args){Log.e("xxxxx", "PlatformViewFactory create > id: " + id);returen new MyPlatformView(context);}
修改MyPlatformView:
MyPlatformView implements PlatformView {private MyTextView mTxtView;public MyPlatformView() {Log.e("xxxxx", "PlatformView init");mTxtView = new MyTextView(registrar.context());mTxtView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {Log.e("xxxxx", "PlatformView onClick");}});mTxtView.setOnLongClickListener(new View.OnLongClickListener() {@Overridepublic boolean onLongClick(View view) {Log.e("xxxxx", "PlatformView onLongClick");return false;}});mTxtView.setText("123456789");}@Overridepublic View getView() {Log.e("xxxxx", "PlatformView getView");return mTxtView;}@Overridepublic void dispose() {Log.e("xxxxx", "PlatformView dispose");}}
2019-09-04 11:53:28.411 25300-25300/? E/xxxxx: PlatformViewFactory init2019-09-04 11:53:29.899 25300-25300/fluttermapexample E/xxxxx: PlatformViewFactory create > id: 02019-09-04 11:53:29.899 25300-25300/fluttermapexample E/xxxxx: PlatformView init2019-09-04 11:53:29.940 25300-25300/fluttermapexample E/xxxxx: PlatformView getView2019-09-04 11:53:29.949 25300-25300/fluttermapexample E/xxxxx: PlatformView getView2019-09-04 11:53:29.963 25300-25300/fluttermapexample E/xxxxx: MyTextView onMeasure > width: 1080, height: 16802019-09-04 11:53:29.989 25300-25300/fluttermapexample E/xxxxx: MyTextView onMeasure > width: 1080, height: 16802019-09-04 11:53:29.989 25300-25300/fluttermapexample E/xxxxx: MyTextView onLayout > left: 0, top: 0, right: 1080, bottom: 16802019-09-04 11:53:29.995 25300-25300/fluttermapexample E/xxxxx: MyTextView onDraw
PlatformView getView调用了两次- MyTextView的onMeasure、onLayout、onDraw都执行了,并且大小为设备的大小
2019-09-04 11:57:25.922 25300-25300/fluttermapexample E/xxxxx: PlatformView getView2019-09-04 11:57:25.922 25300-25300/fluttermapexample E/xxxxx: MyTextView dispatchTouchEvent2019-09-04 11:57:25.927 25300-25300/fluttermapexample E/xxxxx: PlatformView getView2019-09-04 11:57:25.927 25300-25300/fluttermapexample E/xxxxx: MyTextView dispatchTouchEvent2019-09-04 11:57:25.929 25300-25300/fluttermapexample E/xxxxx: PlatformView onClick
- 每次事件传递,都会执行一次
PlatformView getView- PlatformViewFactory的构造函数和create(),都只执行一次
- PlatformView的构造函数也只执行一次
return AndroidView(viewType: viewType,onPlatformViewCreated: _onViewCreate,creationParams: {"type": 6},creationParamsCodec: const StandardMessageCodec(),);
既然AndroidView是一个Widget,那么我们是不是可以通过setState(),来更新AndroidView的viewType呢?当然是可以的了
setState(() {_viewType = "plugins.test/my_view_2";});
我们替换"plugins.test/my_view"为"plugins.test/my_view_2",不过仅仅这样是不够的,因为Android端需要跟Flutter一致,我们看一下注册插件的地方:
public class MyPlugin {public static void registerWith(Registrar registrar) {if (registrar.activity() == null) {return;}registrar.platformViewRegistry().registerViewFactory("plugins.test/my_view", new MyPlatformViewFactory(registrar));}}
registrar.platformViewRegistry()得到的是PlatformViewRegistry接口,唯一实现为PlatformViewRegistryImpl,我们可以看一下:
class PlatformViewRegistryImpl implements PlatformViewRegistry {private final Map<String, PlatformViewFactory> viewFactories = new HashMap();PlatformViewRegistryImpl() {}public boolean registerViewFactory(String viewTypeId, PlatformViewFactory factory) {if (this.viewFactories.containsKey(viewTypeId)) {return false;} else {this.viewFactories.put(viewTypeId, factory);return true;}}PlatformViewFactory getFactory(String viewTypeId) {return (PlatformViewFactory)this.viewFactories.get(viewTypeId);}}
里面是一个HashMap存的viewType和PlatformViewFactory的对应关系,所以说,我们可以registerViewFactory多次:
public class MyPlugin {public static void registerWith(Registrar registrar) {if (registrar.activity() == null) {return;}registrar.platformViewRegistry().registerViewFactory("plugins.test/my_view", new MyPlatformViewFactory(registrar));PlatformViewRegistry tPlatformViewRegistry = registrar.platformViewRegistry();MyPlatformViewFactory tMyPlatformViewFactory = new MyPlatformViewFactory(registrar);tPlatformViewRegistry.registerViewFactory("plugins.test/my_view", tMyPlatformViewFactory);MyPlatformViewFactory2 tMyPlatformViewFactory2 = new MyPlatformViewFactory2(registrar);tPlatformViewRegistry.registerViewFactory("plugins.test/my_view_2", tMyPlatformViewFactory2);}}
好了,这样就可以了,我们运行程序的时候,还是跟上面一样的:
2019-09-04 11:53:28.411 25300-25300/? E/xxxxx: PlatformViewFactory init2019-09-04 11:53:29.899 25300-25300/fluttermapexample E/xxxxx: PlatformViewFactory create > id: 02019-09-04 11:53:29.899 25300-25300/fluttermapexample E/xxxxx: PlatformView init2019-09-04 11:53:29.940 25300-25300/fluttermapexample E/xxxxx: PlatformView getView2019-09-04 11:53:29.949 25300-25300/fluttermapexample E/xxxxx: PlatformView getView2019-09-04 11:53:29.963 25300-25300/fluttermapexample E/xxxxx: MyTextView onMeasure > width: 1080, height: 16802019-09-04 11:53:29.989 25300-25300/fluttermapexample E/xxxxx: MyTextView onMeasure > width: 1080, height: 16802019-09-04 11:53:29.989 25300-25300/fluttermapexample E/xxxxx: MyTextView onLayout > left: 0, top: 0, right: 1080, bottom: 16802019-09-04 11:53:29.995 25300-25300/fluttermapexample E/xxxxx: MyTextView onDraw
然后,我们执行setState()来更改viewType:
2019-09-04 11:53:29.899 25300-25300/fluttermapexample E/xxxxx: PlatformView dispose2019-09-04 11:53:29.899 25300-25300/fluttermapexample E/xxxxx: PlatformViewFactory create > id: 12019-09-04 11:53:29.899 25300-25300/fluttermapexample E/xxxxx: PlatformView2 init2019-09-04 11:53:29.940 25300-25300/fluttermapexample E/xxxxx: PlatformView2 getView2019-09-04 11:53:29.949 25300-25300/fluttermapexample E/xxxxx: PlatformView2 getView2019-09-04 11:53:29.963 25300-25300/fluttermapexample E/xxxxx: MyTextView2 onMeasure > width: 1080, height: 16802019-09-04 11:53:29.989 25300-25300/fluttermapexample E/xxxxx: MyTextView2 onMeasure > width: 1080, height: 16802019-09-04 11:53:29.989 25300-25300/fluttermapexample E/xxxxx: MyTextView2 onLayout > left: 0, top: 0, right: 1080, bottom: 16802019-09-04 11:53:29.995 25300-25300/fluttermapexample E/xxxxx: MyTextView2 onDraw
- 先执行了上一个PlatformView#dispose()
- PlatformViewFactory#create()再次执行,这一次id变成了1
- PlatformView2重新了初始化的流程
- 两个View切换的间隔,会有黑屏现象
其实,仔细想想,跟上面连接里面的结论也是不冲突的,因为我们改变了Native View,Flutter也会立马得知,然后进行改变。所以就感觉是屏幕显示的是我们自己的native view,其实并不是,都是Flutter来渲染的。
注意,后来发现这里是有问题的:
我们上面是通过改变AndroidView的viewType,来切换Native View,但是Flutter会存在一个bug
java.lang.NullPointerException: Attempt to invoke virtual method 'int android.view.Display.getDisplayId()' on a null object referenceat android.view.SurfaceView.updateWindow(SurfaceView.java:539)at android.view.SurfaceView$3.onPreDraw(SurfaceView.java:162)at android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.java:944)at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2245)at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1290)at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6399)at android.view.Choreographer$CallbackRecord.run(Choreographer.java:873)at android.view.Choreographer.doCallbacks(Choreographer.java:685)at android.view.Choreographer.doFrame(Choreographer.java:621)at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:859)at android.os.Handler.handleCallback(Handler.java:754)at android.os.Handler.dispatchMessage(Handler.java:95)at android.os.Looper.loop(Looper.java:165)at android.app.ActivityThread.main(ActivityThread.java:6375)at java.lang.reflect.Method.invoke(Native Method)at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:912)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:802)
参考;https://github.com/flutter/flutter/issues/26345
java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View io.flutter.plugin.platform.VirtualDisplayController.getView()' on a null object referenceat io.flutter.plugin.platform.PlatformViewsController.onTouch(PlatformViewsController.java:275)at io.flutter.plugin.platform.PlatformViewsController.onMethodCall(PlatformViewsController.java:127)at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:200)at io.flutter.view.FlutterNativeView.handlePlatformMessage(FlutterNativeView.java:163)at android.os.MessageQueue.nativePollOnce(Native Method)at android.os.MessageQueue.next(MessageQueue.java:329)at android.os.Looper.loop(Looper.java:142)at android.app.ActivityThread.main(ActivityThread.java:6375)at java.lang.reflect.Method.invoke(Native Method)at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:912)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:802)
this.methodChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {@Overridepublic void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {if ("switchView".equals(methodCall.method)) {((FrameLayout)mMapView.getParent()).addView(mTxtView, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));((FrameLayout)mMapView.getParent()).removeView(mMapView);}}
然后在PlatformView#getView()返回这个TextView即可。
当然,最后一种方法也不是太好,因为直接修改了副屏的布局。
最好的方式是,不要通过viewType来切换Native View了,就使用一个Native View,然后通过channel发送消息,让这个Native View增加或者删除自己的子View。
图一:

图二:

我们运行程序之后,打开“layout inspector”,惊奇的发现有两个MainActivity,分别对应上面的两张图
增加了一个布局,这个布局的root是FrameLayout,看源码其实就是AccessibilityDelegatingFrameLayout;里面有两个子View,一个是FrameLayout,这个是专门用来方native view的;另一个是SingleViewPresentation#FakeWindowViewGroup,这是一个自定义ViewGroup。
当我们执行:
((FrameLayout)mTxtView.getParent()).setVisibility(View.GONE);
这个时候MyTextView就会从界面上消失了,也就是说我们在手机上看不到MyTextView了。但是,当点击屏幕的时候,PlatformView#getView()依然会调用。而且,我们通过Flutter加进来的FloatingActionButton依然还在界面上。
再次:
mViewGroup = new FrameLayout(registrar.context());mViewGroup.setBackgroundColor(Color.RED);((FrameLayout)mMapView.getParent()).addView(mViewGroup, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
这个mViewGroup会覆盖之前写的TextView,当然,FloatingActionButton依然还在界面上。
图二只有一个FlutterView,我们知道FlutterView是一个SurfaceView,而且这个才是显示在手机屏幕上的那个View,为什么?
因为我们的Activity是继承FlutterActivity的,而FlutterActivity的布局是FlutterView。
Flutter 面试知识点集锦
那么图二和图一是什么关系呢?
PlatformView 可以嵌套原生 View 到 Flutter UI 中,这里面其实是使用了 Presentation + VirtualDisplay + Surface 等实现的,大致原理就是:
使用了类似副屏显示的技术,VirtualDisplay 类代表一个虚拟显示器,调用 DisplayManager 的 createVirtualDisplay() 方法,将虚拟显示器的内容渲染在一个 Surface 控件上,然后将 Surface 的 id 通知给 Dart,让 engine 绘制时,在内存中找到对应的 Surface 画面内存数据,然后绘制出来。em... 实时控件截图渲染显示技术。
我们可以通过一些adb 命令来侧面印证一下:
adb dumpsys activity activities
结果:
ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)Display #0 (activities from top to bottom):Stack #1:mResumedActivity=ActivityRecord{1ab999c u0 fluttermapexample/.MainActivity t463}Running activities (most recent first):TaskRecord{4a3257f #463 A=fluttermapexample U=0 StackId=1 sz=1}Run #0: ActivityRecord{1ab999c u0 fluttermapexample/.MainActivity t463}mResumedActivity: ActivityRecord{1ab999c u0 fluttermapexample/.MainActivity t463}Display #43 (activities from top to bottom):Display #44 (activities from top to bottom):Display #92 (activities from top to bottom):mFocusedActivity: ActivityRecord{1ab999c u0 fluttermapexample/.MainActivity t463}mFocusedStack=ActivityStack{7ce9a9b stackId=1, 1 tasks} mLastFocusedStack=ActivityStack{7ce9a9b stackId=1, 1 tasks}mSleepTimeout=falsemCurTaskIdForUser={0=463}mUserStackInFront={}mActivityContainers={0=ActivtyContainer{0}A, 1=ActivtyContainer{1}A}mLockTaskModeState=NONE mLockTaskPackages (userId:packages)=0:[]mLockTaskModeTasks[]
截取了一部分,我们重点看这些:
Display #0 (activities from top to bottom):mResumedActivity=ActivityRecord{1ab999c u0 fluttermapexample/.MainActivity t463}Running activities (most recent first):TaskRecord{4a3257f #463 A=fluttermapexample U=0 StackId=1 sz=1}Run #0: ActivityRecord{1ab999c u0 fluttermapexample/.MainActivity t463}Display #92 (activities from top to bottom):mFocusedActivity: ActivityRecord{1ab999c u0 fluttermapexample/.MainActivity t463}mFocusedStack=ActivityStack{7ce9a9b stackId=1, 1 tasks} mLastFocusedStack=ActivityStack{7ce9a9b stackId=1, 1 tasks}
我们继续,执行:
adb shell dumpsys display | grep DisplayDeviceInfo
得到结果:
DisplayDeviceInfo{"内置屏幕": uniqueId="local:0", 1080 x 1920, modeId 1, defaultModeId 1, supportedModes [{id=1, width=1080, height=1920, fps=60.000004}], colorTransformId 1, defaultColorTransformId 1, supportedColorTransforms [{id=1, colorTransform=0}], HdrCapabilities android.view.Display$HdrCapabilities@ceffe26, density 480, 397.565 x 399.737 dpi, appVsyncOff 0, presDeadline 17666666, touch INTERNAL, rotation 0, type BUILT_IN, state ON, FLAG_DEFAULT_DISPLAY, FLAG_ROTATES_WITH_CONTENT, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS}DisplayDeviceInfo{"flutter-vd": uniqueId="virtual:fluttermapexample,10971,flutter-vd,0", 600 x 600, modeId 93, defaultModeId 93, supportedModes [{id=93, width=600, height=600, fps=60.0}], colorTransformId 0, defaultColorTransformId 0, supportedColorTransforms [], HdrCapabilities null, density 480, 480.0 x 480.0 dpi, appVsyncOff 0, presDeadline 16666666, touch NONE, rotation 0, type VIRTUAL, state ON, owner fluttermapexample (uid 10971), FLAG_PRIVATE, FLAG_NEVER_BLANK, FLAG_OWN_CONTENT_ONLY}
可以看到有两个DisplayDeviceInfo,一个是"local:0",一个是"virtual:fluttermapexample"
- 程序启动之后,会有两个MainActivity的实例,一个包含FlutterView,一个包含PlatformView#getView()返回的那个TextView
- 包含FlutterView的Activity是真正在屏幕显示的,这个称为主屏。
- 另外一个是通过VirtualDisplay,代表一个虚拟显示器,可以称为副屏。
- Native侧会把这个副屏的内容渲染到Surface上,这些数据对应一个textureId,Native侧会把这个textureId传递给Flutter,Flutter通过这个ID可以直接在GPU中找到相应的绘图数据并使用显示在主屏上
- 原生层 Flutter 通过 Presentation 副屏显示的原理,利用 VirtualDisplay 的方式,让 Android 控件在内存中绘制到 Surface 层。 VirtualDisplay 绘制在 Surface 的 textureId ,之后会通知到 Dart 层,在 Dart 层利用 AndroidView 定义好的 Widget 并带上 textureId ,那么 Engine 在渲染时,就会在内存中将 textureId 对应的数据渲染到 AndroidView 上。
Flutter学习系列(5)— 页面初始化
Flutter学习系列(6)— FlutterView初始化
https://cloud.tencent.com/developer/article/1584477
在Flutter中嵌入Native组件的正确姿势
其实Flutter中PlatFromView也是基于texture来实现的
刚开始,我以为事件需要我们自己处理,写了demo,跑了之后发现,事件flutter已经帮我们处理好了。
Flutter按照自己的规则去处理事件,如果AndroidView赢得了事件,事件就会被封装成相应的Native端的事件并且通过方法通道传回Native,Native再根据自己的处理事件的规则去处理。
总之,当触摸native的view的时候,flutter会把触摸事件传递给native的view。