Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@

static constexpr int kMicrosecondsPerSecond = 1000 * 1000;
static constexpr CGFloat kScrollViewContentSize = 2.0;
static constexpr fml::TimeDelta kKeyboardAnimationUpdateViewportMetricsDelay =
Copy link
Contributor

Choose a reason for hiding this comment

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

This does make the animation better, but an arbitrary delay could also cause janks.

Maybe instead of relying on a arbitrary delay, we could try a solution where the keyboard animation and rasterizer is synchronized, similar to macOS PlatformView.
One thing to try is to get a callback from the Shell when frame is finished rasterizing and synchronously run the keyboard animation with a mutex.
https://github.com/flutter/engine/blob/main/shell/common/shell.cc#L1447

Copy link
Contributor Author

@luckysmg luckysmg Apr 17, 2023

Choose a reason for hiding this comment

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

Since an arbitrary delay maybe cause some another issue. What I m going to do is to make SetViewportMetrics call after vsync process callback. Maybe using raster callback is a little bit late. I will raise a new PR for changing that and have a try on that. This marks as draft. ^_^

fml::TimeDelta::FromMilliseconds(1);

static NSString* const kFlutterRestorationStateAppData = @"FlutterRestorationStateAppData";

Expand Down Expand Up @@ -1661,15 +1663,19 @@ - (void)setupKeyboardAnimationVsyncClient {
flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom =
flutterViewController.get()
.keyboardAnimationView.layer.presentationLayer.frame.origin.y;
[flutterViewController updateViewportMetricsIfNeeded];
[flutterViewController keyboardAnimationDelayUpdateViewportMetrics];
}
} else {
fml::TimeDelta timeElapsed = recorder.get()->GetVsyncTargetTime() -
// Because updateViewportMetrics will work in next frame and the frame will
// present in next next frame, so here should add a time of one frame interval
// to get correct animation target time.
fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime();
fml::TimeDelta timeElapsed = recorder.get()->GetVsyncTargetTime() + frameInterval -
flutterViewController.get().keyboardAnimationStartTime;

flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom =
[[flutterViewController keyboardSpringAnimation] curveFunction:timeElapsed.ToSecondsF()];
[flutterViewController updateViewportMetricsIfNeeded];
[flutterViewController keyboardAnimationDelayUpdateViewportMetrics];
}
};
flutter::Shell& shell = [_engine.get() shell];
Expand All @@ -1682,6 +1688,29 @@ - (void)setupKeyboardAnimationVsyncClient {
[_keyboardAnimationVSyncClient await];
}

- (void)keyboardAnimationDelayUpdateViewportMetrics {
// Because the keyboard animation is driven by vsync callback in platform
// thread, and it is extremely closed to vsync callback in UI thread, so it maybe
// run before the vsync process callback in UI thread.
// And if this happens, will cause jitter behavior in rendering, so adding a
// tiny delay to keep the updateViewportMetrics signal run after the vsync
// process callback in UI thread to keep things smooth.
flutter::Shell& shell = [_engine.get() shell];
shell.GetTaskRunners().GetPlatformTaskRunner()->PostDelayedTask(
[weakSelf = [self getWeakPtr]] {
if (!weakSelf) {
return;
}
fml::scoped_nsobject<FlutterViewController> flutterViewController(
[(FlutterViewController*)weakSelf.get() retain]);
if (!flutterViewController) {
return;
}
[flutterViewController updateViewportMetricsIfNeeded];
},
kKeyboardAnimationUpdateViewportMetricsDelay);
}

- (void)invalidateKeyboardAnimationVSyncClient {
[_keyboardAnimationVSyncClient invalidate];
[_keyboardAnimationVSyncClient release];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ - (void)setupKeyboardAnimationVsyncClient;
- (UIView*)keyboardAnimationView;
- (SpringAnimation*)keyboardSpringAnimation;
- (void)setupKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation;
- (void)keyboardAnimationDelayUpdateViewportMetrics;
- (void)ensureViewportMetricsIsCorrect;
- (void)invalidateKeyboardAnimationVSyncClient;
- (void)addInternalPlugins;
Expand Down Expand Up @@ -185,6 +186,26 @@ - (id)setupMockMainScreenAndView:(FlutterViewController*)viewControllerMock
return mockView;
}

- (void)testKeyboardAnimationDelayUpdateViewportMetricsWillWorkCorrectly {
FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
[mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
nibName:nil
bundle:nil];
FlutterViewController* viewControllerMock = OCMPartialMock(viewController);
CGRect viewFrame = UIScreen.mainScreen.bounds;
[self setupMockMainScreenAndView:viewControllerMock viewFrame:viewFrame convertedFrame:viewFrame];

[viewControllerMock keyboardAnimationDelayUpdateViewportMetrics];

// Expect the updateViewportMetrics will invoke after some time.
XCTestExpectation* expectation = [self expectationWithDescription:@"delay update viewport"];
OCMStub([viewControllerMock updateViewportMetricsIfNeeded]).andDo(^(NSInvocation* invocation) {
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:5.0 handler:nil];
}

- (void)testViewDidLoadWillInvokeCreateTouchRateCorrectionVSyncClient {
FlutterEngine* engine = [[FlutterEngine alloc] init];
[engine runWithEntrypoint:nil];
Expand Down