[ios platform view] fix overlay zPosition (flutter/engine#29930)

This commit is contained in:
Chris Yang
2021-12-07 16:04:01 -08:00
committed by GitHub
parent 670277d11c
commit 22693e2599
5 changed files with 234 additions and 75 deletions

View File

@@ -575,15 +575,19 @@ void FlutterPlatformViewsController::BringLayersIntoView(LayersMap layer_map) {
if (platform_view_root.superview != flutter_view) {
[flutter_view addSubview:platform_view_root];
} else {
platform_view_root.layer.zPosition = zIndex++;
}
// Make sure the platform_view_root is higher than the last platform_view_root in
// composition_order_.
platform_view_root.layer.zPosition = zIndex++;
for (const std::shared_ptr<FlutterPlatformViewLayer>& layer : layers) {
if ([layer->overlay_view_wrapper superview] != flutter_view) {
if ([layer->overlay_view_wrapper.get() superview] != flutter_view) {
[flutter_view addSubview:layer->overlay_view_wrapper];
} else {
layer->overlay_view_wrapper.get().layer.zPosition = zIndex++;
}
// Make sure all the overlays are higher than the platform view.
layer->overlay_view_wrapper.get().layer.zPosition = zIndex++;
FML_DCHECK(layer->overlay_view_wrapper.get().layer.zPosition >
platform_view_root.layer.zPosition);
}
active_composition_order_.push_back(platform_view_id);
}

View File

@@ -61,6 +61,7 @@
@"--bogus-font-text" : @"bogus_font_text",
@"--spawn-engine-works" : @"spawn_engine_works",
@"--pointer-events" : @"pointer_events",
@"--platform-view-scrolling-under-widget" : @"platform_view_scrolling_under_widget"
};
__block NSString* flutterViewControllerTestName = nil;
[launchArgsMap

View File

@@ -257,3 +257,40 @@ static const NSInteger kSecondsToWaitForPlatformView = 30;
}
@end
@interface PlatformViewScrollingUnderWidget : XCTestCase
@end
@implementation PlatformViewScrollingUnderWidget
- (void)setUp {
[super setUp];
self.continueAfterFailure = NO;
}
- (void)testPlatformViewScrollingUnderWidget {
XCUIApplication* app = [[XCUIApplication alloc] init];
app.launchArguments =
@[ @"--platform-view-scrolling-under-widget", @"--with-continuous-texture" ];
[app launch];
XCUIElement* platformView = app.textViews.firstMatch;
BOOL exists = [platformView waitForExistenceWithTimeout:kSecondsToWaitForPlatformView];
if (!exists) {
XCTFail(@"It took longer than %@ second to find the platform view."
@"There might be issues with the platform view's construction,"
@"or with how the scenario is built.",
@(kSecondsToWaitForPlatformView));
}
// Wait and let the scenario app scroll a bit.
XCTWaiterResult waitResult = [XCTWaiter
waitForExpectations:@[ [[XCTestExpectation alloc] initWithDescription:@"Wait for 5 seconds"] ]
timeout:5];
// If the waiter is not interrupted, we know the app is in a valid state after timeout, thus the
// test passes.
XCTAssert(waitResult != XCTWaiterResultInterrupted);
}
@end

View File

@@ -28,11 +28,13 @@ List<int> _to64(num value) {
}
/// A simple platform view.
class PlatformViewScenario extends Scenario with _BasePlatformViewScenarioMixin {
class PlatformViewScenario extends Scenario
with _BasePlatformViewScenarioMixin {
/// Creates the PlatformView scenario.
///
/// The [dispatcher] parameter must not be null.
PlatformViewScenario(PlatformDispatcher dispatcher, String text, { required this.id })
PlatformViewScenario(PlatformDispatcher dispatcher, String text,
{required this.id})
: assert(dispatcher != null),
super(dispatcher) {
createPlatformView(dispatcher, text, id);
@@ -75,11 +77,14 @@ class NonFullScreenFlutterViewPlatformViewScenario extends Scenario
}
/// A simple platform view with overlay that doesn't intersect with the platform view.
class PlatformViewNoOverlayIntersectionScenario extends Scenario with _BasePlatformViewScenarioMixin {
class PlatformViewNoOverlayIntersectionScenario extends Scenario
with _BasePlatformViewScenarioMixin {
/// Creates the PlatformView scenario.
///
/// The [dispatcher] parameter must not be null.
PlatformViewNoOverlayIntersectionScenario(PlatformDispatcher dispatcher, String text, { required this.id })
PlatformViewNoOverlayIntersectionScenario(
PlatformDispatcher dispatcher, String text,
{required this.id})
: assert(dispatcher != null),
super(dispatcher) {
createPlatformView(dispatcher, text, id);
@@ -101,11 +106,14 @@ class PlatformViewNoOverlayIntersectionScenario extends Scenario with _BasePlatf
}
/// A simple platform view with an overlay that partially intersects with the platform view.
class PlatformViewPartialIntersectionScenario extends Scenario with _BasePlatformViewScenarioMixin {
class PlatformViewPartialIntersectionScenario extends Scenario
with _BasePlatformViewScenarioMixin {
/// Creates the PlatformView scenario.
///
/// The [dispatcher] parameter must not be null.
PlatformViewPartialIntersectionScenario(PlatformDispatcher dispatcher, String text, { required this.id })
PlatformViewPartialIntersectionScenario(
PlatformDispatcher dispatcher, String text,
{required this.id})
: assert(dispatcher != null),
super(dispatcher) {
createPlatformView(dispatcher, text, id);
@@ -127,11 +135,14 @@ class PlatformViewPartialIntersectionScenario extends Scenario with _BasePlatfor
}
/// A simple platform view with two overlays that intersect with each other and the platform view.
class PlatformViewTwoIntersectingOverlaysScenario extends Scenario with _BasePlatformViewScenarioMixin {
class PlatformViewTwoIntersectingOverlaysScenario extends Scenario
with _BasePlatformViewScenarioMixin {
/// Creates the PlatformView scenario.
///
/// The [dispatcher] parameter must not be null.
PlatformViewTwoIntersectingOverlaysScenario(PlatformDispatcher dispatcher, String text, { required this.id })
PlatformViewTwoIntersectingOverlaysScenario(
PlatformDispatcher dispatcher, String text,
{required this.id})
: assert(dispatcher != null),
super(dispatcher) {
createPlatformView(dispatcher, text, id);
@@ -166,11 +177,14 @@ class PlatformViewTwoIntersectingOverlaysScenario extends Scenario with _BasePla
}
/// A simple platform view with one overlay and two overlays that intersect with each other and the platform view.
class PlatformViewOneOverlayTwoIntersectingOverlaysScenario extends Scenario with _BasePlatformViewScenarioMixin {
class PlatformViewOneOverlayTwoIntersectingOverlaysScenario extends Scenario
with _BasePlatformViewScenarioMixin {
/// Creates the PlatformView scenario.
///
/// The [dispatcher] parameter must not be null.
PlatformViewOneOverlayTwoIntersectingOverlaysScenario(PlatformDispatcher dispatcher, String text, { required this.id })
PlatformViewOneOverlayTwoIntersectingOverlaysScenario(
PlatformDispatcher dispatcher, String text,
{required this.id})
: assert(dispatcher != null),
super(dispatcher) {
createPlatformView(dispatcher, text, id);
@@ -210,11 +224,14 @@ class PlatformViewOneOverlayTwoIntersectingOverlaysScenario extends Scenario wit
}
/// Two platform views without an overlay intersecting either platform view.
class MultiPlatformViewWithoutOverlaysScenario extends Scenario with _BasePlatformViewScenarioMixin {
class MultiPlatformViewWithoutOverlaysScenario extends Scenario
with _BasePlatformViewScenarioMixin {
/// Creates the PlatformView scenario.
///
/// The [dispatcher] parameter must not be null.
MultiPlatformViewWithoutOverlaysScenario(PlatformDispatcher dispatcher, String text, { required this.firstId, required this.secondId })
MultiPlatformViewWithoutOverlaysScenario(
PlatformDispatcher dispatcher, String text,
{required this.firstId, required this.secondId})
: assert(dispatcher != null),
super(dispatcher) {
createPlatformView(dispatcher, text, firstId);
@@ -254,11 +271,13 @@ class MultiPlatformViewWithoutOverlaysScenario extends Scenario with _BasePlatfo
}
/// A simple platform view with too many overlays result in a single native view.
class PlatformViewMaxOverlaysScenario extends Scenario with _BasePlatformViewScenarioMixin {
class PlatformViewMaxOverlaysScenario extends Scenario
with _BasePlatformViewScenarioMixin {
/// Creates the PlatformView scenario.
///
/// The [dispatcher] parameter must not be null.
PlatformViewMaxOverlaysScenario(PlatformDispatcher dispatcher, String text, { required this.id })
PlatformViewMaxOverlaysScenario(PlatformDispatcher dispatcher, String text,
{required this.id})
: assert(dispatcher != null),
super(dispatcher) {
createPlatformView(dispatcher, text, id);
@@ -303,11 +322,13 @@ class PlatformViewMaxOverlaysScenario extends Scenario with _BasePlatformViewSce
}
/// Builds a scene with 2 platform views.
class MultiPlatformViewScenario extends Scenario with _BasePlatformViewScenarioMixin {
class MultiPlatformViewScenario extends Scenario
with _BasePlatformViewScenarioMixin {
/// Creates the PlatformView scenario.
///
/// The [dispatcher] parameter must not be null.
MultiPlatformViewScenario(PlatformDispatcher dispatcher, {required this.firstId, required this.secondId})
MultiPlatformViewScenario(PlatformDispatcher dispatcher,
{required this.firstId, required this.secondId})
: assert(dispatcher != null),
super(dispatcher) {
createPlatformView(dispatcher, 'platform view 1', firstId);
@@ -337,11 +358,13 @@ class MultiPlatformViewScenario extends Scenario with _BasePlatformViewScenarioM
/// Renders a frame with 2 platform views covered by a flutter drawn rectangle,
/// when the app goes to the background and comes back to the foreground renders a new frame
/// with the 2 platform views but without the flutter drawn rectangle.
class MultiPlatformViewBackgroundForegroundScenario extends Scenario with _BasePlatformViewScenarioMixin {
class MultiPlatformViewBackgroundForegroundScenario extends Scenario
with _BasePlatformViewScenarioMixin {
/// Creates the PlatformView scenario.
///
/// The [dispatcher] parameter must not be null.
MultiPlatformViewBackgroundForegroundScenario(PlatformDispatcher dispatcher, {required this.firstId, required this.secondId})
MultiPlatformViewBackgroundForegroundScenario(PlatformDispatcher dispatcher,
{required this.firstId, required this.secondId})
: assert(dispatcher != null),
super(dispatcher) {
_nextFrame = _firstFrame;
@@ -405,15 +428,16 @@ class MultiPlatformViewBackgroundForegroundScenario extends Scenario with _BaseP
@override
void onPlatformMessage(
String name,
ByteData? data,
PlatformMessageResponseCallback? callback,
) {
String name,
ByteData? data,
PlatformMessageResponseCallback? callback,
) {
if (name != 'flutter/lifecycle') {
return;
}
final String message = utf8.decode(data!.buffer.asUint8List());
if (_lastLifecycleState == 'AppLifecycleState.inactive' && message == 'AppLifecycleState.resumed') {
if (_lastLifecycleState == 'AppLifecycleState.inactive' &&
message == 'AppLifecycleState.resumed') {
_nextFrame = _secondFrame;
window.scheduleFrame();
}
@@ -423,9 +447,11 @@ class MultiPlatformViewBackgroundForegroundScenario extends Scenario with _BaseP
}
/// Platform view with clip rect.
class PlatformViewClipRectScenario extends Scenario with _BasePlatformViewScenarioMixin {
class PlatformViewClipRectScenario extends Scenario
with _BasePlatformViewScenarioMixin {
/// Constructs a platform view with clip rect scenario.
PlatformViewClipRectScenario(PlatformDispatcher dispatcher, String text, { required this.id })
PlatformViewClipRectScenario(PlatformDispatcher dispatcher, String text,
{required this.id})
: assert(dispatcher != null),
super(dispatcher) {
createPlatformView(dispatcher, text, id);
@@ -436,8 +462,7 @@ class PlatformViewClipRectScenario extends Scenario with _BasePlatformViewScenar
@override
void onBeginFrame(Duration duration) {
final SceneBuilder builder =
SceneBuilder()
final SceneBuilder builder = SceneBuilder()
..pushClipRect(const Rect.fromLTRB(100, 100, 400, 400));
finishBuilderByAddingPlatformViewAndPicture(builder, id);
@@ -447,7 +472,8 @@ class PlatformViewClipRectScenario extends Scenario with _BasePlatformViewScenar
/// Platform view with clip rrect.
class PlatformViewClipRRectScenario extends PlatformViewScenario {
/// Constructs a platform view with clip rrect scenario.
PlatformViewClipRRectScenario(PlatformDispatcher dispatcher, String text, { int id = 0 })
PlatformViewClipRRectScenario(PlatformDispatcher dispatcher, String text,
{int id = 0})
: super(dispatcher, text, id: id);
@override
@@ -471,7 +497,8 @@ class PlatformViewClipRRectScenario extends PlatformViewScenario {
/// Platform view with clip path.
class PlatformViewClipPathScenario extends PlatformViewScenario {
/// Constructs a platform view with clip rrect scenario.
PlatformViewClipPathScenario(PlatformDispatcher dispatcher, String text, { int id = 0 })
PlatformViewClipPathScenario(PlatformDispatcher dispatcher, String text,
{int id = 0})
: super(dispatcher, text, id: id);
@override
@@ -491,7 +518,8 @@ class PlatformViewClipPathScenario extends PlatformViewScenario {
/// Platform view with transform.
class PlatformViewTransformScenario extends PlatformViewScenario {
/// Constructs a platform view with transform scenario.
PlatformViewTransformScenario(PlatformDispatcher dispatcher, String text, { int id = 0 })
PlatformViewTransformScenario(PlatformDispatcher dispatcher, String text,
{int id = 0})
: super(dispatcher, text, id: id);
@override
@@ -510,7 +538,8 @@ class PlatformViewTransformScenario extends PlatformViewScenario {
/// Platform view with opacity.
class PlatformViewOpacityScenario extends PlatformViewScenario {
/// Constructs a platform view with transform scenario.
PlatformViewOpacityScenario(PlatformDispatcher dispatcher, String text, { int id = 0 })
PlatformViewOpacityScenario(PlatformDispatcher dispatcher, String text,
{int id = 0})
: super(dispatcher, text, id: id);
@override
@@ -526,13 +555,15 @@ class PlatformViewForTouchIOSScenario extends Scenario
/// Creates the PlatformView scenario.
///
/// The [dispatcher] parameter must not be null.
PlatformViewForTouchIOSScenario(PlatformDispatcher dispatcher, String text, {int id = 0, required bool accept, bool rejectUntilTouchesEnded = false})
PlatformViewForTouchIOSScenario(PlatformDispatcher dispatcher, String text,
{int id = 0, required bool accept, bool rejectUntilTouchesEnded = false})
: assert(dispatcher != null),
_accept = accept,
_viewId = id,
_accept = accept,
_viewId = id,
super(dispatcher) {
if (rejectUntilTouchesEnded) {
createPlatformView(dispatcher, text, id, viewType: 'scenarios/textPlatformView_blockPolicyUntilTouchesEnded');
createPlatformView(dispatcher, text, id,
viewType: 'scenarios/textPlatformView_blockPolicyUntilTouchesEnded');
} else {
createPlatformView(dispatcher, text, id);
}
@@ -563,32 +594,31 @@ class PlatformViewForTouchIOSScenario extends Scenario
@override
void onPointerDataPacket(PointerDataPacket packet) {
if (packet.data.first.change == PointerChange.add) {
String method = 'rejectGesture';
if (_accept) {
method = 'acceptGesture';
String method = 'rejectGesture';
if (_accept) {
method = 'acceptGesture';
}
const int _valueString = 7;
const int _valueInt32 = 3;
const int _valueMap = 13;
final Uint8List message = Uint8List.fromList(<int>[
_valueString,
method.length,
...utf8.encode(method),
_valueMap,
1,
_valueString,
'id'.length,
...utf8.encode('id'),
_valueInt32,
..._to32(_viewId),
]);
window.sendPlatformMessage(
'flutter/platform_views',
message.buffer.asByteData(),
(ByteData? response) {},
);
}
const int _valueString = 7;
const int _valueInt32 = 3;
const int _valueMap = 13;
final Uint8List message = Uint8List.fromList(<int>[
_valueString,
method.length,
...utf8.encode(method),
_valueMap,
1,
_valueString,
'id'.length,
...utf8.encode('id'),
_valueInt32,
..._to32(_viewId),
]);
window.sendPlatformMessage(
'flutter/platform_views',
message.buffer.asByteData(),
(ByteData? response) {},
);
}
}
void _firstFrame() {
@@ -606,14 +636,16 @@ class PlatformViewForTouchIOSScenario extends Scenario
/// For example, it simulates a video being played.
class PlatformViewWithContinuousTexture extends PlatformViewScenario {
/// Constructs a platform view with continuous texture layer.
PlatformViewWithContinuousTexture(PlatformDispatcher dispatcher, String text, { int id = 0 })
PlatformViewWithContinuousTexture(PlatformDispatcher dispatcher, String text,
{int id = 0})
: super(dispatcher, text, id: id);
@override
void onBeginFrame(Duration duration) {
final SceneBuilder builder = SceneBuilder();
builder.addTexture(0, width: 300, height: 300, offset: const Offset(200, 200));
builder.addTexture(0,
width: 300, height: 300, offset: const Offset(200, 200));
finishBuilderByAddingPlatformViewAndPicture(builder, id);
}
@@ -627,7 +659,9 @@ class PlatformViewWithContinuousTexture extends PlatformViewScenario {
/// See: https://github.com/flutter/flutter/issues/80766
class PlatformViewWithOtherBackDropFilter extends PlatformViewScenario {
/// Constructs the scenario.
PlatformViewWithOtherBackDropFilter(PlatformDispatcher dispatcher, String text, { int id = 0 })
PlatformViewWithOtherBackDropFilter(
PlatformDispatcher dispatcher, String text,
{int id = 0})
: super(dispatcher, text, id: id);
@override
@@ -692,9 +726,11 @@ class PlatformViewWithOtherBackDropFilter extends PlatformViewScenario {
/// The stack would look like: picture 1 -> pv1 -> picture 2 -> filter -> pv2 - > picture 3.
/// Because backdrop filter on platform views has not been implemented(see: https://github.com/flutter/flutter/issues/43902),
/// the result will not including a filtered pv1.
class TwoPlatformViewsWithOtherBackDropFilter extends Scenario with _BasePlatformViewScenarioMixin {
class TwoPlatformViewsWithOtherBackDropFilter extends Scenario
with _BasePlatformViewScenarioMixin {
/// Constructs the scenario.
TwoPlatformViewsWithOtherBackDropFilter(PlatformDispatcher dispatcher, {required int firstId, required int secondId})
TwoPlatformViewsWithOtherBackDropFilter(PlatformDispatcher dispatcher,
{required int firstId, required int secondId})
: _firstId = firstId,
_secondId = secondId,
super(dispatcher) {
@@ -765,6 +801,83 @@ class TwoPlatformViewsWithOtherBackDropFilter extends Scenario with _BasePlatfor
}
}
/// Builds a scenario where many platform views are scrolling and pass under a picture.
class PlatformViewScrollingUnderWidget extends Scenario
with _BasePlatformViewScenarioMixin {
/// Creates the PlatformView scenario.
///
/// The [dispatcher] parameter must not be null.
PlatformViewScrollingUnderWidget(PlatformDispatcher dispatcher,
{required int firstPlatformViewId, required int lastPlatformViewId})
: _firstPlatformViewId = firstPlatformViewId,
_lastPlatformViewId = lastPlatformViewId,
assert(dispatcher != null),
super(dispatcher) {
for (int i = _firstPlatformViewId; i <= _lastPlatformViewId; i++) {
createPlatformView(dispatcher, 'platform view', i);
}
}
final int _firstPlatformViewId;
final int _lastPlatformViewId;
double _offset = 0;
bool _movingUp = true;
@override
void onBeginFrame(Duration duration) {
_buildOneFrame(_offset);
}
@override
void onDrawFrame() {
// Scroll up until -1000, then scroll down until -1.
if (_offset < -1000) {
_movingUp = false;
} else if (_offset > -1) {
_movingUp = true;
}
if (_movingUp) {
_offset -= 100;
} else {
_offset += 100;
}
window.scheduleFrame();
super.onDrawFrame();
}
void _buildOneFrame(double offset) {
const double cellWidth = 1000;
double localOffset = offset;
final SceneBuilder builder = SceneBuilder();
const double cellHeight = 300;
for (int i = _firstPlatformViewId; i <= _lastPlatformViewId; i++) {
// Build a list view with platform views.
builder.pushOffset(0, localOffset);
_addPlatformViewToScene(builder, i, cellWidth, cellHeight);
builder.pop();
localOffset += cellHeight;
}
// Add a "banner" that should display on top of the list view.
final PictureRecorder recorder = PictureRecorder();
final Canvas canvas = Canvas(recorder);
canvas.drawRect(
const Rect.fromLTRB(0, cellHeight, cellWidth, 100),
Paint()..color = const Color(0xFFFF0000),
);
final Picture picture = recorder.endRecording();
builder.addPicture(const Offset(0, 20), picture);
final Scene scene = builder.build();
window.render(scene);
scene.dispose();
}
}
mixin _BasePlatformViewScenarioMixin on Scenario {
int? _textureId;
@@ -777,7 +890,8 @@ mixin _BasePlatformViewScenarioMixin on Scenario {
/// It prepare a TextPlatformView so it can be added to the SceneBuilder in `onBeginFrame`.
/// Call this method in the constructor of the platform view related scenarios
/// to perform necessary set up.
void createPlatformView(PlatformDispatcher dispatcher, String text, int id, {String viewType = 'scenarios/textPlatformView'}) {
void createPlatformView(PlatformDispatcher dispatcher, String text, int id,
{String viewType = 'scenarios/textPlatformView'}) {
const int _valueTrue = 1;
const int _valueInt32 = 3;
const int _valueFloat64 = 6;
@@ -790,8 +904,7 @@ mixin _BasePlatformViewScenarioMixin on Scenario {
'create'.length, // this won't work if we use multi-byte characters.
...utf8.encode('create'),
_valueMap,
if (Platform.isIOS)
3, // 3 entries in map for iOS.
if (Platform.isIOS) 3, // 3 entries in map for iOS.
if (Platform.isAndroid && !usesAndroidHybridComposition)
6, // 6 entries in map for virtual displays on Android.
if (Platform.isAndroid && usesAndroidHybridComposition)
@@ -847,7 +960,9 @@ mixin _BasePlatformViewScenarioMixin on Scenario {
'flutter/platform_views',
message.buffer.asByteData(),
(ByteData? response) {
if (response != null && Platform.isAndroid && !usesAndroidHybridComposition) {
if (response != null &&
Platform.isAndroid &&
!usesAndroidHybridComposition) {
// Envelope.
_textureId = response.getUint8(0);
}
@@ -870,7 +985,8 @@ mixin _BasePlatformViewScenarioMixin on Scenario {
sceneBuilder.addTexture(_textureId!, width: width, height: height);
}
} else {
throw UnsupportedError('Platform ${Platform.operatingSystem} is not supported');
throw UnsupportedError(
'Platform ${Platform.operatingSystem} is not supported');
}
}

View File

@@ -43,6 +43,7 @@ Map<String, ScenarioFactory> _scenarios = <String, ScenarioFactory>{
'platform_view_gesture_reject_eager': () => PlatformViewForTouchIOSScenario(PlatformDispatcher.instance, 'platform view touch', id: _viewId++, accept: false),
'platform_view_gesture_accept': () => PlatformViewForTouchIOSScenario(PlatformDispatcher.instance, 'platform view touch', id: _viewId++, accept: true),
'platform_view_gesture_reject_after_touches_ended': () => PlatformViewForTouchIOSScenario(PlatformDispatcher.instance, 'platform view touch', id: _viewId++, accept: false, rejectUntilTouchesEnded: true),
'platform_view_scrolling_under_widget':()=>PlatformViewScrollingUnderWidget(PlatformDispatcher.instance, firstPlatformViewId: _viewId++, lastPlatformViewId: _viewId+=16),
'tap_status_bar': () => TouchesScenario(PlatformDispatcher.instance),
'text_semantics_focus': () => SendTextFocusSemantics(PlatformDispatcher.instance),
'initial_route_reply': () => InitialRouteReply(PlatformDispatcher.instance),