mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Use Android R (API 30) getInsets() to compute padding (flutter/engine#18339)
This commit is contained in:
parent
e026de2840
commit
cfe1db1146
@ -37,7 +37,7 @@ configurations {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
compileSdkVersion 30
|
||||
|
||||
dependencies {
|
||||
embedding "androidx.annotation:annotation:1.1.0"
|
||||
|
||||
@ -461,7 +461,6 @@ public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseC
|
||||
return ZeroSides.NONE;
|
||||
}
|
||||
|
||||
// TODO(garyq): Use new Android R getInsets API
|
||||
// TODO(garyq): The keyboard detection may interact strangely with
|
||||
// https://github.com/flutter/flutter/issues/22061
|
||||
|
||||
@ -469,6 +468,9 @@ public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseC
|
||||
// be padded. When the on-screen keyboard is detected, we want to include the full inset
|
||||
// but when the inset is just the hidden nav bar, we want to provide a zero inset so the space
|
||||
// can be used.
|
||||
//
|
||||
// This method is replaced by Android API 30 (R/11) getInsets() method which can take the
|
||||
// android.view.WindowInsets.Type.ime() flag to find the keyboard inset.
|
||||
@TargetApi(20)
|
||||
@RequiresApi(20)
|
||||
private int guessBottomKeyboardInset(WindowInsets insets) {
|
||||
@ -506,37 +508,61 @@ public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseC
|
||||
public final WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) {
|
||||
WindowInsets newInsets = super.onApplyWindowInsets(insets);
|
||||
|
||||
boolean statusBarHidden = (SYSTEM_UI_FLAG_FULLSCREEN & getWindowSystemUiVisibility()) != 0;
|
||||
boolean navigationBarHidden =
|
||||
(SYSTEM_UI_FLAG_HIDE_NAVIGATION & getWindowSystemUiVisibility()) != 0;
|
||||
// We zero the left and/or right sides to prevent the padding the
|
||||
// navigation bar would have caused.
|
||||
ZeroSides zeroSides = ZeroSides.NONE;
|
||||
if (navigationBarHidden) {
|
||||
zeroSides = calculateShouldZeroSides();
|
||||
boolean statusBarVisible = (SYSTEM_UI_FLAG_FULLSCREEN & getWindowSystemUiVisibility()) == 0;
|
||||
boolean navigationBarVisible =
|
||||
(SYSTEM_UI_FLAG_HIDE_NAVIGATION & getWindowSystemUiVisibility()) == 0;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
int mask = 0;
|
||||
if (navigationBarVisible) {
|
||||
mask = mask | android.view.WindowInsets.Type.navigationBars();
|
||||
}
|
||||
if (statusBarVisible) {
|
||||
mask = mask | android.view.WindowInsets.Type.statusBars();
|
||||
}
|
||||
mask = mask | android.view.WindowInsets.Type.ime();
|
||||
|
||||
Insets finalInsets = insets.getInsets(mask);
|
||||
viewportMetrics.paddingTop = finalInsets.top;
|
||||
viewportMetrics.paddingRight = finalInsets.right;
|
||||
viewportMetrics.paddingBottom = 0;
|
||||
viewportMetrics.paddingLeft = finalInsets.left;
|
||||
|
||||
viewportMetrics.viewInsetTop = 0;
|
||||
viewportMetrics.viewInsetRight = 0;
|
||||
viewportMetrics.viewInsetBottom = finalInsets.bottom;
|
||||
viewportMetrics.viewInsetLeft = 0;
|
||||
} else {
|
||||
// We zero the left and/or right sides to prevent the padding the
|
||||
// navigation bar would have caused.
|
||||
ZeroSides zeroSides = ZeroSides.NONE;
|
||||
if (!navigationBarVisible) {
|
||||
zeroSides = calculateShouldZeroSides();
|
||||
}
|
||||
|
||||
// Status bar (top) and left/right system insets should partially obscure the content
|
||||
// (padding).
|
||||
viewportMetrics.paddingTop = statusBarVisible ? insets.getSystemWindowInsetTop() : 0;
|
||||
viewportMetrics.paddingRight =
|
||||
zeroSides == ZeroSides.RIGHT || zeroSides == ZeroSides.BOTH
|
||||
? 0
|
||||
: insets.getSystemWindowInsetRight();
|
||||
viewportMetrics.paddingBottom = 0;
|
||||
viewportMetrics.paddingLeft =
|
||||
zeroSides == ZeroSides.LEFT || zeroSides == ZeroSides.BOTH
|
||||
? 0
|
||||
: insets.getSystemWindowInsetLeft();
|
||||
|
||||
// Bottom system inset (keyboard) should adjust scrollable bottom edge (inset).
|
||||
viewportMetrics.viewInsetTop = 0;
|
||||
viewportMetrics.viewInsetRight = 0;
|
||||
viewportMetrics.viewInsetBottom =
|
||||
navigationBarVisible
|
||||
? insets.getSystemWindowInsetBottom()
|
||||
: guessBottomKeyboardInset(insets);
|
||||
viewportMetrics.viewInsetLeft = 0;
|
||||
}
|
||||
|
||||
// Status bar (top) and left/right system insets should partially obscure the content (padding).
|
||||
viewportMetrics.paddingTop = statusBarHidden ? 0 : insets.getSystemWindowInsetTop();
|
||||
viewportMetrics.paddingRight =
|
||||
zeroSides == ZeroSides.RIGHT || zeroSides == ZeroSides.BOTH
|
||||
? 0
|
||||
: insets.getSystemWindowInsetRight();
|
||||
viewportMetrics.paddingBottom = 0;
|
||||
viewportMetrics.paddingLeft =
|
||||
zeroSides == ZeroSides.LEFT || zeroSides == ZeroSides.BOTH
|
||||
? 0
|
||||
: insets.getSystemWindowInsetLeft();
|
||||
|
||||
// Bottom system inset (keyboard) should adjust scrollable bottom edge (inset).
|
||||
viewportMetrics.viewInsetTop = 0;
|
||||
viewportMetrics.viewInsetRight = 0;
|
||||
viewportMetrics.viewInsetBottom =
|
||||
navigationBarHidden
|
||||
? guessBottomKeyboardInset(insets)
|
||||
: insets.getSystemWindowInsetBottom();
|
||||
viewportMetrics.viewInsetLeft = 0;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
Insets systemGestureInsets = insets.getSystemGestureInsets();
|
||||
viewportMetrics.systemGestureInsetTop = systemGestureInsets.top;
|
||||
|
||||
@ -4,6 +4,7 @@ import static junit.framework.TestCase.assertEquals;
|
||||
import static junit.framework.TestCase.assertFalse;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
@ -15,6 +16,7 @@ import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Insets;
|
||||
import android.media.Image;
|
||||
import android.media.ImageReader;
|
||||
import android.view.View;
|
||||
@ -47,7 +49,7 @@ import org.robolectric.shadows.ShadowDisplay;
|
||||
|
||||
@Config(manifest = Config.NONE)
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@TargetApi(28)
|
||||
@TargetApi(30)
|
||||
public class FlutterViewTest {
|
||||
@Mock FlutterJNI mockFlutterJni;
|
||||
@Mock FlutterLoader mockFlutterLoader;
|
||||
@ -375,6 +377,109 @@ public class FlutterViewTest {
|
||||
assertEquals(100, viewportMetricsCaptor.getValue().paddingRight);
|
||||
}
|
||||
|
||||
// This test uses the API 30+ Algorithm for window insets. The legacy algorithm is
|
||||
// set to -1 values, so it is clear if the wrong algorithm is used.
|
||||
@Test
|
||||
@TargetApi(30)
|
||||
@Config(sdk = 30)
|
||||
public void systemInsetGetInsetsFullscreen() {
|
||||
RuntimeEnvironment.setQualifiers("+land");
|
||||
FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext));
|
||||
ShadowDisplay display =
|
||||
Shadows.shadowOf(
|
||||
((WindowManager)
|
||||
RuntimeEnvironment.systemContext.getSystemService(Context.WINDOW_SERVICE))
|
||||
.getDefaultDisplay());
|
||||
display.setRotation(3);
|
||||
assertEquals(0, flutterView.getSystemUiVisibility());
|
||||
when(flutterView.getWindowSystemUiVisibility())
|
||||
.thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
|
||||
when(flutterView.getContext()).thenReturn(RuntimeEnvironment.systemContext);
|
||||
|
||||
FlutterEngine flutterEngine =
|
||||
spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni));
|
||||
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
|
||||
when(flutterEngine.getRenderer()).thenReturn(flutterRenderer);
|
||||
|
||||
// When we attach a new FlutterView to the engine without any system insets,
|
||||
// the viewport metrics default to 0.
|
||||
flutterView.attachToFlutterEngine(flutterEngine);
|
||||
ArgumentCaptor<FlutterRenderer.ViewportMetrics> viewportMetricsCaptor =
|
||||
ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class);
|
||||
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
|
||||
assertEquals(0, viewportMetricsCaptor.getValue().paddingTop);
|
||||
|
||||
Insets insets = Insets.of(100, 100, 100, 100);
|
||||
// Then we simulate the system applying a window inset.
|
||||
WindowInsets windowInsets = mock(WindowInsets.class);
|
||||
when(windowInsets.getSystemWindowInsetTop()).thenReturn(-1);
|
||||
when(windowInsets.getSystemWindowInsetBottom()).thenReturn(-1);
|
||||
when(windowInsets.getSystemWindowInsetLeft()).thenReturn(-1);
|
||||
when(windowInsets.getSystemWindowInsetRight()).thenReturn(-1);
|
||||
when(windowInsets.getInsets(anyInt())).thenReturn(insets);
|
||||
|
||||
flutterView.onApplyWindowInsets(windowInsets);
|
||||
|
||||
verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture());
|
||||
// Top padding is removed due to full screen.
|
||||
assertEquals(0, viewportMetricsCaptor.getValue().paddingTop);
|
||||
// Padding bottom is always 0.
|
||||
assertEquals(0, viewportMetricsCaptor.getValue().paddingBottom);
|
||||
// Left padding is zero because the rotation is 270deg
|
||||
assertEquals(0, viewportMetricsCaptor.getValue().paddingLeft);
|
||||
assertEquals(100, viewportMetricsCaptor.getValue().paddingRight);
|
||||
}
|
||||
|
||||
// This test uses the pre-API 30 Algorithm for window insets.
|
||||
@Test
|
||||
@TargetApi(29)
|
||||
@Config(sdk = 29)
|
||||
public void systemInsetGetInsetsFullscreenLegacy() {
|
||||
RuntimeEnvironment.setQualifiers("+land");
|
||||
FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext));
|
||||
ShadowDisplay display =
|
||||
Shadows.shadowOf(
|
||||
((WindowManager)
|
||||
RuntimeEnvironment.systemContext.getSystemService(Context.WINDOW_SERVICE))
|
||||
.getDefaultDisplay());
|
||||
display.setRotation(3);
|
||||
assertEquals(0, flutterView.getSystemUiVisibility());
|
||||
when(flutterView.getWindowSystemUiVisibility())
|
||||
.thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
|
||||
when(flutterView.getContext()).thenReturn(RuntimeEnvironment.systemContext);
|
||||
|
||||
FlutterEngine flutterEngine =
|
||||
spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni));
|
||||
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
|
||||
when(flutterEngine.getRenderer()).thenReturn(flutterRenderer);
|
||||
|
||||
// When we attach a new FlutterView to the engine without any system insets,
|
||||
// the viewport metrics default to 0.
|
||||
flutterView.attachToFlutterEngine(flutterEngine);
|
||||
ArgumentCaptor<FlutterRenderer.ViewportMetrics> viewportMetricsCaptor =
|
||||
ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class);
|
||||
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
|
||||
assertEquals(0, viewportMetricsCaptor.getValue().paddingTop);
|
||||
|
||||
// Then we simulate the system applying a window inset.
|
||||
WindowInsets windowInsets = mock(WindowInsets.class);
|
||||
when(windowInsets.getSystemWindowInsetTop()).thenReturn(100);
|
||||
when(windowInsets.getSystemWindowInsetBottom()).thenReturn(101);
|
||||
when(windowInsets.getSystemWindowInsetLeft()).thenReturn(102);
|
||||
when(windowInsets.getSystemWindowInsetRight()).thenReturn(103);
|
||||
|
||||
flutterView.onApplyWindowInsets(windowInsets);
|
||||
|
||||
verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture());
|
||||
// Top padding is removed due to full screen.
|
||||
assertEquals(0, viewportMetricsCaptor.getValue().paddingTop);
|
||||
// Padding bottom is always 0.
|
||||
assertEquals(0, viewportMetricsCaptor.getValue().paddingBottom);
|
||||
// Left padding is zero because the rotation is 270deg
|
||||
assertEquals(0, viewportMetricsCaptor.getValue().paddingLeft);
|
||||
assertEquals(103, viewportMetricsCaptor.getValue().paddingRight);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flutterImageView_acquiresImageAndInvalidates() {
|
||||
final ImageReader mockReader = mock(ImageReader.class);
|
||||
|
||||
@ -15,7 +15,7 @@ android {
|
||||
defaultConfig {
|
||||
applicationId "dev.flutter.scenarios"
|
||||
minSdkVersion 18
|
||||
targetSdkVersion 28
|
||||
targetSdkVersion 30
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner "dev.flutter.TestRunner"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user