mirror of
https://github.com/flutter/flutter
synced 2024-10-06 00:09:53 +00:00
Reland: "Use texture layer when displaying an Android view" (#100990)
This commit is contained in:
parent
f07f6fef9a
commit
032205eaca
1
.ci.yaml
1
.ci.yaml
|
@ -2023,6 +2023,7 @@ targets:
|
||||||
- name: Linux_android hybrid_android_views_integration_test
|
- name: Linux_android hybrid_android_views_integration_test
|
||||||
recipe: devicelab/devicelab_drone
|
recipe: devicelab/devicelab_drone
|
||||||
presubmit: false
|
presubmit: false
|
||||||
|
bringup: true # https://github.com/flutter/flutter/issues/100991
|
||||||
timeout: 60
|
timeout: 60
|
||||||
properties:
|
properties:
|
||||||
tags: >
|
tags: >
|
||||||
|
|
|
@ -16,7 +16,8 @@ found in the LICENSE file. -->
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize"
|
||||||
|
android:exported="true">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
|
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
|
||||||
android:value="true" />
|
android:value="true" />
|
||||||
|
@ -28,9 +29,5 @@ found in the LICENSE file. -->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2" />
|
android:value="2" />
|
||||||
<!-- Hybrid composition -->
|
|
||||||
<meta-data
|
|
||||||
android:name="io.flutter.embedded_views_preview"
|
|
||||||
android:value="true" />
|
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -17,6 +17,7 @@ class AndroidPlatformView extends StatelessWidget {
|
||||||
const AndroidPlatformView({
|
const AndroidPlatformView({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.onPlatformViewCreated,
|
this.onPlatformViewCreated,
|
||||||
|
this.useHybridComposition = false,
|
||||||
required this.viewType,
|
required this.viewType,
|
||||||
}) : assert(viewType != null),
|
}) : assert(viewType != null),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
@ -31,6 +32,9 @@ class AndroidPlatformView extends StatelessWidget {
|
||||||
/// May be null.
|
/// May be null.
|
||||||
final PlatformViewCreatedCallback? onPlatformViewCreated;
|
final PlatformViewCreatedCallback? onPlatformViewCreated;
|
||||||
|
|
||||||
|
// Use hybrid composition.
|
||||||
|
final bool useHybridComposition;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return PlatformViewLink(
|
return PlatformViewLink(
|
||||||
|
@ -44,17 +48,27 @@ class AndroidPlatformView extends StatelessWidget {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onCreatePlatformView: (PlatformViewCreationParams params) {
|
onCreatePlatformView: (PlatformViewCreationParams params) {
|
||||||
final AndroidViewController controller =
|
print('useHybridComposition=$useHybridComposition');
|
||||||
PlatformViewsService.initSurfaceAndroidView(
|
late AndroidViewController controller;
|
||||||
|
if (useHybridComposition) {
|
||||||
|
controller = PlatformViewsService.initExpensiveAndroidView(
|
||||||
id: params.id,
|
id: params.id,
|
||||||
viewType: params.viewType,
|
viewType: params.viewType,
|
||||||
layoutDirection: TextDirection.ltr,
|
layoutDirection: TextDirection.ltr,
|
||||||
)
|
);
|
||||||
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated);
|
} else {
|
||||||
|
controller = PlatformViewsService.initSurfaceAndroidView(
|
||||||
|
id: params.id,
|
||||||
|
viewType: params.viewType,
|
||||||
|
layoutDirection: TextDirection.ltr,
|
||||||
|
);
|
||||||
|
}
|
||||||
if (onPlatformViewCreated != null) {
|
if (onPlatformViewCreated != null) {
|
||||||
controller.addOnPlatformViewCreatedListener(onPlatformViewCreated!);
|
controller.addOnPlatformViewCreatedListener(onPlatformViewCreated!);
|
||||||
}
|
}
|
||||||
return controller..create();
|
return controller
|
||||||
|
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
|
||||||
|
..create();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ class NestedViewEventBodyState extends State<NestedViewEventBody> {
|
||||||
int? id;
|
int? id;
|
||||||
int nestedViewClickCount = 0;
|
int nestedViewClickCount = 0;
|
||||||
bool showPlatformView = true;
|
bool showPlatformView = true;
|
||||||
|
bool useHybridComposition = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -55,40 +56,64 @@ class NestedViewEventBodyState extends State<NestedViewEventBody> {
|
||||||
key: const ValueKey<String>('PlatformView'),
|
key: const ValueKey<String>('PlatformView'),
|
||||||
viewType: 'simple_view',
|
viewType: 'simple_view',
|
||||||
onPlatformViewCreated: onPlatformViewCreated,
|
onPlatformViewCreated: onPlatformViewCreated,
|
||||||
|
useHybridComposition: useHybridComposition,
|
||||||
) : null,
|
) : null,
|
||||||
),
|
),
|
||||||
if (_lastTestStatus != _LastTestStatus.pending) _statusWidget(),
|
if (_lastTestStatus != _LastTestStatus.pending) _statusWidget(),
|
||||||
if (viewChannel != null) ... <Widget>[
|
if (viewChannel != null) ... <Widget>[
|
||||||
ElevatedButton(
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton(
|
||||||
key: const ValueKey<String>('ShowAlertDialog'),
|
key: const ValueKey<String>('ShowAlertDialog'),
|
||||||
onPressed: onShowAlertDialogPressed,
|
onPressed: onShowAlertDialogPressed,
|
||||||
child: const Text('SHOW ALERT DIALOG'),
|
child: const Text('SHOW ALERT DIALOG'),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton(
|
||||||
key: const ValueKey<String>('TogglePlatformView'),
|
key: const ValueKey<String>('TogglePlatformView'),
|
||||||
onPressed: onTogglePlatformView,
|
onPressed: onTogglePlatformView,
|
||||||
child: const Text('TOGGLE PLATFORM VIEW'),
|
child: const Text('TOGGLE PLATFORM VIEW'),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
Row(
|
Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
ElevatedButton(
|
Expanded(
|
||||||
|
child: ElevatedButton(
|
||||||
|
key: const ValueKey<String>('ToggleHybridComposition'),
|
||||||
|
child: const Text('TOGGLE HC'),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
useHybridComposition = !useHybridComposition;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton(
|
||||||
key: const ValueKey<String>('AddChildView'),
|
key: const ValueKey<String>('AddChildView'),
|
||||||
onPressed: onChildViewPressed,
|
onPressed: onChildViewPressed,
|
||||||
child: const Text('ADD CHILD VIEW'),
|
child: const Text('ADD CHILD VIEW'),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton(
|
||||||
key: const ValueKey<String>('TapChildView'),
|
key: const ValueKey<String>('TapChildView'),
|
||||||
onPressed: onTapChildViewPressed,
|
onPressed: onTapChildViewPressed,
|
||||||
child: const Text('TAP CHILD VIEW'),
|
child: const Text('TAP CHILD VIEW'),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
if (nestedViewClickCount > 0)
|
if (nestedViewClickCount > 0)
|
||||||
Text(
|
Text(
|
||||||
'Click count: $nestedViewClickCount',
|
'Click count: $nestedViewClickCount',
|
||||||
key: const ValueKey<String>('NestedViewClickCount'),
|
key: const ValueKey<String>('NestedViewClickCount'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -59,10 +59,58 @@ Future<void> main() async {
|
||||||
}, timeout: Timeout.none);
|
}, timeout: Timeout.none);
|
||||||
});
|
});
|
||||||
|
|
||||||
group('Flutter surface switch', () {
|
group('Flutter surface without hybrid composition', () {
|
||||||
setUpAll(() async {
|
setUpAll(() async {
|
||||||
final SerializableFinder wmListTile = find.byValueKey('NestedViewEventTile');
|
await driver.tap(find.byValueKey('NestedViewEventTile'));
|
||||||
await driver.tap(wmListTile);
|
});
|
||||||
|
|
||||||
|
tearDownAll(() async {
|
||||||
|
await driver.waitFor(find.pageBack());
|
||||||
|
await driver.tap(find.pageBack());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Uses FlutterSurfaceView when Android view is on the screen', () async {
|
||||||
|
await driver.waitFor(find.byValueKey('PlatformView'));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await driver.requestData('hierarchy'),
|
||||||
|
'|-FlutterView\n'
|
||||||
|
' |-FlutterSurfaceView\n' // Flutter UI
|
||||||
|
' |-ViewGroup\n' // Platform View
|
||||||
|
' |-ViewGroup\n'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Hide platform view.
|
||||||
|
final SerializableFinder togglePlatformView = find.byValueKey('TogglePlatformView');
|
||||||
|
await driver.tap(togglePlatformView);
|
||||||
|
await driver.waitForAbsent(find.byValueKey('PlatformView'));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await driver.requestData('hierarchy'),
|
||||||
|
'|-FlutterView\n'
|
||||||
|
' |-FlutterSurfaceView\n' // Just the Flutter UI
|
||||||
|
);
|
||||||
|
|
||||||
|
// Show platform view again.
|
||||||
|
await driver.tap(togglePlatformView);
|
||||||
|
await driver.waitFor(find.byValueKey('PlatformView'));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await driver.requestData('hierarchy'),
|
||||||
|
'|-FlutterView\n'
|
||||||
|
' |-FlutterSurfaceView\n' // Flutter UI
|
||||||
|
' |-ViewGroup\n' // Platform View
|
||||||
|
' |-ViewGroup\n'
|
||||||
|
);
|
||||||
|
}, timeout: Timeout.none);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Flutter surface with hybrid composition', () {
|
||||||
|
setUpAll(() async {
|
||||||
|
await driver.tap(find.byValueKey('NestedViewEventTile'));
|
||||||
|
await driver.tap(find.byValueKey('ToggleHybridComposition'));
|
||||||
|
await driver.tap(find.byValueKey('TogglePlatformView'));
|
||||||
|
await driver.tap(find.byValueKey('TogglePlatformView'));
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDownAll(() async {
|
tearDownAll(() async {
|
||||||
|
|
|
@ -52,7 +52,7 @@ Set<Type> _factoriesTypeSet<T>(Set<Factory<T>> factories) {
|
||||||
|
|
||||||
/// A render object for an Android view.
|
/// A render object for an Android view.
|
||||||
///
|
///
|
||||||
/// Requires Android API level 20 or greater.
|
/// Requires Android API level 23 or greater.
|
||||||
///
|
///
|
||||||
/// [RenderAndroidView] is responsible for sizing, displaying and passing touch events to an
|
/// [RenderAndroidView] is responsible for sizing, displaying and passing touch events to an
|
||||||
/// Android [View](https://developer.android.com/reference/android/view/View).
|
/// Android [View](https://developer.android.com/reference/android/view/View).
|
||||||
|
@ -74,7 +74,7 @@ Set<Type> _factoriesTypeSet<T>(Set<Factory<T>> factories) {
|
||||||
///
|
///
|
||||||
/// * [AndroidView] which is a widget that is used to show an Android view.
|
/// * [AndroidView] which is a widget that is used to show an Android view.
|
||||||
/// * [PlatformViewsService] which is a service for controlling platform views.
|
/// * [PlatformViewsService] which is a service for controlling platform views.
|
||||||
class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
|
class RenderAndroidView extends PlatformViewRenderBox {
|
||||||
/// Creates a render object for an Android view.
|
/// Creates a render object for an Android view.
|
||||||
RenderAndroidView({
|
RenderAndroidView({
|
||||||
required AndroidViewController viewController,
|
required AndroidViewController viewController,
|
||||||
|
@ -86,7 +86,8 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
|
||||||
assert(gestureRecognizers != null),
|
assert(gestureRecognizers != null),
|
||||||
assert(clipBehavior != null),
|
assert(clipBehavior != null),
|
||||||
_viewController = viewController,
|
_viewController = viewController,
|
||||||
_clipBehavior = clipBehavior {
|
_clipBehavior = clipBehavior,
|
||||||
|
super(controller: viewController, hitTestBehavior: hitTestBehavior, gestureRecognizers: gestureRecognizers) {
|
||||||
_viewController.pointTransformer = (Offset offset) => globalToLocal(offset);
|
_viewController.pointTransformer = (Offset offset) => globalToLocal(offset);
|
||||||
updateGestureRecognizers(gestureRecognizers);
|
updateGestureRecognizers(gestureRecognizers);
|
||||||
_viewController.addOnPlatformViewCreatedListener(_onPlatformViewCreated);
|
_viewController.addOnPlatformViewCreatedListener(_onPlatformViewCreated);
|
||||||
|
@ -101,18 +102,22 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
|
||||||
bool _isDisposed = false;
|
bool _isDisposed = false;
|
||||||
|
|
||||||
/// The Android view controller for the Android view associated with this render object.
|
/// The Android view controller for the Android view associated with this render object.
|
||||||
AndroidViewController get viewController => _viewController;
|
@override
|
||||||
|
AndroidViewController get controller => _viewController;
|
||||||
|
|
||||||
AndroidViewController _viewController;
|
AndroidViewController _viewController;
|
||||||
|
|
||||||
/// Sets a new Android view controller.
|
/// Sets a new Android view controller.
|
||||||
///
|
@override
|
||||||
/// `viewController` must not be null.
|
set controller(AndroidViewController controller) {
|
||||||
set viewController(AndroidViewController viewController) {
|
|
||||||
assert(_viewController != null);
|
assert(_viewController != null);
|
||||||
assert(viewController != null);
|
assert(controller != null);
|
||||||
if (_viewController == viewController)
|
if (_viewController == controller)
|
||||||
return;
|
return;
|
||||||
_viewController.removeOnPlatformViewCreatedListener(_onPlatformViewCreated);
|
_viewController.removeOnPlatformViewCreatedListener(_onPlatformViewCreated);
|
||||||
_viewController = viewController;
|
super.controller = controller;
|
||||||
|
_viewController = controller;
|
||||||
|
_viewController.pointTransformer = (Offset offset) => globalToLocal(offset);
|
||||||
_sizePlatformView();
|
_sizePlatformView();
|
||||||
if (_viewController.isCreated) {
|
if (_viewController.isCreated) {
|
||||||
markNeedsSemanticsUpdate();
|
markNeedsSemanticsUpdate();
|
||||||
|
@ -138,26 +143,6 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
|
||||||
markNeedsSemanticsUpdate();
|
markNeedsSemanticsUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// {@template flutter.rendering.RenderAndroidView.updateGestureRecognizers}
|
|
||||||
/// Updates which gestures should be forwarded to the platform view.
|
|
||||||
///
|
|
||||||
/// Gesture recognizers created by factories in this set participate in the gesture arena for each
|
|
||||||
/// pointer that was put down on the render box. If any of the recognizers on this list wins the
|
|
||||||
/// gesture arena, the entire pointer event sequence starting from the pointer down event
|
|
||||||
/// will be dispatched to the Android view.
|
|
||||||
///
|
|
||||||
/// The `gestureRecognizers` property must not contain more than one factory with the same [Factory.type].
|
|
||||||
///
|
|
||||||
/// Setting a new set of gesture recognizer factories with the same [Factory.type]s as the current
|
|
||||||
/// set has no effect, because the factories' constructors would have already been called with the previous set.
|
|
||||||
/// {@endtemplate}
|
|
||||||
///
|
|
||||||
/// Any active gesture arena the Android view participates in is rejected when the
|
|
||||||
/// set of gesture recognizers is changed.
|
|
||||||
void updateGestureRecognizers(Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers) {
|
|
||||||
_updateGestureRecognizersWithCallBack(gestureRecognizers, _viewController.dispatchPointerEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get sizedByParent => true;
|
bool get sizedByParent => true;
|
||||||
|
|
||||||
|
@ -182,9 +167,8 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
|
||||||
// Android virtual displays cannot have a zero size.
|
// Android virtual displays cannot have a zero size.
|
||||||
// Trying to size it to 0 crashes the app, which was happening when starting the app
|
// Trying to size it to 0 crashes the app, which was happening when starting the app
|
||||||
// with a locked screen (see: https://github.com/flutter/flutter/issues/20456).
|
// with a locked screen (see: https://github.com/flutter/flutter/issues/20456).
|
||||||
if (_state == _PlatformViewState.resizing || size.isEmpty) {
|
if (_state == _PlatformViewState.resizing || size.isEmpty)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
_state = _PlatformViewState.resizing;
|
_state = _PlatformViewState.resizing;
|
||||||
markNeedsPaint();
|
markNeedsPaint();
|
||||||
|
@ -212,6 +196,7 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
|
||||||
void _setOffset() {
|
void _setOffset() {
|
||||||
SchedulerBinding.instance.addPostFrameCallback((_) async {
|
SchedulerBinding.instance.addPostFrameCallback((_) async {
|
||||||
if (!_isDisposed) {
|
if (!_isDisposed) {
|
||||||
|
if (attached)
|
||||||
await _viewController.setOffset(localToGlobal(Offset.zero));
|
await _viewController.setOffset(localToGlobal(Offset.zero));
|
||||||
// Schedule a new post frame callback.
|
// Schedule a new post frame callback.
|
||||||
_setOffset();
|
_setOffset();
|
||||||
|
@ -221,7 +206,7 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(PaintingContext context, Offset offset) {
|
void paint(PaintingContext context, Offset offset) {
|
||||||
if (_viewController.textureId == null)
|
if (_viewController.textureId == null || _currentTextureSize == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// As resizing the Android view happens asynchronously we don't know exactly when is a
|
// As resizing the Android view happens asynchronously we don't know exactly when is a
|
||||||
|
@ -264,14 +249,15 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
|
||||||
|
|
||||||
context.addLayer(TextureLayer(
|
context.addLayer(TextureLayer(
|
||||||
rect: offset & _currentTextureSize!,
|
rect: offset & _currentTextureSize!,
|
||||||
textureId: viewController.textureId!,
|
textureId: _viewController.textureId!,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void describeSemanticsConfiguration (SemanticsConfiguration config) {
|
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||||
super.describeSemanticsConfiguration(config);
|
// Don't call the super implementation since `platformViewId` should
|
||||||
|
// be set only when the platform view is created, but the concept of
|
||||||
|
// a "created" platform view belongs to this subclass.
|
||||||
config.isSemanticBoundary = true;
|
config.isSemanticBoundary = true;
|
||||||
|
|
||||||
if (_viewController.isCreated) {
|
if (_viewController.isCreated) {
|
||||||
|
@ -339,7 +325,7 @@ class RenderUiKitView extends RenderBox {
|
||||||
// any newly arriving events there's nothing we need to invalidate.
|
// any newly arriving events there's nothing we need to invalidate.
|
||||||
PlatformViewHitTestBehavior hitTestBehavior;
|
PlatformViewHitTestBehavior hitTestBehavior;
|
||||||
|
|
||||||
/// {@macro flutter.rendering.RenderAndroidView.updateGestureRecognizers}
|
/// {@macro flutter.rendering.PlatformViewRenderBox.updateGestureRecognizers}
|
||||||
void updateGestureRecognizers(Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers) {
|
void updateGestureRecognizers(Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers) {
|
||||||
assert(gestureRecognizers != null);
|
assert(gestureRecognizers != null);
|
||||||
assert(
|
assert(
|
||||||
|
@ -653,11 +639,11 @@ class PlatformViewRenderBox extends RenderBox with _PlatformViewGestureMixin {
|
||||||
PlatformViewController get controller => _controller;
|
PlatformViewController get controller => _controller;
|
||||||
PlatformViewController _controller;
|
PlatformViewController _controller;
|
||||||
/// This value must not be null, and setting it to a new value will result in a repaint.
|
/// This value must not be null, and setting it to a new value will result in a repaint.
|
||||||
set controller(PlatformViewController controller) {
|
set controller(covariant PlatformViewController controller) {
|
||||||
assert(controller != null);
|
assert(controller != null);
|
||||||
assert(controller.viewId != null && controller.viewId > -1);
|
assert(controller.viewId != null && controller.viewId > -1);
|
||||||
|
|
||||||
if ( _controller == controller) {
|
if (_controller == controller) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final bool needsSemanticsUpdate = _controller.viewId != controller.viewId;
|
final bool needsSemanticsUpdate = _controller.viewId != controller.viewId;
|
||||||
|
@ -668,7 +654,19 @@ class PlatformViewRenderBox extends RenderBox with _PlatformViewGestureMixin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// {@macro flutter.rendering.RenderAndroidView.updateGestureRecognizers}
|
/// {@template flutter.rendering.PlatformViewRenderBox.updateGestureRecognizers}
|
||||||
|
/// Updates which gestures should be forwarded to the platform view.
|
||||||
|
///
|
||||||
|
/// Gesture recognizers created by factories in this set participate in the gesture arena for each
|
||||||
|
/// pointer that was put down on the render box. If any of the recognizers on this list wins the
|
||||||
|
/// gesture arena, the entire pointer event sequence starting from the pointer down event
|
||||||
|
/// will be dispatched to the Android view.
|
||||||
|
///
|
||||||
|
/// The `gestureRecognizers` property must not contain more than one factory with the same [Factory.type].
|
||||||
|
///
|
||||||
|
/// Setting a new set of gesture recognizer factories with the same [Factory.type]s as the current
|
||||||
|
/// set has no effect, because the factories' constructors would have already been called with the previous set.
|
||||||
|
/// {@endtemplate}
|
||||||
///
|
///
|
||||||
/// Any active gesture arena the `PlatformView` participates in is rejected when the
|
/// Any active gesture arena the `PlatformView` participates in is rejected when the
|
||||||
/// set of gesture recognizers is changed.
|
/// set of gesture recognizers is changed.
|
||||||
|
@ -700,7 +698,7 @@ class PlatformViewRenderBox extends RenderBox with _PlatformViewGestureMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void describeSemanticsConfiguration (SemanticsConfiguration config) {
|
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||||
super.describeSemanticsConfiguration(config);
|
super.describeSemanticsConfiguration(config);
|
||||||
assert(_controller.viewId != null);
|
assert(_controller.viewId != null);
|
||||||
config.isSemanticBoundary = true;
|
config.isSemanticBoundary = true;
|
||||||
|
|
|
@ -76,10 +76,8 @@ class PlatformViewsService {
|
||||||
/// The callbacks are invoked when the platform view asks to be focused.
|
/// The callbacks are invoked when the platform view asks to be focused.
|
||||||
final Map<int, VoidCallback> _focusCallbacks = <int, VoidCallback>{};
|
final Map<int, VoidCallback> _focusCallbacks = <int, VoidCallback>{};
|
||||||
|
|
||||||
|
/// {@template flutter.services.PlatformViewsService.initAndroidView}
|
||||||
/// Creates a [TextureAndroidViewController] for a new Android view.
|
/// Creates a controller for a new Android view.
|
||||||
///
|
|
||||||
/// The view is created after calling [TextureAndroidViewController.setSize].
|
|
||||||
///
|
///
|
||||||
/// `id` is an unused unique identifier generated with [platformViewsRegistry].
|
/// `id` is an unused unique identifier generated with [platformViewsRegistry].
|
||||||
///
|
///
|
||||||
|
@ -103,7 +101,8 @@ class PlatformViewsService {
|
||||||
///
|
///
|
||||||
/// The `id, `viewType, and `layoutDirection` parameters must not be null.
|
/// The `id, `viewType, and `layoutDirection` parameters must not be null.
|
||||||
/// If `creationParams` is non null then `creationParamsCodec` must not be null.
|
/// If `creationParams` is non null then `creationParamsCodec` must not be null.
|
||||||
static TextureAndroidViewController initAndroidView({
|
/// {@endtemplate}
|
||||||
|
static AndroidViewController initAndroidView({
|
||||||
required int id,
|
required int id,
|
||||||
required String viewType,
|
required String viewType,
|
||||||
required TextDirection layoutDirection,
|
required TextDirection layoutDirection,
|
||||||
|
@ -128,32 +127,11 @@ class PlatformViewsService {
|
||||||
return controller;
|
return controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a [SurfaceAndroidViewController] for a new Android view.
|
/// {@macro flutter.services.PlatformViewsService.initAndroidView}
|
||||||
///
|
///
|
||||||
/// The view is created after calling [AndroidViewController.create].
|
/// Alias for [initAndroidView].
|
||||||
///
|
/// This factory is provided for backward compatibility purposes.
|
||||||
/// `id` is an unused unique identifier generated with [platformViewsRegistry].
|
/// In the future, this method will be deprecated.
|
||||||
///
|
|
||||||
/// `viewType` is the identifier of the Android view type to be created, a
|
|
||||||
/// factory for this view type must have been registered on the platform side.
|
|
||||||
/// Platform view factories are typically registered by plugin code.
|
|
||||||
/// Plugins can register a platform view factory with
|
|
||||||
/// [PlatformViewRegistry#registerViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewRegistry.html#registerViewFactory-java.lang.String-io.flutter.plugin.platform.PlatformViewFactory-).
|
|
||||||
///
|
|
||||||
/// `creationParams` will be passed as the args argument of [PlatformViewFactory#create](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#create-android.content.Context-int-java.lang.Object-)
|
|
||||||
///
|
|
||||||
/// `creationParamsCodec` is the codec used to encode `creationParams` before sending it to the
|
|
||||||
/// platform side. It should match the codec passed to the constructor of [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#PlatformViewFactory-io.flutter.plugin.common.MessageCodec-).
|
|
||||||
/// This is typically one of: [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec].
|
|
||||||
///
|
|
||||||
/// `onFocus` is a callback that will be invoked when the Android View asks to get the
|
|
||||||
/// input focus.
|
|
||||||
///
|
|
||||||
/// The Android view will only be created after [AndroidViewController.setSize] is called for the
|
|
||||||
/// first time.
|
|
||||||
///
|
|
||||||
/// The `id, `viewType, and `layoutDirection` parameters must not be null.
|
|
||||||
/// If `creationParams` is non null then `creationParamsCodec` must not be null.
|
|
||||||
static SurfaceAndroidViewController initSurfaceAndroidView({
|
static SurfaceAndroidViewController initSurfaceAndroidView({
|
||||||
required int id,
|
required int id,
|
||||||
required String viewType,
|
required String viewType,
|
||||||
|
@ -174,28 +152,43 @@ class PlatformViewsService {
|
||||||
creationParams: creationParams,
|
creationParams: creationParams,
|
||||||
creationParamsCodec: creationParamsCodec,
|
creationParamsCodec: creationParamsCodec,
|
||||||
);
|
);
|
||||||
|
_instance._focusCallbacks[id] = onFocus ?? () {};
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@macro flutter.services.PlatformViewsService.initAndroidView}
|
||||||
|
///
|
||||||
|
/// When this factory is used, the Android view and Flutter widgets are composed at the
|
||||||
|
/// Android view hierarchy level.
|
||||||
|
/// This is only useful if the view is a Android SurfaceView. However, using this method
|
||||||
|
/// has a performance cost on devices that run below 10, or underpowered devices.
|
||||||
|
/// In most situations, you should use [initAndroidView].
|
||||||
|
static ExpensiveAndroidViewController initExpensiveAndroidView({
|
||||||
|
required int id,
|
||||||
|
required String viewType,
|
||||||
|
required TextDirection layoutDirection,
|
||||||
|
dynamic creationParams,
|
||||||
|
MessageCodec<dynamic>? creationParamsCodec,
|
||||||
|
VoidCallback? onFocus,
|
||||||
|
}) {
|
||||||
|
final ExpensiveAndroidViewController controller = ExpensiveAndroidViewController._(
|
||||||
|
viewId: id,
|
||||||
|
viewType: viewType,
|
||||||
|
layoutDirection: layoutDirection,
|
||||||
|
creationParams: creationParams,
|
||||||
|
creationParamsCodec: creationParamsCodec,
|
||||||
|
);
|
||||||
|
|
||||||
_instance._focusCallbacks[id] = onFocus ?? () {};
|
_instance._focusCallbacks[id] = onFocus ?? () {};
|
||||||
return controller;
|
return controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the render surface of the Android `FlutterView` should be converted to a `FlutterImageView`.
|
/// Whether the render surface of the Android `FlutterView` should be converted to a `FlutterImageView`.
|
||||||
///
|
@Deprecated(
|
||||||
/// When adding platform views using
|
'No longer necessary to improve performance. '
|
||||||
/// [Hybrid Composition](https://flutter.dev/docs/development/platform-integration/platform-views),
|
'This feature was deprecated after v2.11.0-0.1.pre.',
|
||||||
/// the engine converts the render surface to a `FlutterImageView` to improve
|
)
|
||||||
/// animation synchronization between Flutter widgets and the Android platform
|
static Future<void> synchronizeToNativeViewHierarchy(bool yes) async {}
|
||||||
/// views. On Android versions < 10, this can have some performance issues.
|
|
||||||
/// This flag allows disabling this conversion.
|
|
||||||
///
|
|
||||||
/// Defaults to true.
|
|
||||||
static Future<void> synchronizeToNativeViewHierarchy(bool yes) {
|
|
||||||
assert(defaultTargetPlatform == TargetPlatform.android);
|
|
||||||
return SystemChannels.platform_views.invokeMethod<void>(
|
|
||||||
'synchronizeToNativeViewHierarchy',
|
|
||||||
yes,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(amirh): reference the iOS plugin API for registering a UIView factory once it lands.
|
// TODO(amirh): reference the iOS plugin API for registering a UIView factory once it lands.
|
||||||
/// This is work in progress, not yet ready to be used, and requires a custom engine build. Creates a controller for a new iOS UIView.
|
/// This is work in progress, not yet ready to be used, and requires a custom engine build. Creates a controller for a new iOS UIView.
|
||||||
|
@ -665,7 +658,7 @@ class _AndroidMotionEventConverter {
|
||||||
event is! PointerDownEvent && event is! PointerUpEvent;
|
event is! PointerDownEvent && event is! PointerUpEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Controls an Android view.
|
/// Controls an Android view that is composed using a GL texture.
|
||||||
///
|
///
|
||||||
/// Typically created with [PlatformViewsService.initAndroidView].
|
/// Typically created with [PlatformViewsService.initAndroidView].
|
||||||
// TODO(bparrishMines): Remove abstract methods that are not required by all subclasses.
|
// TODO(bparrishMines): Remove abstract methods that are not required by all subclasses.
|
||||||
|
@ -676,7 +669,6 @@ abstract class AndroidViewController extends PlatformViewController {
|
||||||
required TextDirection layoutDirection,
|
required TextDirection layoutDirection,
|
||||||
dynamic creationParams,
|
dynamic creationParams,
|
||||||
MessageCodec<dynamic>? creationParamsCodec,
|
MessageCodec<dynamic>? creationParamsCodec,
|
||||||
bool waitingForSize = false,
|
|
||||||
}) : assert(viewId != null),
|
}) : assert(viewId != null),
|
||||||
assert(viewType != null),
|
assert(viewType != null),
|
||||||
assert(layoutDirection != null),
|
assert(layoutDirection != null),
|
||||||
|
@ -684,10 +676,7 @@ abstract class AndroidViewController extends PlatformViewController {
|
||||||
_viewType = viewType,
|
_viewType = viewType,
|
||||||
_layoutDirection = layoutDirection,
|
_layoutDirection = layoutDirection,
|
||||||
_creationParams = creationParams,
|
_creationParams = creationParams,
|
||||||
_creationParamsCodec = creationParamsCodec,
|
_creationParamsCodec = creationParamsCodec;
|
||||||
_state = waitingForSize
|
|
||||||
? _AndroidViewState.waitingForSize
|
|
||||||
: _AndroidViewState.creating;
|
|
||||||
|
|
||||||
/// Action code for when a primary pointer touched the screen.
|
/// Action code for when a primary pointer touched the screen.
|
||||||
///
|
///
|
||||||
|
@ -737,7 +726,7 @@ abstract class AndroidViewController extends PlatformViewController {
|
||||||
|
|
||||||
TextDirection _layoutDirection;
|
TextDirection _layoutDirection;
|
||||||
|
|
||||||
_AndroidViewState _state;
|
_AndroidViewState _state = _AndroidViewState.waitingForSize;
|
||||||
|
|
||||||
final dynamic _creationParams;
|
final dynamic _creationParams;
|
||||||
|
|
||||||
|
@ -848,10 +837,16 @@ abstract class AndroidViewController extends PlatformViewController {
|
||||||
|
|
||||||
/// Removes a callback added with [addOnPlatformViewCreatedListener].
|
/// Removes a callback added with [addOnPlatformViewCreatedListener].
|
||||||
void removeOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) {
|
void removeOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) {
|
||||||
|
assert(listener != null);
|
||||||
assert(_state != _AndroidViewState.disposed);
|
assert(_state != _AndroidViewState.disposed);
|
||||||
_platformViewCreatedCallbacks.remove(listener);
|
_platformViewCreatedCallbacks.remove(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The created callbacks that are invoked after the platform view has been
|
||||||
|
/// created.
|
||||||
|
@visibleForTesting
|
||||||
|
List<PlatformViewCreatedCallback> get createdCallbacks => _platformViewCreatedCallbacks;
|
||||||
|
|
||||||
/// Sets the layout direction for the Android view.
|
/// Sets the layout direction for the Android view.
|
||||||
Future<void> setLayoutDirection(TextDirection layoutDirection) async {
|
Future<void> setLayoutDirection(TextDirection layoutDirection) async {
|
||||||
assert(
|
assert(
|
||||||
|
@ -938,10 +933,10 @@ abstract class AndroidViewController extends PlatformViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Controls an Android view by rendering to an [AndroidViewSurface].
|
/// Controls an Android view that is composed using a GL texture.
|
||||||
///
|
/// This controller is created from the [PlatformViewsService.initSurfaceAndroidView] factory,
|
||||||
/// Typically created with [PlatformViewsService.initAndroidView].
|
/// and is defined for backward compatibility.
|
||||||
class SurfaceAndroidViewController extends AndroidViewController {
|
class SurfaceAndroidViewController extends TextureAndroidViewController{
|
||||||
SurfaceAndroidViewController._({
|
SurfaceAndroidViewController._({
|
||||||
required int viewId,
|
required int viewId,
|
||||||
required String viewType,
|
required String viewType,
|
||||||
|
@ -955,6 +950,24 @@ class SurfaceAndroidViewController extends AndroidViewController {
|
||||||
creationParams: creationParams,
|
creationParams: creationParams,
|
||||||
creationParamsCodec: creationParamsCodec,
|
creationParamsCodec: creationParamsCodec,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Controls an Android view that is composed using the Android view hierarchy.
|
||||||
|
/// This controller is created from the [PlatformViewsService.initExpensiveAndroidView] factory.
|
||||||
|
class ExpensiveAndroidViewController extends AndroidViewController {
|
||||||
|
ExpensiveAndroidViewController._({
|
||||||
|
required int viewId,
|
||||||
|
required String viewType,
|
||||||
|
required TextDirection layoutDirection,
|
||||||
|
dynamic creationParams,
|
||||||
|
MessageCodec<dynamic>? creationParamsCodec,
|
||||||
|
}) : super._(
|
||||||
|
viewId: viewId,
|
||||||
|
viewType: viewType,
|
||||||
|
layoutDirection: layoutDirection,
|
||||||
|
creationParams: creationParams,
|
||||||
|
creationParamsCodec: creationParamsCodec,
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> _sendCreateMessage() {
|
Future<void> _sendCreateMessage() {
|
||||||
|
@ -1019,7 +1032,6 @@ class TextureAndroidViewController extends AndroidViewController {
|
||||||
layoutDirection: layoutDirection,
|
layoutDirection: layoutDirection,
|
||||||
creationParams: creationParams,
|
creationParams: creationParams,
|
||||||
creationParamsCodec: creationParamsCodec,
|
creationParamsCodec: creationParamsCodec,
|
||||||
waitingForSize: true,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/// The texture entry id into which the Android view is rendered.
|
/// The texture entry id into which the Android view is rendered.
|
||||||
|
@ -1032,7 +1044,8 @@ class TextureAndroidViewController extends AndroidViewController {
|
||||||
@override
|
@override
|
||||||
int? get textureId => _textureId;
|
int? get textureId => _textureId;
|
||||||
|
|
||||||
late Size _initialSize;
|
/// The size used to create the platform view.
|
||||||
|
Size? _initialSize;
|
||||||
|
|
||||||
/// The current offset of the platform view.
|
/// The current offset of the platform view.
|
||||||
Offset _off = Offset.zero;
|
Offset _off = Offset.zero;
|
||||||
|
@ -1047,7 +1060,7 @@ class TextureAndroidViewController extends AndroidViewController {
|
||||||
if (_state == _AndroidViewState.waitingForSize) {
|
if (_state == _AndroidViewState.waitingForSize) {
|
||||||
_initialSize = size;
|
_initialSize = size;
|
||||||
await create();
|
await create();
|
||||||
return _initialSize;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Map<Object?, Object?>? meta = await SystemChannels.platform_views.invokeMapMethod<Object?, Object?>(
|
final Map<Object?, Object?>? meta = await SystemChannels.platform_views.invokeMapMethod<Object?, Object?>(
|
||||||
|
@ -1093,17 +1106,21 @@ class TextureAndroidViewController extends AndroidViewController {
|
||||||
///
|
///
|
||||||
/// Throws an [AssertionError] if view was already disposed.
|
/// Throws an [AssertionError] if view was already disposed.
|
||||||
@override
|
@override
|
||||||
Future<void> create() => super.create();
|
Future<void> create() async {
|
||||||
|
if (_initialSize != null)
|
||||||
|
return super.create();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> _sendCreateMessage() async {
|
Future<void> _sendCreateMessage() async {
|
||||||
assert(!_initialSize.isEmpty, 'trying to create $TextureAndroidViewController without setting a valid size.');
|
assert(_initialSize != null, 'trying to create $TextureAndroidViewController without setting an initial size.');
|
||||||
|
assert(!_initialSize!.isEmpty, 'trying to create $TextureAndroidViewController without setting a valid size.');
|
||||||
|
|
||||||
final Map<String, dynamic> args = <String, dynamic>{
|
final Map<String, dynamic> args = <String, dynamic>{
|
||||||
'id': viewId,
|
'id': viewId,
|
||||||
'viewType': _viewType,
|
'viewType': _viewType,
|
||||||
'width': _initialSize.width,
|
'width': _initialSize!.width,
|
||||||
'height': _initialSize.height,
|
'height': _initialSize!.height,
|
||||||
'direction': AndroidViewController._getAndroidDirection(_layoutDirection),
|
'direction': AndroidViewController._getAndroidDirection(_layoutDirection),
|
||||||
};
|
};
|
||||||
if (_creationParams != null) {
|
if (_creationParams != null) {
|
||||||
|
|
|
@ -15,7 +15,7 @@ import 'framework.dart';
|
||||||
|
|
||||||
/// Embeds an Android view in the Widget hierarchy.
|
/// Embeds an Android view in the Widget hierarchy.
|
||||||
///
|
///
|
||||||
/// Requires Android API level 20 or greater.
|
/// Requires Android API level 23 or greater.
|
||||||
///
|
///
|
||||||
/// Embedding Android views is an expensive operation and should be avoided when a Flutter
|
/// Embedding Android views is an expensive operation and should be avoided when a Flutter
|
||||||
/// equivalent is possible.
|
/// equivalent is possible.
|
||||||
|
@ -681,7 +681,7 @@ class _AndroidPlatformView extends LeafRenderObjectWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void updateRenderObject(BuildContext context, RenderAndroidView renderObject) {
|
void updateRenderObject(BuildContext context, RenderAndroidView renderObject) {
|
||||||
renderObject.viewController = controller;
|
renderObject.controller = controller;
|
||||||
renderObject.hitTestBehavior = hitTestBehavior;
|
renderObject.hitTestBehavior = hitTestBehavior;
|
||||||
renderObject.updateGestureRecognizers(gestureRecognizers);
|
renderObject.updateGestureRecognizers(gestureRecognizers);
|
||||||
renderObject.clipBehavior = clipBehavior;
|
renderObject.clipBehavior = clipBehavior;
|
||||||
|
@ -842,15 +842,11 @@ class PlatformViewLink extends StatefulWidget {
|
||||||
class _PlatformViewLinkState extends State<PlatformViewLink> {
|
class _PlatformViewLinkState extends State<PlatformViewLink> {
|
||||||
int? _id;
|
int? _id;
|
||||||
PlatformViewController? _controller;
|
PlatformViewController? _controller;
|
||||||
bool _platformViewCreated = false;
|
|
||||||
Widget? _surface;
|
Widget? _surface;
|
||||||
FocusNode? _focusNode;
|
FocusNode? _focusNode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (!_platformViewCreated) {
|
|
||||||
return const SizedBox.expand();
|
|
||||||
}
|
|
||||||
_surface ??= widget._surfaceFactory(context, _controller!);
|
_surface ??= widget._surfaceFactory(context, _controller!);
|
||||||
return Focus(
|
return Focus(
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
|
@ -875,9 +871,6 @@ class _PlatformViewLinkState extends State<PlatformViewLink> {
|
||||||
// The _surface has to be recreated as its controller is disposed.
|
// The _surface has to be recreated as its controller is disposed.
|
||||||
// Setting _surface to null will trigger its creation in build().
|
// Setting _surface to null will trigger its creation in build().
|
||||||
_surface = null;
|
_surface = null;
|
||||||
|
|
||||||
// We are about to create a new platform view.
|
|
||||||
_platformViewCreated = false;
|
|
||||||
_initialize();
|
_initialize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -888,16 +881,12 @@ class _PlatformViewLinkState extends State<PlatformViewLink> {
|
||||||
PlatformViewCreationParams._(
|
PlatformViewCreationParams._(
|
||||||
id: _id!,
|
id: _id!,
|
||||||
viewType: widget.viewType,
|
viewType: widget.viewType,
|
||||||
onPlatformViewCreated: _onPlatformViewCreated,
|
onPlatformViewCreated: (_) {},
|
||||||
onFocusChanged: _handlePlatformFocusChanged,
|
onFocusChanged: _handlePlatformFocusChanged,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onPlatformViewCreated(int id) {
|
|
||||||
setState(() { _platformViewCreated = true; });
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleFrameworkFocusChanged(bool isFocused) {
|
void _handleFrameworkFocusChanged(bool isFocused) {
|
||||||
if (!isFocused) {
|
if (!isFocused) {
|
||||||
_controller?.clearFocus();
|
_controller?.clearFocus();
|
||||||
|
@ -1020,18 +1009,18 @@ class PlatformViewSurface extends LeafRenderObjectWidget {
|
||||||
|
|
||||||
/// Integrates an Android view with Flutter's compositor, touch, and semantics subsystems.
|
/// Integrates an Android view with Flutter's compositor, touch, and semantics subsystems.
|
||||||
///
|
///
|
||||||
/// The compositor integration is done by adding a [PlatformViewLayer] to the layer tree. [PlatformViewLayer]
|
/// The compositor integration is done by adding a [TextureLayer] to the layer tree.
|
||||||
/// isn't supported on all platforms. Custom Flutter embedders can support
|
|
||||||
/// [PlatformViewLayer]s by implementing a SystemCompositor.
|
|
||||||
///
|
///
|
||||||
/// The widget fills all available space, the parent of this object must provide bounded layout
|
/// The parent of this object must provide bounded layout constraints.
|
||||||
/// constraints.
|
|
||||||
///
|
///
|
||||||
/// If the associated platform view is not created, the [AndroidViewSurface] does not paint any contents.
|
/// If the associated platform view is not created, the [AndroidViewSurface] does not paint any contents.
|
||||||
///
|
///
|
||||||
|
/// When possible, you may want to use [AndroidView] directly, since it requires less boilerplate code
|
||||||
|
/// than [AndroidViewSurface], and there's no difference in performance, or other trade-off(s).
|
||||||
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [AndroidView] which embeds an Android platform view in the widget hierarchy using a [TextureLayer].
|
/// * [AndroidView] which embeds an Android platform view in the widget hierarchy.
|
||||||
/// * [UiKitView] which embeds an iOS platform view in the widget hierarchy.
|
/// * [UiKitView] which embeds an iOS platform view in the widget hierarchy.
|
||||||
class AndroidViewSurface extends PlatformViewSurface {
|
class AndroidViewSurface extends PlatformViewSurface {
|
||||||
/// Construct an `AndroidPlatformViewSurface`.
|
/// Construct an `AndroidPlatformViewSurface`.
|
||||||
|
@ -1052,12 +1041,26 @@ class AndroidViewSurface extends PlatformViewSurface {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
RenderObject createRenderObject(BuildContext context) {
|
RenderObject createRenderObject(BuildContext context) {
|
||||||
|
final AndroidViewController viewController = controller as AndroidViewController;
|
||||||
|
// Compose using the Android view hierarchy.
|
||||||
|
// This is useful when embedding a SurfaceView into a Flutter app.
|
||||||
|
// SurfaceViews cannot be composed using GL textures.
|
||||||
|
if (viewController is ExpensiveAndroidViewController) {
|
||||||
final PlatformViewRenderBox renderBox =
|
final PlatformViewRenderBox renderBox =
|
||||||
super.createRenderObject(context) as PlatformViewRenderBox;
|
super.createRenderObject(context) as PlatformViewRenderBox;
|
||||||
|
viewController.pointTransformer =
|
||||||
(controller as AndroidViewController).pointTransformer =
|
(Offset position) => renderBox.globalToLocal(position);
|
||||||
|
return renderBox;
|
||||||
|
}
|
||||||
|
// Use GL texture based composition.
|
||||||
|
// App should use GL texture unless they require to embed a SurfaceView.
|
||||||
|
final RenderAndroidView renderBox = RenderAndroidView(
|
||||||
|
viewController: viewController,
|
||||||
|
gestureRecognizers: gestureRecognizers,
|
||||||
|
hitTestBehavior: hitTestBehavior,
|
||||||
|
);
|
||||||
|
viewController.pointTransformer =
|
||||||
(Offset position) => renderBox.globalToLocal(position);
|
(Offset position) => renderBox.globalToLocal(position);
|
||||||
|
|
||||||
return renderBox;
|
return renderBox;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,6 +151,38 @@ void main() {
|
||||||
// Passes if no crashes.
|
// Passes if no crashes.
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('created callback is reset when controller is changed', () {
|
||||||
|
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||||
|
viewsController.registerViewType('webview');
|
||||||
|
final AndroidViewController firstController = PlatformViewsService.initAndroidView(
|
||||||
|
id: 0,
|
||||||
|
viewType: 'webview',
|
||||||
|
layoutDirection: TextDirection.rtl,
|
||||||
|
);
|
||||||
|
final RenderAndroidView renderBox = RenderAndroidView(
|
||||||
|
viewController: firstController,
|
||||||
|
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
|
||||||
|
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{},
|
||||||
|
);
|
||||||
|
layout(renderBox);
|
||||||
|
pumpFrame(phase: EnginePhase.flushSemantics);
|
||||||
|
|
||||||
|
expect(firstController.createdCallbacks, isNotEmpty);
|
||||||
|
expect(firstController.createdCallbacks.length, 1);
|
||||||
|
|
||||||
|
final AndroidViewController secondController = PlatformViewsService.initAndroidView(
|
||||||
|
id: 0,
|
||||||
|
viewType: 'webview',
|
||||||
|
layoutDirection: TextDirection.rtl,
|
||||||
|
);
|
||||||
|
// Reset controller.
|
||||||
|
renderBox.controller = secondController;
|
||||||
|
|
||||||
|
expect(firstController.createdCallbacks, isEmpty);
|
||||||
|
expect(secondController.createdCallbacks, isNotEmpty);
|
||||||
|
expect(secondController.createdCallbacks.length, 1);
|
||||||
|
});
|
||||||
|
|
||||||
test('render object changed its visual appearance after texture is created', () {
|
test('render object changed its visual appearance after texture is created', () {
|
||||||
FakeAsync().run((FakeAsync async) {
|
FakeAsync().run((FakeAsync async) {
|
||||||
final AndroidViewController viewController =
|
final AndroidViewController viewController =
|
||||||
|
|
|
@ -84,24 +84,23 @@ class FakeAndroidViewController implements AndroidViewController {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Size> setSize(Size size) {
|
Future<Size> setSize(Size size) {
|
||||||
throw UnimplementedError();
|
return Future<Size>.value(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> setOffset(Offset off) {
|
Future<void> setOffset(Offset off) async {}
|
||||||
throw UnimplementedError();
|
|
||||||
|
@override
|
||||||
|
int get textureId => 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isCreated => created;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) {
|
||||||
|
created = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
int get textureId => throw UnimplementedError();
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get isCreated => throw UnimplementedError();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void addOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) =>
|
|
||||||
throw UnimplementedError();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void removeOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) {
|
void removeOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) {
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
|
@ -118,9 +117,10 @@ class FakeAndroidViewController implements AndroidViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> create() async {
|
Future<void> create() async {}
|
||||||
created = true;
|
|
||||||
}
|
@override
|
||||||
|
List<PlatformViewCreatedCallback> get createdCallbacks => <PlatformViewCreatedCallback>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
class FakeAndroidPlatformViewsController {
|
class FakeAndroidPlatformViewsController {
|
||||||
|
@ -143,8 +143,6 @@ class FakeAndroidPlatformViewsController {
|
||||||
|
|
||||||
int? lastClearedFocusViewId;
|
int? lastClearedFocusViewId;
|
||||||
|
|
||||||
bool synchronizeToNativeViewHierarchy = true;
|
|
||||||
|
|
||||||
Map<int, Offset> offsets = <int, Offset>{};
|
Map<int, Offset> offsets = <int, Offset>{};
|
||||||
|
|
||||||
void registerViewType(String viewType) {
|
void registerViewType(String viewType) {
|
||||||
|
@ -174,8 +172,6 @@ class FakeAndroidPlatformViewsController {
|
||||||
return _clearFocus(call);
|
return _clearFocus(call);
|
||||||
case 'offset':
|
case 'offset':
|
||||||
return _offset(call);
|
return _offset(call);
|
||||||
case 'synchronizeToNativeViewHierarchy':
|
|
||||||
return _synchronizeToNativeViewHierarchy(call);
|
|
||||||
}
|
}
|
||||||
return Future<dynamic>.sync(() => null);
|
return Future<dynamic>.sync(() => null);
|
||||||
}
|
}
|
||||||
|
@ -318,11 +314,6 @@ class FakeAndroidPlatformViewsController {
|
||||||
lastClearedFocusViewId = id;
|
lastClearedFocusViewId = id;
|
||||||
return Future<dynamic>.sync(() => null);
|
return Future<dynamic>.sync(() => null);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> _synchronizeToNativeViewHierarchy(MethodCall call) {
|
|
||||||
synchronizeToNativeViewHierarchy = call.arguments as bool;
|
|
||||||
return Future<dynamic>.sync(() => null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class FakeIosPlatformViewsController {
|
class FakeIosPlatformViewsController {
|
||||||
|
|
|
@ -18,7 +18,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('create Android view of unregistered type', () async {
|
test('create Android view of unregistered type', () async {
|
||||||
expect(
|
expectLater(
|
||||||
() {
|
() {
|
||||||
return PlatformViewsService.initAndroidView(
|
return PlatformViewsService.initAndroidView(
|
||||||
id: 0,
|
id: 0,
|
||||||
|
@ -29,16 +29,25 @@ void main() {
|
||||||
throwsA(isA<PlatformException>()),
|
throwsA(isA<PlatformException>()),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
try {
|
||||||
() {
|
await PlatformViewsService.initSurfaceAndroidView(
|
||||||
return PlatformViewsService.initSurfaceAndroidView(
|
|
||||||
id: 0,
|
id: 0,
|
||||||
viewType: 'web',
|
viewType: 'web',
|
||||||
layoutDirection: TextDirection.ltr,
|
layoutDirection: TextDirection.ltr,
|
||||||
).create();
|
).create();
|
||||||
},
|
} catch (e) {
|
||||||
throwsA(isA<PlatformException>()),
|
expect(false, isTrue, reason: 'did not expected any exception, but instead got `$e`');
|
||||||
);
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await PlatformViewsService.initAndroidView(
|
||||||
|
id: 0,
|
||||||
|
viewType: 'web',
|
||||||
|
layoutDirection: TextDirection.ltr,
|
||||||
|
).create();
|
||||||
|
} catch (e) {
|
||||||
|
expect(false, isTrue, reason: 'did not expected any exception, but instead got `$e`');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('create Android views', () async {
|
test('create Android views', () async {
|
||||||
|
@ -47,13 +56,13 @@ void main() {
|
||||||
.setSize(const Size(100.0, 100.0));
|
.setSize(const Size(100.0, 100.0));
|
||||||
await PlatformViewsService.initAndroidView( id: 1, viewType: 'webview', layoutDirection: TextDirection.rtl)
|
await PlatformViewsService.initAndroidView( id: 1, viewType: 'webview', layoutDirection: TextDirection.rtl)
|
||||||
.setSize(const Size(200.0, 300.0));
|
.setSize(const Size(200.0, 300.0));
|
||||||
|
// This platform view isn't created until the size is set.
|
||||||
await PlatformViewsService.initSurfaceAndroidView(id: 2, viewType: 'webview', layoutDirection: TextDirection.rtl).create();
|
await PlatformViewsService.initSurfaceAndroidView(id: 2, viewType: 'webview', layoutDirection: TextDirection.rtl).create();
|
||||||
expect(
|
expect(
|
||||||
viewsController.views,
|
viewsController.views,
|
||||||
unorderedEquals(<FakeAndroidPlatformView>[
|
unorderedEquals(<FakeAndroidPlatformView>[
|
||||||
const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr, null),
|
const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr, null),
|
||||||
const FakeAndroidPlatformView(1, 'webview', Size(200.0, 300.0), AndroidViewController.kAndroidLayoutDirectionRtl, null),
|
const FakeAndroidPlatformView(1, 'webview', Size(200.0, 300.0), AndroidViewController.kAndroidLayoutDirectionRtl, null),
|
||||||
const FakeAndroidPlatformView(2, 'webview', null, AndroidViewController.kAndroidLayoutDirectionRtl, true),
|
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -65,7 +74,7 @@ void main() {
|
||||||
viewType: 'webview',
|
viewType: 'webview',
|
||||||
layoutDirection: TextDirection.ltr,
|
layoutDirection: TextDirection.ltr,
|
||||||
).setSize(const Size(100.0, 100.0));
|
).setSize(const Size(100.0, 100.0));
|
||||||
expect(
|
expectLater(
|
||||||
() => PlatformViewsService.initAndroidView(
|
() => PlatformViewsService.initAndroidView(
|
||||||
id: 0,
|
id: 0,
|
||||||
viewType: 'web',
|
viewType: 'web',
|
||||||
|
@ -73,20 +82,6 @@ void main() {
|
||||||
).setSize(const Size(100.0, 100.0)),
|
).setSize(const Size(100.0, 100.0)),
|
||||||
throwsA(isA<PlatformException>()),
|
throwsA(isA<PlatformException>()),
|
||||||
);
|
);
|
||||||
|
|
||||||
await PlatformViewsService.initSurfaceAndroidView(
|
|
||||||
id: 1,
|
|
||||||
viewType: 'webview',
|
|
||||||
layoutDirection: TextDirection.ltr,
|
|
||||||
).create();
|
|
||||||
expect(
|
|
||||||
() => PlatformViewsService.initSurfaceAndroidView(
|
|
||||||
id: 1,
|
|
||||||
viewType: 'web',
|
|
||||||
layoutDirection: TextDirection.ltr,
|
|
||||||
).create(),
|
|
||||||
throwsA(isA<PlatformException>()),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('dispose Android view', () async {
|
test('dispose Android view', () async {
|
||||||
|
@ -240,11 +235,6 @@ void main() {
|
||||||
await viewController.setOffset(const Offset(10, 20));
|
await viewController.setOffset(const Offset(10, 20));
|
||||||
expect(viewsController.offsets, equals(<int, Offset>{}));
|
expect(viewsController.offsets, equals(<int, Offset>{}));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('synchronizeToNativeViewHierarchy', () async {
|
|
||||||
await PlatformViewsService.synchronizeToNativeViewHierarchy(false);
|
|
||||||
expect(viewsController.synchronizeToNativeViewHierarchy, false);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
group('iOS', () {
|
group('iOS', () {
|
||||||
|
|
|
@ -2312,43 +2312,6 @@ void main() {
|
||||||
expect(factoryInvocationCount, 1);
|
expect(factoryInvocationCount, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets(
|
|
||||||
'PlatformViewLink Widget init, should create a SizedBox widget before onPlatformViewCreated and a PlatformViewSurface after',
|
|
||||||
(WidgetTester tester) async {
|
|
||||||
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
|
||||||
late int createdPlatformViewId;
|
|
||||||
|
|
||||||
late PlatformViewCreatedCallback onPlatformViewCreatedCallBack;
|
|
||||||
|
|
||||||
final PlatformViewLink platformViewLink = PlatformViewLink(
|
|
||||||
viewType: 'webview',
|
|
||||||
onCreatePlatformView: (PlatformViewCreationParams params) {
|
|
||||||
onPlatformViewCreatedCallBack = params.onPlatformViewCreated;
|
|
||||||
createdPlatformViewId = params.id;
|
|
||||||
return FakePlatformViewController(params.id);
|
|
||||||
},
|
|
||||||
surfaceFactory: (BuildContext context, PlatformViewController controller) {
|
|
||||||
return PlatformViewSurface(
|
|
||||||
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
|
|
||||||
controller: controller,
|
|
||||||
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
await tester.pumpWidget(platformViewLink);
|
|
||||||
expect(() => tester.allWidgets.whereType<SizedBox>().first, returnsNormally);
|
|
||||||
|
|
||||||
onPlatformViewCreatedCallBack(createdPlatformViewId);
|
|
||||||
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
expect(() => tester.allWidgets.whereType<PlatformViewSurface>().first, returnsNormally);
|
|
||||||
|
|
||||||
expect(createdPlatformViewId, currentViewId + 1);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
testWidgets('PlatformViewLink Widget dispose', (WidgetTester tester) async {
|
testWidgets('PlatformViewLink Widget dispose', (WidgetTester tester) async {
|
||||||
late FakePlatformViewController disposedController;
|
late FakePlatformViewController disposedController;
|
||||||
final PlatformViewLink platformViewLink = PlatformViewLink(
|
final PlatformViewLink platformViewLink = PlatformViewLink(
|
||||||
|
|
Loading…
Reference in a new issue