Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
f58e8f56
Unverified
Commit
f58e8f56
authored
Mar 14, 2022
by
Emmanuel Garcia
Committed by
GitHub
Mar 14, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix AndroidView offset and resize (#99888)
parent
bb4a5fa7
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
172 additions
and
43 deletions
+172
-43
platform_view.dart
packages/flutter/lib/src/rendering/platform_view.dart
+41
-19
platform_views.dart
packages/flutter/lib/src/services/platform_views.dart
+71
-17
platform_view.dart
packages/flutter/lib/src/widgets/platform_view.dart
+1
-1
fake_platform_views.dart
packages/flutter/test/services/fake_platform_views.dart
+19
-1
platform_views_test.dart
packages/flutter/test/services/platform_views_test.dart
+22
-0
platform_view_test.dart
packages/flutter/test/widgets/platform_view_test.dart
+18
-5
No files found.
packages/flutter/lib/src/rendering/platform_view.dart
View file @
f58e8f56
...
...
@@ -6,6 +6,7 @@ import 'dart:ui';
import
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/scheduler.dart'
;
import
'package:flutter/semantics.dart'
;
import
'package:flutter/services.dart'
;
...
...
@@ -90,10 +91,15 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
updateGestureRecognizers
(
gestureRecognizers
);
_viewController
.
addOnPlatformViewCreatedListener
(
_onPlatformViewCreated
);
this
.
hitTestBehavior
=
hitTestBehavior
;
_setOffset
();
}
_PlatformViewState
_state
=
_PlatformViewState
.
uninitialized
;
Size
?
_currentTextureSize
;
bool
_isDisposed
=
false
;
/// The Android view controller for the Android view associated with this render object.
AndroidViewController
get
viewController
=>
_viewController
;
AndroidViewController
_viewController
;
...
...
@@ -172,8 +178,6 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
_sizePlatformView
();
}
late
Size
_currentAndroidViewSize
;
Future
<
void
>
_sizePlatformView
()
async
{
// Android virtual displays cannot have a zero size.
// Trying to size it to 0 crashes the app, which was happening when starting the app
...
...
@@ -188,8 +192,7 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
Size
targetSize
;
do
{
targetSize
=
size
;
await
_viewController
.
setSize
(
targetSize
);
_currentAndroidViewSize
=
targetSize
;
_currentTextureSize
=
await
_viewController
.
setSize
(
targetSize
);
// We've resized the platform view to targetSize, but it is possible that
// while we were resizing the render object's size was changed again.
// In that case we will resize the platform view again.
...
...
@@ -199,14 +202,39 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
markNeedsPaint
();
}
// Sets the offset of the underlaying platform view on the platform side.
//
// This allows the Android native view to draw the a11y highlights in the same
// location on the screen as the platform view widget in the Flutter framework.
//
// It also allows platform code to obtain the correct position of the Android
// native view on the screen.
void
_setOffset
()
{
SchedulerBinding
.
instance
.
addPostFrameCallback
((
_
)
async
{
if
(!
_isDisposed
)
{
await
_viewController
.
setOffset
(
localToGlobal
(
Offset
.
zero
));
// Schedule a new post frame callback.
_setOffset
();
}
});
}
@override
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
if
(
_viewController
.
textureId
==
null
)
return
;
// Clip the texture if it's going to paint out of the bounds of the renter box
// (see comment in _paintTexture for an explanation of when this happens).
if
((
size
.
width
<
_currentAndroidViewSize
.
width
||
size
.
height
<
_currentAndroidViewSize
.
height
)
&&
clipBehavior
!=
Clip
.
none
)
{
// As resizing the Android view happens asynchronously we don't know exactly when is a
// texture frame with the new size is ready for consumption.
// TextureLayer is unaware of the texture frame's size and always maps it to the
// specified rect. If the rect we provide has a different size from the current texture frame's
// size the texture frame will be scaled.
// To prevent unwanted scaling artifacts while resizing, clip the texture.
// This guarantees that the size of the texture frame we're painting is always
// _currentAndroidTextureSize.
final
bool
isTextureLargerThanWidget
=
_currentTextureSize
!.
width
>
size
.
width
||
_currentTextureSize
!.
height
>
size
.
height
;
if
(
isTextureLargerThanWidget
&&
clipBehavior
!=
Clip
.
none
)
{
_clipRectLayer
.
layer
=
context
.
pushClipRect
(
true
,
offset
,
...
...
@@ -225,24 +253,18 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
@override
void
dispose
()
{
_isDisposed
=
true
;
_clipRectLayer
.
layer
=
null
;
super
.
dispose
();
}
void
_paintTexture
(
PaintingContext
context
,
Offset
offset
)
{
// As resizing the Android view happens asynchronously we don't know exactly when is a
// texture frame with the new size is ready for consumption.
// TextureLayer is unaware of the texture frame's size and always maps it to the
// specified rect. If the rect we provide has a different size from the current texture frame's
// size the texture frame will be scaled.
// To prevent unwanted scaling artifacts while resizing we freeze the texture frame, until
// we know that a frame with the new size is in the buffer.
// This guarantees that the size of the texture frame we're painting is always
// _currentAndroidViewSize.
if
(
_currentTextureSize
==
null
)
return
;
context
.
addLayer
(
TextureLayer
(
rect:
offset
&
_currentAndroidViewSize
,
textureId:
_viewController
.
textureId
!,
freeze:
_state
==
_PlatformViewState
.
resizing
,
rect:
offset
&
_currentTextureSize
!,
textureId:
viewController
.
textureId
!,
));
}
...
...
packages/flutter/lib/src/services/platform_views.dart
View file @
f58e8f56
...
...
@@ -780,11 +780,27 @@ abstract class AndroidViewController extends PlatformViewController {
/// Sizes the Android View.
///
///
`size`
is the view's new size in logical pixel, it must not be null and must
///
[size]
is the view's new size in logical pixel, it must not be null and must
/// be bigger than zero.
///
/// The first time a size is set triggers the creation of the Android view.
Future
<
void
>
setSize
(
Size
size
);
///
/// Returns the buffer size in logical pixel that backs the texture where the platform
/// view pixels are written to.
///
/// The buffer size may or may not be the same as [size].
///
/// As a result, consumers are expected to clip the texture using [size], while using
/// the return value to size the texture.
Future
<
Size
>
setSize
(
Size
size
);
/// Sets the offset of the platform view.
///
/// [off] is the view's new offset in logical pixel.
///
/// On Android, this allows the Android native view to draw the a11y highlights in the same
/// location on the screen as the platform view widget in the Flutter framework.
Future
<
void
>
setOffset
(
Offset
off
);
/// Returns the texture entry id that the Android view is rendering into.
///
...
...
@@ -975,15 +991,19 @@ class SurfaceAndroidViewController extends AndroidViewController {
}
@override
Future
<
void
>
setSize
(
Size
size
)
{
Future
<
Size
>
setSize
(
Size
size
)
{
throw
UnimplementedError
(
'Not supported for
$SurfaceAndroidViewController
.'
);
}
@override
Future
<
void
>
setOffset
(
Offset
off
)
{
throw
UnimplementedError
(
'Not supported for
$SurfaceAndroidViewController
.'
);
}
}
/// Controls an Android view that is rendered to a texture.
///
/// This is typically used by [AndroidView] to display an Android View in a
/// [VirtualDisplay](https://developer.android.com/reference/android/hardware/display/VirtualDisplay).
/// This is typically used by [AndroidView] to display an Android View in the Android view hierarchy.
///
/// Typically created with [PlatformViewsService.initAndroidView].
class
TextureAndroidViewController
extends
AndroidViewController
{
...
...
@@ -1012,25 +1032,59 @@ class TextureAndroidViewController extends AndroidViewController {
@override
int
?
get
textureId
=>
_textureId
;
late
Size
_size
;
late
Size
_initialSize
;
/// The current offset of the platform view.
Offset
_off
=
Offset
.
zero
;
@override
Future
<
void
>
setSize
(
Size
size
)
async
{
Future
<
Size
>
setSize
(
Size
size
)
async
{
assert
(
_state
!=
_AndroidViewState
.
disposed
,
'trying to size a disposed Android View. View id:
$viewId
'
);
assert
(
size
!=
null
);
assert
(!
size
.
isEmpty
);
if
(
_state
==
_AndroidViewState
.
waitingForSize
)
{
_size
=
size
;
return
create
();
_initialSize
=
size
;
await
create
();
return
_initialSize
;
}
await
SystemChannels
.
platform_views
.
invokeMethod
<
void
>(
'resize'
,
<
String
,
dynamic
>{
'id'
:
viewId
,
'width'
:
size
.
width
,
'height'
:
size
.
height
,
});
final
Map
<
Object
?,
Object
?>?
meta
=
await
SystemChannels
.
platform_views
.
invokeMapMethod
<
Object
?,
Object
?>(
'resize'
,
<
String
,
dynamic
>{
'id'
:
viewId
,
'width'
:
size
.
width
,
'height'
:
size
.
height
,
},
);
assert
(
meta
!=
null
);
assert
(
meta
!.
containsKey
(
'width'
));
assert
(
meta
!.
containsKey
(
'height'
));
return
Size
(
meta
![
'width'
]!
as
double
,
meta
[
'height'
]!
as
double
);
}
@override
Future
<
void
>
setOffset
(
Offset
off
)
async
{
if
(
off
==
_off
)
return
;
// Don't set the offset unless the Android view has been created.
// The implementation of this method channel throws if the Android view for this viewId
// isn't addressable.
if
(
_state
!=
_AndroidViewState
.
created
)
return
;
_off
=
off
;
await
SystemChannels
.
platform_views
.
invokeMethod
<
void
>(
'offset'
,
<
String
,
dynamic
>{
'id'
:
viewId
,
'top'
:
off
.
dy
,
'left'
:
off
.
dx
,
},
);
}
/// Creates the Android View.
...
...
@@ -1043,13 +1097,13 @@ class TextureAndroidViewController extends AndroidViewController {
@override
Future
<
void
>
_sendCreateMessage
()
async
{
assert
(!
_
s
ize
.
isEmpty
,
'trying to create
$TextureAndroidViewController
without setting a valid size.'
);
assert
(!
_
initialS
ize
.
isEmpty
,
'trying to create
$TextureAndroidViewController
without setting a valid size.'
);
final
Map
<
String
,
dynamic
>
args
=
<
String
,
dynamic
>{
'id'
:
viewId
,
'viewType'
:
_viewType
,
'width'
:
_
s
ize
.
width
,
'height'
:
_
s
ize
.
height
,
'width'
:
_
initialS
ize
.
width
,
'height'
:
_
initialS
ize
.
height
,
'direction'
:
AndroidViewController
.
_getAndroidDirection
(
_layoutDirection
),
};
if
(
_creationParams
!=
null
)
{
...
...
packages/flutter/lib/src/widgets/platform_view.dart
View file @
f58e8f56
...
...
@@ -546,7 +546,7 @@ class _AndroidViewState extends State<AndroidView> {
}
SystemChannels
.
textInput
.
invokeMethod
<
void
>(
'TextInput.setPlatformViewClient'
,
<
String
,
dynamic
>{
'platformViewId'
:
_id
,
'usesVirtualDisplay'
:
true
},
<
String
,
dynamic
>{
'platformViewId'
:
_id
},
).
catchError
((
dynamic
e
)
{
if
(
e
is
MissingPluginException
)
{
// We land the framework part of Android platform views keyboard
...
...
packages/flutter/test/services/fake_platform_views.dart
View file @
f58e8f56
...
...
@@ -83,7 +83,12 @@ class FakeAndroidViewController implements AndroidViewController {
}
@override
Future
<
void
>
setSize
(
Size
size
)
{
Future
<
Size
>
setSize
(
Size
size
)
{
throw
UnimplementedError
();
}
@override
Future
<
void
>
setOffset
(
Offset
off
)
{
throw
UnimplementedError
();
}
...
...
@@ -140,6 +145,8 @@ class FakeAndroidPlatformViewsController {
bool
synchronizeToNativeViewHierarchy
=
true
;
Map
<
int
,
Offset
>
offsets
=
<
int
,
Offset
>{};
void
registerViewType
(
String
viewType
)
{
_registeredViewTypes
.
add
(
viewType
);
}
...
...
@@ -165,6 +172,8 @@ class FakeAndroidPlatformViewsController {
return
_setDirection
(
call
);
case
'clearFocus'
:
return
_clearFocus
(
call
);
case
'offset'
:
return
_offset
(
call
);
case
'synchronizeToNativeViewHierarchy'
:
return
_synchronizeToNativeViewHierarchy
(
call
);
}
...
...
@@ -247,6 +256,15 @@ class FakeAndroidPlatformViewsController {
}
_views
[
id
]
=
_views
[
id
]!.
copyWith
(
size:
Size
(
width
,
height
));
return
Future
<
Map
<
dynamic
,
dynamic
>>.
sync
(()
=>
<
dynamic
,
dynamic
>{
'width'
:
width
,
'height'
:
height
});
}
Future
<
dynamic
>
_offset
(
MethodCall
call
)
async
{
final
Map
<
dynamic
,
dynamic
>
args
=
call
.
arguments
as
Map
<
dynamic
,
dynamic
>;
final
int
id
=
args
[
'id'
]
as
int
;
final
double
top
=
args
[
'top'
]
as
double
;
final
double
left
=
args
[
'left'
]
as
double
;
offsets
[
id
]
=
Offset
(
left
,
top
);
return
Future
<
dynamic
>.
sync
(()
=>
null
);
}
...
...
packages/flutter/test/services/platform_views_test.dart
View file @
f58e8f56
...
...
@@ -219,6 +219,28 @@ void main() {
);
});
test
(
"set Android view's offset if view is created"
,
()
async
{
viewsController
.
registerViewType
(
'webview'
);
final
AndroidViewController
viewController
=
PlatformViewsService
.
initAndroidView
(
id:
7
,
viewType:
'webview'
,
layoutDirection:
TextDirection
.
ltr
);
await
viewController
.
setSize
(
const
Size
(
100.0
,
100.0
));
// Creates view.
await
viewController
.
setOffset
(
const
Offset
(
10
,
20
));
expect
(
viewsController
.
offsets
,
equals
(<
int
,
Offset
>{
7
:
const
Offset
(
10
,
20
),
}),
);
});
test
(
"doesn't set Android view's offset if view isn't created"
,
()
async
{
viewsController
.
registerViewType
(
'webview'
);
final
AndroidViewController
viewController
=
PlatformViewsService
.
initAndroidView
(
id:
7
,
viewType:
'webview'
,
layoutDirection:
TextDirection
.
ltr
);
await
viewController
.
setOffset
(
const
Offset
(
10
,
20
));
expect
(
viewsController
.
offsets
,
equals
(<
int
,
Offset
>{}));
});
test
(
'synchronizeToNativeViewHierarchy'
,
()
async
{
await
PlatformViewsService
.
synchronizeToNativeViewHierarchy
(
false
);
expect
(
viewsController
.
synchronizeToNativeViewHierarchy
,
false
);
...
...
packages/flutter/test/widgets/platform_view_test.dart
View file @
f58e8f56
...
...
@@ -1087,9 +1087,6 @@ void main() {
expect
(
lastPlatformViewTextClient
.
containsKey
(
'platformViewId'
),
true
);
expect
(
lastPlatformViewTextClient
[
'platformViewId'
],
currentViewId
+
1
);
expect
(
lastPlatformViewTextClient
.
containsKey
(
'usesVirtualDisplay'
),
true
);
expect
(
lastPlatformViewTextClient
[
'usesVirtualDisplay'
],
true
);
});
testWidgets
(
'AndroidView clears platform focus when unfocused'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -1224,6 +1221,24 @@ void main() {
clipRectLayer
=
tester
.
layers
.
whereType
<
ClipRectLayer
>().
first
;
expect
(
clipRectLayer
.
clipRect
,
const
Rect
.
fromLTWH
(
0.0
,
0.0
,
50.0
,
50.0
));
});
testWidgets
(
'offset is sent to the platform'
,
(
WidgetTester
tester
)
async
{
final
FakeAndroidPlatformViewsController
viewsController
=
FakeAndroidPlatformViewsController
();
viewsController
.
registerViewType
(
'webview'
);
await
tester
.
pumpWidget
(
const
Padding
(
padding:
EdgeInsets
.
fromLTRB
(
10
,
20
,
0
,
0
),
child:
AndroidView
(
viewType:
'webview'
,
layoutDirection:
TextDirection
.
ltr
,
),
),
);
await
tester
.
pump
();
expect
(
viewsController
.
offsets
.
values
,
equals
(<
Offset
>[
const
Offset
(
10
,
20
)]));
});
});
group
(
'AndroidViewSurface'
,
()
{
...
...
@@ -2615,8 +2630,6 @@ void main() {
expect
(
focusNode
.
hasFocus
,
true
);
expect
(
lastPlatformViewTextClient
.
containsKey
(
'platformViewId'
),
true
);
expect
(
lastPlatformViewTextClient
[
'platformViewId'
],
viewId
);
expect
(
lastPlatformViewTextClient
.
containsKey
(
'usesVirtualDisplay'
),
false
);
});
});
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment