Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit a28b7f0

Browse files
author
Emmanuel Garcia
authored
Implement onDisplayPlatformView (#19344)
1 parent b3cfbd2 commit a28b7f0

File tree

2 files changed

+159
-38
lines changed

2 files changed

+159
-38
lines changed

shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -80,29 +80,47 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result
8080

8181
private void create(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
8282
Map<String, Object> createArgs = call.arguments();
83+
boolean usesHybridComposition =
84+
createArgs.containsKey("hybrid") && (boolean) createArgs.get("hybrid");
85+
// In hybrid mode, the size of the view is determined by the size of the Flow layer.
86+
double width = (usesHybridComposition) ? 0 : (double) createArgs.get("width");
87+
double height = (usesHybridComposition) ? 0 : (double) createArgs.get("height");
88+
8389
PlatformViewCreationRequest request =
8490
new PlatformViewCreationRequest(
8591
(int) createArgs.get("id"),
8692
(String) createArgs.get("viewType"),
87-
(double) createArgs.get("width"),
88-
(double) createArgs.get("height"),
93+
width,
94+
height,
8995
(int) createArgs.get("direction"),
9096
createArgs.containsKey("params")
9197
? ByteBuffer.wrap((byte[]) createArgs.get("params"))
9298
: null);
93-
9499
try {
95-
long textureId = handler.createPlatformView(request);
96-
result.success(textureId);
100+
if (usesHybridComposition) {
101+
handler.createAndroidViewForPlatformView(request);
102+
result.success(null);
103+
} else {
104+
long textureId = handler.createVirtualDisplayForPlatformView(request);
105+
result.success(textureId);
106+
}
97107
} catch (IllegalStateException exception) {
98108
result.error("error", detailedExceptionString(exception), null);
99109
}
100110
}
101111

102112
private void dispose(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
103-
int viewId = call.arguments();
113+
Map<String, Object> disposeArgs = call.arguments();
114+
int viewId = (int) disposeArgs.get("id");
115+
boolean usesHybridComposition =
116+
disposeArgs.containsKey("hybrid") && (boolean) disposeArgs.get("hybrid");
117+
104118
try {
105-
handler.disposePlatformView(viewId);
119+
if (usesHybridComposition) {
120+
handler.disposeAndroidViewForPlatformView(viewId);
121+
} else {
122+
handler.disposeVirtualDisplayForPlatformView(viewId);
123+
}
106124
result.success(null);
107125
} catch (IllegalStateException exception) {
108126
result.error("error", detailedExceptionString(exception), null);
@@ -216,18 +234,29 @@ public interface PlatformViewsHandler {
216234
* The Flutter application would like to display a new Android {@code View}, i.e., platform
217235
* view.
218236
*
219-
* <p>The handler should instantiate the desired Android {@code View}, create a new {@link
220-
* io.flutter.view.FlutterView.SurfaceTextureRegistryEntry} within the given Flutter execution
221-
* context, and then return the new texture's ID.
237+
* <p>The Android {@code View} is added to the view hierarchy.
222238
*/
223-
// TODO(mattcarroll): Introduce an annotation for @TextureId
224-
long createPlatformView(@NonNull PlatformViewCreationRequest request);
239+
void createAndroidViewForPlatformView(@NonNull PlatformViewCreationRequest request);
225240

226241
/**
227-
* The Flutter application could like dispose of an existing Android {@code View}, i.e.,
228-
* platform view.
242+
* The Flutter application would like to dispose of an existing Android {@code View} rendered in
243+
* the view hierarchy.
229244
*/
230-
void disposePlatformView(int viewId);
245+
void disposeAndroidViewForPlatformView(int viewId);
246+
247+
/**
248+
* The Flutter application would like to display a new Android {@code View}.
249+
*
250+
* <p>{@code View} is added to a {@code VirtualDisplay}. The framework uses id returned by this
251+
* method to lookup the texture in the engine.
252+
*/
253+
long createVirtualDisplayForPlatformView(@NonNull PlatformViewCreationRequest request);
254+
255+
/**
256+
* The Flutter application would like to dispose of an existing Android {@code View} rendered in
257+
* a virtual display.
258+
*/
259+
void disposeVirtualDisplayForPlatformView(int viewId);
231260

232261
/**
233262
* The Flutter application would like to resize an existing Android {@code View}, i.e., platform
@@ -279,6 +308,7 @@ public static class PlatformViewCreationRequest {
279308
/** Custom parameters that are unique to the desired platform view. */
280309
@Nullable public final ByteBuffer params;
281310

311+
/** Creates a request to construct a platform view that uses a virtual display. */
282312
public PlatformViewCreationRequest(
283313
int viewId,
284314
@NonNull String viewType,
@@ -295,7 +325,11 @@ public PlatformViewCreationRequest(
295325
}
296326
}
297327

298-
/** Request sent from Flutter to resize a platform view. */
328+
/**
329+
* Request sent from Flutter to resize a platform view.
330+
*
331+
* <p>This only applies to platform views that use virtual displays.
332+
*/
299333
public static class PlatformViewResizeRequest {
300334
/** The ID of the platform view as seen by the Flutter side. */
301335
public final int viewId;

shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java

Lines changed: 109 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,6 @@
4444
public class PlatformViewsController implements PlatformViewsAccessibilityDelegate {
4545
private static final String TAG = "PlatformViewsController";
4646

47-
// API level 20 is required for VirtualDisplay#setSurface which we use when resizing a platform
48-
// view.
49-
private static final int MINIMAL_SDK = Build.VERSION_CODES.KITKAT_WATCH;
50-
5147
private final PlatformViewRegistryImpl registry;
5248

5349
// The context of the Activity or Fragment hosting the render target for the Flutter engine.
@@ -80,6 +76,9 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
8076
// it is associated with(e.g if a platform view creates other views in the same virtual display.
8177
private final HashMap<Context, View> contextToPlatformView;
8278

79+
private final SparseArray<PlatformViewsChannel.PlatformViewCreationRequest> platformViewRequests;
80+
private final SparseArray<View> platformViews;
81+
8382
// Map of unique IDs to views that render overlay layers.
8483
private final SparseArray<FlutterImageView> overlayLayerViews;
8584

@@ -92,14 +91,39 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
9291
// Overlay layer IDs that were displayed since the start of the current frame.
9392
private HashSet<Integer> currentFrameUsedOverlayLayerIds;
9493

94+
// Platform view IDs that were displayed since the start of the current frame.
95+
private HashSet<Integer> currentFrameUsedPlatformViewIds;
96+
9597
private final PlatformViewsChannel.PlatformViewsHandler channelHandler =
9698
new PlatformViewsChannel.PlatformViewsHandler() {
97-
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
99+
98100
@Override
99-
public long createPlatformView(
101+
public void createAndroidViewForPlatformView(
100102
@NonNull PlatformViewsChannel.PlatformViewCreationRequest request) {
101-
ensureValidAndroidVersion();
103+
// API level 19 is required for android.graphics.ImageReader.
104+
ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT);
105+
platformViewRequests.put(request.viewId, request);
106+
}
107+
108+
@Override
109+
public void disposeAndroidViewForPlatformView(int viewId) {
110+
// Hybrid view.
111+
if (platformViewRequests.get(viewId) != null) {
112+
platformViewRequests.remove(viewId);
113+
}
114+
if (platformViews.get(viewId) != null) {
115+
((FlutterView) flutterView).removeView(platformViews.get(viewId));
116+
platformViews.remove(viewId);
117+
}
118+
}
102119

120+
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
121+
@Override
122+
public long createVirtualDisplayForPlatformView(
123+
@NonNull PlatformViewsChannel.PlatformViewCreationRequest request) {
124+
// API level 20 is required for VirtualDisplay#setSurface which we use when resizing a
125+
// platform view.
126+
ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT_WATCH);
103127
if (!validateDirection(request.direction)) {
104128
throw new IllegalStateException(
105129
"Trying to create a view with unknown direction value: "
@@ -171,9 +195,8 @@ public long createPlatformView(
171195
}
172196

173197
@Override
174-
public void disposePlatformView(int viewId) {
175-
ensureValidAndroidVersion();
176-
198+
public void disposeVirtualDisplayForPlatformView(int viewId) {
199+
ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT_WATCH);
177200
VirtualDisplayController vdController = vdControllers.get(viewId);
178201
if (vdController == null) {
179202
throw new IllegalStateException(
@@ -193,7 +216,7 @@ public void disposePlatformView(int viewId) {
193216
public void resizePlatformView(
194217
@NonNull PlatformViewsChannel.PlatformViewResizeRequest request,
195218
@NonNull Runnable onComplete) {
196-
ensureValidAndroidVersion();
219+
ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT_WATCH);
197220

198221
final VirtualDisplayController vdController = vdControllers.get(request.viewId);
199222
if (vdController == null) {
@@ -224,8 +247,6 @@ public void run() {
224247

225248
@Override
226249
public void onTouch(@NonNull PlatformViewsChannel.PlatformViewTouch touch) {
227-
ensureValidAndroidVersion();
228-
229250
float density = context.getResources().getDisplayMetrics().density;
230251
PointerProperties[] pointerProperties =
231252
parsePointerPropertiesList(touch.rawPointerPropertiesList)
@@ -256,14 +277,13 @@ public void onTouch(@NonNull PlatformViewsChannel.PlatformViewTouch touch) {
256277
touch.source,
257278
touch.flags);
258279

280+
ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT_WATCH);
259281
vdControllers.get(touch.viewId).dispatchTouchEvent(event);
260282
}
261283

262284
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
263285
@Override
264286
public void setDirection(int viewId, int direction) {
265-
ensureValidAndroidVersion();
266-
267287
if (!validateDirection(direction)) {
268288
throw new IllegalStateException(
269289
"Trying to set unknown direction value: "
@@ -273,6 +293,7 @@ public void setDirection(int viewId, int direction) {
273293
+ ")");
274294
}
275295

296+
ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT_WATCH);
276297
View view = vdControllers.get(viewId).getView();
277298
if (view == null) {
278299
throw new IllegalStateException(
@@ -284,17 +305,18 @@ public void setDirection(int viewId, int direction) {
284305

285306
@Override
286307
public void clearFocus(int viewId) {
308+
ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT_WATCH);
287309
View view = vdControllers.get(viewId).getView();
288310
view.clearFocus();
289311
}
290312

291-
private void ensureValidAndroidVersion() {
292-
if (Build.VERSION.SDK_INT < MINIMAL_SDK) {
313+
private void ensureValidAndroidVersion(int minSdkVersion) {
314+
if (Build.VERSION.SDK_INT < minSdkVersion) {
293315
throw new IllegalStateException(
294316
"Trying to use platform views with API "
295317
+ Build.VERSION.SDK_INT
296318
+ ", required API level is: "
297-
+ MINIMAL_SDK);
319+
+ minSdkVersion);
298320
}
299321
}
300322
};
@@ -306,6 +328,10 @@ public PlatformViewsController() {
306328
contextToPlatformView = new HashMap<>();
307329
overlayLayerViews = new SparseArray<>();
308330
currentFrameUsedOverlayLayerIds = new HashSet<>();
331+
currentFrameUsedPlatformViewIds = new HashSet<>();
332+
333+
platformViewRequests = new SparseArray<>();
334+
platformViews = new SparseArray<>();
309335
}
310336

311337
/**
@@ -565,13 +591,61 @@ private void initializeRootImageViewIfNeeded() {
565591
}
566592
}
567593

594+
private void initializePlatformViewIfNeeded(int viewId) {
595+
if (platformViews.get(viewId) != null) {
596+
return;
597+
}
598+
599+
PlatformViewsChannel.PlatformViewCreationRequest request = platformViewRequests.get(viewId);
600+
if (request == null) {
601+
throw new IllegalStateException(
602+
"Platform view hasn't been initialized from the platform view channel.");
603+
}
604+
605+
if (!validateDirection(request.direction)) {
606+
throw new IllegalStateException(
607+
"Trying to create a view with unknown direction value: "
608+
+ request.direction
609+
+ "(view id: "
610+
+ viewId
611+
+ ")");
612+
}
613+
614+
PlatformViewFactory factory = registry.getFactory(request.viewType);
615+
if (factory == null) {
616+
throw new IllegalStateException(
617+
"Trying to create a platform view of unregistered type: " + request.viewType);
618+
}
619+
620+
Object createParams = null;
621+
if (request.params != null) {
622+
createParams = factory.getCreateArgsCodec().decodeMessage(request.params);
623+
}
624+
625+
PlatformView platformView = factory.create(context, viewId, createParams);
626+
View view = platformView.getView();
627+
platformViews.put(viewId, view);
628+
629+
((FlutterView) flutterView).addView(view);
630+
}
631+
568632
public void onDisplayPlatformView(int viewId, int x, int y, int width, int height) {
569633
initializeRootImageViewIfNeeded();
570-
// TODO: Implement this method. https://github.com/flutter/flutter/issues/58288
634+
initializePlatformViewIfNeeded(viewId);
635+
636+
View platformView = platformViews.get(viewId);
637+
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams((int) width, (int) height);
638+
layoutParams.leftMargin = (int) x;
639+
layoutParams.topMargin = (int) y;
640+
platformView.setLayoutParams(layoutParams);
641+
platformView.setVisibility(View.VISIBLE);
642+
platformView.bringToFront();
643+
currentFrameUsedPlatformViewIds.add(viewId);
571644
}
572645

573646
public void onDisplayOverlaySurface(int id, int x, int y, int width, int height) {
574647
initializeRootImageViewIfNeeded();
648+
575649
FlutterImageView overlayView = overlayLayerViews.get(id);
576650
if (overlayView.getParent() == null) {
577651
((FlutterView) flutterView).addView(overlayView);
@@ -588,19 +662,32 @@ public void onDisplayOverlaySurface(int id, int x, int y, int width, int height)
588662

589663
public void onBeginFrame() {
590664
currentFrameUsedOverlayLayerIds.clear();
665+
currentFrameUsedPlatformViewIds.clear();
591666
}
592667

593668
public void onEndFrame() {
669+
// Hide overlay surfaces that aren't rendered in the current frame.
594670
for (int i = 0; i < overlayLayerViews.size(); i++) {
595-
int key = overlayLayerViews.keyAt(i);
671+
int overlayId = overlayLayerViews.keyAt(i);
596672
FlutterImageView overlayView = overlayLayerViews.valueAt(i);
597-
if (currentFrameUsedOverlayLayerIds.contains(key)) {
673+
if (currentFrameUsedOverlayLayerIds.contains(overlayId)) {
598674
overlayView.acquireLatestImage();
599675
} else {
600676
overlayView.setVisibility(View.GONE);
601677
}
602678
}
603-
679+
// Hide platform views that aren't rendered in the current frame.
680+
// The platform view is destroyed by the framework after the widget is disposed.
681+
//
682+
// The framework diposes the platform view, when its `State` object will never
683+
// build again.
684+
for (int i = 0; i < platformViews.size(); i++) {
685+
int viewId = platformViews.keyAt(i);
686+
if (!currentFrameUsedPlatformViewIds.contains(viewId)) {
687+
platformViews.get(viewId).setVisibility(View.GONE);
688+
}
689+
}
690+
// If the background surface is still an image, then acquire the latest image.
604691
if (flutterViewConvertedToImageView) {
605692
((FlutterView) flutterView).acquireLatestImageViewFrame();
606693
}

0 commit comments

Comments
 (0)