Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Conversation

@GaryQian
Copy link
Contributor

@GaryQian GaryQian commented Aug 28, 2020

Adds a WindowInsetsAnimation.Callback and OnApplyWindowInsetsListener class that handles sending view inset changes from the keyboard animating to the framework.

When keyboard is shown or hidden, a call to onApplyWindowInsets() is made immediately with the keyboard's final state. This causes a flicker as the animation kicks in a frame later. To solve this, we check for this initial call and capture the state, deferring it being called until the animation is completed.

During the animation, we pass the window insets to the framework on each frame.

There is an issue where the onProgress WindowInsets received has a bottom ime inset that is higher than expected. It is actually the sum of the navigation bars on top of the keyboard height. There is a workaround implemented to merge the navbar height with the keyboard height to resolve this.

Partially resolves flutter/flutter#62876

@GaryQian GaryQian self-assigned this Aug 28, 2020
@GaryQian GaryQian force-pushed the windowinsets branch 2 times, most recently from 155c5d5 to a6bef30 Compare September 3, 2020 12:51
@GaryQian GaryQian changed the title [WIP] setKeyboardInset Text Channel to manually set keyboard inset position [WIP] Sync keyboard animation with view insets vs Android 11/R/API 30 WindowInsetsAnimation Sep 3, 2020
@GaryQian GaryQian marked this pull request as ready for review September 3, 2020 21:13
@GaryQian GaryQian changed the title [WIP] Sync keyboard animation with view insets vs Android 11/R/API 30 WindowInsetsAnimation [Android R] Sync keyboard animation with view insets vs Android 11/R/API 30 WindowInsetsAnimation Sep 3, 2020
@GaryQian GaryQian removed the Work in progress (WIP) Not ready (yet) for review! label Sep 3, 2020
if ((View.SYSTEM_UI_FLAG_HIDE_NAVIGATION & mView.getWindowSystemUiVisibility()) == 0) {
mask = mask | WindowInsets.Type.navigationBars();
}
if ((View.SYSTEM_UI_FLAG_FULLSCREEN & mView.getWindowSystemUiVisibility()) == 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does full screen also independently affect navigation bar above too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the two values we use to compute viewPadding, so for consistency, I include it together to keep in sync with FlutterView.onApplyWindowInsets. Any padding we consume will also be accounted for here this way.

mask, // Overlay
WindowInsets.Type.ime() // Deferred
);
view.setWindowInsetsAnimationCallback(imeSyncCallback);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add comment on why you need 2

WindowInsets.Type.ime() // Deferred
);
view.setWindowInsetsAnimationCallback(imeSyncCallback);
view.setOnApplyWindowInsetsListener(imeSyncCallback);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you remove this in destroy?

// Loosely based off of
// https://github.com/android/user-interface-samples/blob/master/WindowInsetsAnimation/app/src/main/java/com/google/android/samples/insetsanimation/RootViewDeferringInsetsCallback.kt
// When the IME is shown or hidden, it sends an onApplyWindowInsets call with the
// final state of the IME. This defers the final call to allow the animation to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sentence not clear to me. What's 'this'? 'final call' to what?

super(WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE);
this.overlayInsetTypes = overlayInsetTypes;
this.deferredInsetTypes = deferredInsetTypes;
this.view = view;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

preconditions checknotnull

@Override
public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
this.view = view;
if (!started) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ya, these apis are pretty confusing. I think this needs a lot more comments. Comments at the class level for what the flow should be for the persistent vs delayed stuff are. When this !started should happen, when should deferredInsets happen.

i.e. I don't think the next maintainer would know what to do here if there was a bug and you're out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll make a pass and document the whole thing

@Override
public WindowInsetsAnimation.Bounds onStart(
WindowInsetsAnimation animation, WindowInsetsAnimation.Bounds bounds) {
if (deferredInsets && (animation.getTypeMask() & deferredInsetTypes) != 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comments here too. What's the difference between onprepare and onstart? i.e. why not just set both deferredInsets and started to true here? Can you start and not prepare?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I initially thought that both were necessary, turns out only start is required.

- insets.getInsets(overlayInsetTypes).bottom,
0));
builder.setInsets(deferredInsetTypes, newImeInsets);
// Directly call onApplyWindowInsets as we want to skip this class' version of this call.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't super clear. Maybe elaborate a bit more.

