Skip to content

Commit 3a19325

Browse files
authored
Reland "Apply rect clipping to surface views" (#184732)
Relands #184471 which [was reverted](#184728) because of suspected skia gold bug where the triage button was not working for the created digests. I also changed the names of the output files just in case, to make sure the state doesn't collide with old.
1 parent 9ff5ef3 commit 3a19325

4 files changed

Lines changed: 353 additions & 1 deletion

File tree

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:convert';
6+
7+
import 'package:android_driver_extensions/extension.dart';
8+
import 'package:flutter/foundation.dart';
9+
import 'package:flutter/gestures.dart';
10+
import 'package:flutter/material.dart';
11+
import 'package:flutter/rendering.dart';
12+
import 'package:flutter/services.dart';
13+
import 'package:flutter_driver/driver_extension.dart';
14+
15+
import '../src/allow_list_devices.dart';
16+
17+
void main() async {
18+
ensureAndroidDevice();
19+
enableFlutterDriverExtension(
20+
handler: (String? command) async {
21+
return json.encode(<String, Object?>{
22+
'supported': await HybridAndroidViewController.checkIfSupported(),
23+
});
24+
},
25+
commands: <CommandExtension>[nativeDriverCommands],
26+
);
27+
28+
// Run on full screen.
29+
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
30+
runApp(const SimpleClipRectApp());
31+
}
32+
33+
class SimpleClipRectApp extends StatelessWidget {
34+
const SimpleClipRectApp({super.key});
35+
36+
@override
37+
Widget build(BuildContext context) {
38+
return MaterialApp(
39+
theme: ThemeData(elevatedButtonTheme: const ElevatedButtonThemeData()),
40+
home: const ClipRectHomePage(),
41+
);
42+
}
43+
}
44+
45+
class ClipRectHomePage extends StatefulWidget {
46+
const ClipRectHomePage({super.key});
47+
48+
@override
49+
State<ClipRectHomePage> createState() => _ClipRectHomePageState();
50+
}
51+
52+
class _ClipRectHomePageState extends State<ClipRectHomePage> {
53+
bool _isClipped = false;
54+
55+
void _toggleClip() {
56+
setState(() {
57+
_isClipped = !_isClipped;
58+
});
59+
}
60+
61+
@override
62+
Widget build(BuildContext context) {
63+
// Content that will be clipped
64+
Widget content = const Stack(
65+
alignment: Alignment.center,
66+
children: <Widget>[
67+
// Background
68+
SizedBox.square(dimension: 500, child: ColoredBox(color: Colors.green)),
69+
// Platform View
70+
SizedBox.square(
71+
dimension: 400,
72+
child: _HybridCompositionAndroidPlatformView(
73+
viewType: 'blue_orange_gradient_surface_view_platform_view',
74+
),
75+
),
76+
],
77+
);
78+
79+
// Apply the ClipRect conditionally
80+
if (_isClipped) {
81+
content = ClipRect(clipper: const SimpleRectClipper(dimension: 300.0), child: content);
82+
}
83+
84+
return Scaffold(
85+
body: Column(
86+
children: <Widget>[
87+
// Button to toggle the clip state
88+
Padding(
89+
padding: const EdgeInsets.all(8.0),
90+
child: ElevatedButton(
91+
key: const ValueKey<String>('toggle_cliprect_button'),
92+
onPressed: _toggleClip,
93+
child: Text(_isClipped ? 'Disable ClipRect' : 'Enable ClipRect'),
94+
),
95+
),
96+
// Expanded takes remaining space for the clipped content
97+
Expanded(child: Center(child: content)),
98+
],
99+
),
100+
);
101+
}
102+
}
103+
104+
// A simple clipper that enforces a strict bounding box of the specified dimension
105+
class SimpleRectClipper extends CustomClipper<Rect> {
106+
const SimpleRectClipper({required this.dimension});
107+
108+
final double dimension;
109+
110+
@override
111+
Rect getClip(Size size) {
112+
// Centers the clip area over the widget
113+
return Rect.fromCenter(
114+
center: Offset(size.width / 2, size.height / 2),
115+
width: dimension,
116+
height: dimension,
117+
);
118+
}
119+
120+
@override
121+
bool shouldReclip(covariant SimpleRectClipper oldClipper) {
122+
return oldClipper.dimension != dimension;
123+
}
124+
}
125+
126+
// --- Platform View Definition ---
127+
final class _HybridCompositionAndroidPlatformView extends StatelessWidget {
128+
const _HybridCompositionAndroidPlatformView({required this.viewType});
129+
130+
final String viewType;
131+
132+
@override
133+
Widget build(BuildContext context) {
134+
return PlatformViewLink(
135+
viewType: viewType,
136+
surfaceFactory: (BuildContext context, PlatformViewController controller) {
137+
return AndroidViewSurface(
138+
controller: controller as AndroidViewController,
139+
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
140+
hitTestBehavior: PlatformViewHitTestBehavior.transparent,
141+
);
142+
},
143+
onCreatePlatformView: (PlatformViewCreationParams params) {
144+
return PlatformViewsService.initHybridAndroidView(
145+
id: params.id,
146+
viewType: viewType,
147+
layoutDirection: TextDirection.ltr,
148+
creationParamsCodec: const StandardMessageCodec(),
149+
)
150+
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
151+
..create();
152+
},
153+
);
154+
}
155+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:convert';
6+
7+
import 'package:android_driver_extensions/native_driver.dart';
8+
import 'package:android_driver_extensions/skia_gold.dart';
9+
import 'package:flutter_driver/flutter_driver.dart';
10+
import 'package:test/test.dart';
11+
12+
import '../_luci_skia_gold_prelude.dart';
13+
14+
/// For local debugging, a (local) golden-file is required as a baseline:
15+
///
16+
/// ```sh
17+
/// # Checkout HEAD, i.e. *before* changes you want to test.
18+
/// UPDATE_GOLDENS=1 flutter drive lib/hcpp/platform_view_cliprect_surfaceview_main.dart
19+
///
20+
/// # Make your changes.
21+
///
22+
/// # Run the test against baseline.
23+
/// flutter drive lib/hcpp/platform_view_cliprect_surfaceview_main.dart
24+
/// ```
25+
///
26+
/// For a convenient way to deflake a test, see `tool/deflake.dart`.
27+
void main() async {
28+
const goldenPrefix = 'hybrid_composition_pp_platform_view';
29+
30+
late final FlutterDriver flutterDriver;
31+
late final NativeDriver nativeDriver;
32+
33+
setUpAll(() async {
34+
if (isLuci) {
35+
await enableSkiaGoldComparator(namePrefix: 'android_engine_test$goldenVariant');
36+
}
37+
flutterDriver = await FlutterDriver.connect();
38+
nativeDriver = await AndroidNativeDriver.connect(flutterDriver);
39+
await nativeDriver.configureForScreenshotTesting();
40+
await flutterDriver.waitUntilFirstFrameRasterized();
41+
});
42+
43+
tearDownAll(() async {
44+
await nativeDriver.close();
45+
await flutterDriver.close();
46+
});
47+
48+
test('verify that HCPP is supported and enabled', () async {
49+
final response = json.decode(await flutterDriver.requestData('')) as Map<String, Object?>;
50+
51+
expect(response['supported'], true);
52+
}, timeout: Timeout.none);
53+
54+
test('should screenshot a platform view with no rect clipping', () async {
55+
await expectLater(
56+
nativeDriver.screenshot(),
57+
matchesGoldenFile('$goldenPrefix.no_rect_surfaceview.png'),
58+
);
59+
}, timeout: Timeout.none);
60+
61+
test('should properly clip surfaceview', () async {
62+
await flutterDriver.tap(find.byValueKey('toggle_cliprect_button'));
63+
await expectLater(
64+
nativeDriver.screenshot(),
65+
matchesGoldenFile('$goldenPrefix.yes_rect_surfaceview.png'),
66+
);
67+
}, timeout: Timeout.none);
68+
}

docs/platforms/android/Android-Platform-Views.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ You can enable HCPP using one of the following methods:
6868

6969
### Limitations and Known Issues
7070
The following is a list of limitations and known issues. If you encounter an issue not listed below, please file an issue!
71-
- **SurfaceView Compatibility**: Opting in is currently not recommended if your application contains a platform view which is or contains a native [`SurfaceView`](https://developer.android.com/reference/android/view/SurfaceView) (often used by video players or map plugins), due to clipping issues. This is tracked in https://github.com/flutter/flutter/issues/175546.
7271
- **Complex Overlay Stacking**: Transparent platform views will not display correctly in layout stacks structured as: Flutter canvas -> Platform View -> Overlay -> Transparent Platform View, when all four of these layers intersect.
7372

7473
## Virtual Display

0 commit comments

Comments
 (0)