@TryLoveCatch
2021-08-26T14:20:47.000000Z
字数 20438
阅读 4640
flutter
Flutter之在Flutter布局中嵌入原生组件Android篇
MyPlatformView implements PlatformView {
@Override
public View getView() {
return new TextView();
}
@Override
public void dispose() {
}
}
这里我们返回一个TextView
MyPlatformViewFactory extends PlatformViewFactory {
@Override
public 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 {
@Override
protected 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 {
@override
Widget 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> {
@override
Widget 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> {
@override
Widget 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);
}
@Override
public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
// 在接口的回调方法中可以接收到来自Flutter的调用
}
...
}
MethodChannel也需要一个唯一的标示,我们这里是用"plugins.test/my_channel_" + id来标示,那么这个id是什么呢,继续看
class _MyHomePageState extends State<MyHomePage> {
@override
Widget 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的方法
@Override
public 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);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.e("xxxxx", "MyTextView onMeasure > width: " + getMeasuredWidth() + ", height: " + getMeasuredHeight());
}
@Override
protected 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);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.e("xxxxx", "MyTextView onDraw");
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.e("xxxxx", "MyTextView dispatchTouchEvent");
return super.dispatchTouchEvent(event);
}
}
修改MyPlatformViewFactory:
MyPlatformViewFactory extends PlatformViewFactory {
public MyPlatformViewFactory() {
Log.e("xxxxx", "PlatformViewFactory init");
}
@Override
public 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() {
@Override
public void onClick(View view) {
Log.e("xxxxx", "PlatformView onClick");
}
});
mTxtView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
Log.e("xxxxx", "PlatformView onLongClick");
return false;
}
});
mTxtView.setText("123456789");
}
@Override
public View getView() {
Log.e("xxxxx", "PlatformView getView");
return mTxtView;
}
@Override
public void dispose() {
Log.e("xxxxx", "PlatformView dispose");
}
}
2019-09-04 11:53:28.411 25300-25300/? E/xxxxx: PlatformViewFactory init
2019-09-04 11:53:29.899 25300-25300/fluttermapexample E/xxxxx: PlatformViewFactory create > id: 0
2019-09-04 11:53:29.899 25300-25300/fluttermapexample E/xxxxx: PlatformView init
2019-09-04 11:53:29.940 25300-25300/fluttermapexample E/xxxxx: PlatformView getView
2019-09-04 11:53:29.949 25300-25300/fluttermapexample E/xxxxx: PlatformView getView
2019-09-04 11:53:29.963 25300-25300/fluttermapexample E/xxxxx: MyTextView onMeasure > width: 1080, height: 1680
2019-09-04 11:53:29.989 25300-25300/fluttermapexample E/xxxxx: MyTextView onMeasure > width: 1080, height: 1680
2019-09-04 11:53:29.989 25300-25300/fluttermapexample E/xxxxx: MyTextView onLayout > left: 0, top: 0, right: 1080, bottom: 1680
2019-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 getView
2019-09-04 11:57:25.922 25300-25300/fluttermapexample E/xxxxx: MyTextView dispatchTouchEvent
2019-09-04 11:57:25.927 25300-25300/fluttermapexample E/xxxxx: PlatformView getView
2019-09-04 11:57:25.927 25300-25300/fluttermapexample E/xxxxx: MyTextView dispatchTouchEvent
2019-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 init
2019-09-04 11:53:29.899 25300-25300/fluttermapexample E/xxxxx: PlatformViewFactory create > id: 0
2019-09-04 11:53:29.899 25300-25300/fluttermapexample E/xxxxx: PlatformView init
2019-09-04 11:53:29.940 25300-25300/fluttermapexample E/xxxxx: PlatformView getView
2019-09-04 11:53:29.949 25300-25300/fluttermapexample E/xxxxx: PlatformView getView
2019-09-04 11:53:29.963 25300-25300/fluttermapexample E/xxxxx: MyTextView onMeasure > width: 1080, height: 1680
2019-09-04 11:53:29.989 25300-25300/fluttermapexample E/xxxxx: MyTextView onMeasure > width: 1080, height: 1680
2019-09-04 11:53:29.989 25300-25300/fluttermapexample E/xxxxx: MyTextView onLayout > left: 0, top: 0, right: 1080, bottom: 1680
2019-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 dispose
2019-09-04 11:53:29.899 25300-25300/fluttermapexample E/xxxxx: PlatformViewFactory create > id: 1
2019-09-04 11:53:29.899 25300-25300/fluttermapexample E/xxxxx: PlatformView2 init
2019-09-04 11:53:29.940 25300-25300/fluttermapexample E/xxxxx: PlatformView2 getView
2019-09-04 11:53:29.949 25300-25300/fluttermapexample E/xxxxx: PlatformView2 getView
2019-09-04 11:53:29.963 25300-25300/fluttermapexample E/xxxxx: MyTextView2 onMeasure > width: 1080, height: 1680
2019-09-04 11:53:29.989 25300-25300/fluttermapexample E/xxxxx: MyTextView2 onMeasure > width: 1080, height: 1680
2019-09-04 11:53:29.989 25300-25300/fluttermapexample E/xxxxx: MyTextView2 onLayout > left: 0, top: 0, right: 1080, bottom: 1680
2019-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 reference
at 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 reference
at 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() {
@Override
public 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=false
mCurTaskIdForUser={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。