@Config(sdk = 30)
public void ime_windowInsetsSync() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you say this needs sdk 30 and it's not sdk 30, we should just crash :D

@GaryQian GaryQian force-pushed the windowinsets branch 2 times, most recently from 4613d49 to 25f9d52 Compare September 4, 2020 01:18
@GaryQian GaryQian requested a review from xster September 4, 2020 01:55
Copy link
Member

@xster xster left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

// Loosely based off of
// https://github.com/android/user-interface-samples/blob/master/WindowInsetsAnimation/app/src/main/java/com/google/android/samples/insetsanimation/RootViewDeferringInsetsCallback.kt
//
// When the IME is shown or hidden, it immediately sends an onApplyWindowInsets call
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, this is much better, thanks! Does this class also handle all the non-animating inset changes? Like the nav bar, status bar going in and out of fullscreen etc? How do all the phases like onStart, onApplyWindowInsets etc affect those insets?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-animating changes are just passed right on through to FlutterView.onApplyWindowInsets without impact. The ime-type filtering also prevents this from doing anything on any non-ime animations. During animations, changes to the window are handled by the animation controller, which will end the current animation, and restart a new one with the modified settings to continue. For example, rotating the device mid-keyboard animation just causes the animation to continue where it left off but scaled to the rotated window.

ArgumentCaptor<FlutterRenderer.ViewportMetrics> viewportMetricsCaptor =
ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class);

imeSyncCallback.onApplyWindowInsets(testView, deferredInsets);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was gonna say I'm surprised you have to do this on your callback directly but then I actually can't find this on any of the robolectric shadows https://github.com/robolectric/robolectric/search?q=onApplyWindowInsets 🤷‍♂️

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤷‍♂️

@GaryQian GaryQian merged commit 242d522 into flutter:master Sep 4, 2020
@xster
Copy link
Member

xster commented Sep 4, 2020

🙌

engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Sep 5, 2020
flar pushed a commit to flutter/flutter that referenced this pull request Sep 8, 2020
* 30b829e Populates fuchsia node actions in semantics updates. (flutter/engine#20451)

* c2e7010 Roll Skia from 1ee21cdfb6fe to 6763a713f957 (1 revision) (flutter/engine#20982)

* 841401d restore FML_DCHECK removed due to a code reviewing error (flutter/engine#20953)

* 367c6db Don't use GetTaskQueueId() in rasterizer as it breaks Fuchsia (flutter/engine#20983)

* b22a8c6 Let FlutterActivity/Fragment/FragmentActivity have an app bundle path override instead of eager resolving during construction (flutter/engine#20769)

* 0f0ae68 Update test Dart code to pass the latest Dart analyzer rules (flutter/engine#20986)

* d77dd31 Manual roll of Dart b29f228f62...016e8880f0 (flutter/engine#20967)

* c7b3d53 Roll Fuchsia Mac SDK from gOI3W1UNU... to EN2ycWLxi... (flutter/engine#20985)

* 6a6986d improve sensitivity of BackdropFilter web tests (flutter/engine#20915)

* 9fc9cb2 Roll Dart SDK from 016e8880f0ab to 0f0cff3922ad (7 revisions) (flutter/engine#20990)

* 242d522 [Android R] Sync keyboard animation with view insets vs Android 11/R/API 30 WindowInsetsAnimation (flutter/engine#20843)

* b4e0896 Roll Fuchsia Linux SDK from 81O8Kg_Rw... to A0PKwETay... (flutter/engine#20998)

* d77c4e5 adjust blur radius for HTML to match CanvasKit (flutter/engine#20840)

* 0628492 Roll Dart SDK from 0f0cff3922ad to f3a9ca88b664 (1 revision) (flutter/engine#21000)

* d1d848e Roll Fuchsia Mac SDK from EN2ycWLxi... to sih5f60Gt... (flutter/engine#20999)
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support synchronized IME on Android

3 participants