Skip to content

Update TLHC PlatformViewWrapper offset without requesting layout - #194899

Merged
auto-submit[bot] merged 1 commit into
flutter:masterfrom
gmackall:tlhc-offset-no-relayout
Oct 26, 2026
Merged

auto-submit[bot] merged 1 commit into
flutter:masterfrom
gmackall:tlhc-offset-no-relayout

Conversation

@gmackall

@gmackall gmackall commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Currently when a TLHC platform view scrolls, PlatformViewsController.offset updates the FrameLayout.LayoutParams margins and calls viewWrapper.setLayoutParams(layoutParams). Calling setLayoutParams triggers requestLayout(), which forces a full measure/layout pass and causes PlatformViewWrapper to re-record its display list and re-render the embedded view into the render target on every scroll frame, even though only the wrapper's offset changed.

  1. Moves offset handling into PlatformViewWrapper.setOffset(newLeft, newTop).
  2. Keeps FrameLayout.LayoutParams margins updated in place so that any subsequent layout pass preserves the offset, and when already laid out, translates the view directly with offsetLeftAndRight and offsetTopAndBottom instead of calling setLayoutParams.
  3. Calls onScrollChanged after translating so ViewTreeObserver.OnScrollChangedListeners (e.g. PopupWindow and ad viewability trackers) still fire when the view moves without a layout pass.
  4. Simplifies PlatformViewWrapper.onTouchEvent to translate touches using getLeft() and getTop(), matching FlutterMutatorView.

Left is before, right is after. There's still some jitter on the right because the recording process running on the device seems to push us in to missing a couple frames, but it's nothing like the bad feedback loop on the left, and also isn't visible locally when I test.

tlhc_master_vs_pr194899_slowmo_30fps.1.mp4

Fixes #183626

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Instead of mutating FrameLayout.LayoutParams and calling setLayoutParams
(which triggers requestLayout() and forces a full view hierarchy measure,
layout, and PlatformViewWrapper display list re-record on every scroll
frame), move PlatformViewWrapper offset handling into
PlatformViewWrapper.setOffset(left, top):

- Keep FrameLayout.LayoutParams margins updated in place so any subsequent
  layout pass preserves the offset.
- When already laid out, shift the view directly with offsetLeftAndRight
  and offsetTopAndBottom and call onScrollChanged so
  ViewTreeObserver.OnScrollChangedListeners (such as PopupWindow and
  ad viewability trackers) still fire during scroll without triggering
  requestLayout() or invalidating the wrapper/parent display lists.
- Translate touches in PlatformViewWrapper.onTouchEvent using the view's
  actual layout bounds (getLeft()/getTop()), matching FlutterMutatorView.
@gmackall gmackall added the CICD Run CI/CD label Oct 20, 2026
@github-actions github-actions Bot added platform-android Android applications specifically engine flutter/engine related. See also e: labels. team-android Owned by Android platform team labels Oct 20, 2026
@gmackall
gmackall marked this pull request as ready for review October 21, 2026 01:09
@gmackall
gmackall requested a review from a team as a code owner October 21, 2026 01:09
@gmackall
gmackall requested review from jesswrd and mboetger and removed request for a team October 21, 2026 01:09

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request refactors how PlatformViewWrapper is positioned on Android. It introduces a setOffset method that directly translates the view using offsetLeftAndRight and offsetTopAndBottom without triggering a full layout pass when the view is already laid out, keeping the layout parameters in sync for future passes. It also simplifies touch event matrix translation by using the view's current position via getLeft() and getTop(). Corresponding unit tests have been added to verify the offset behavior and touch event translations. There are no review comments, and I have no feedback to provide.

@gmackall gmackall added the autosubmit Merge PR when tree becomes green via auto submit App label Oct 26, 2026
@auto-submit
auto-submit Bot added this pull request to the merge queue Oct 26, 2026
Merged via the queue into flutter:master with commit 323badc Oct 26, 2026
33 of 34 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Oct 26, 2026
suryaKommana2662 pushed a commit to suryaKommana2662/flutter that referenced this pull request Oct 29, 2026
…tter#194899)

Currently when a TLHC platform view scrolls,
`PlatformViewsController.offset` updates the `FrameLayout.LayoutParams`
margins and calls `viewWrapper.setLayoutParams(layoutParams)`. Calling
`setLayoutParams` triggers `requestLayout()`, which forces a full
measure/layout pass and causes `PlatformViewWrapper` to re-record its
display list and re-render the embedded view into the render target on
every scroll frame, even though only the wrapper's offset changed.

1. Moves offset handling into `PlatformViewWrapper.setOffset(newLeft,
newTop)`.
2. Keeps `FrameLayout.LayoutParams` margins updated in place so that any
subsequent layout pass preserves the offset, and when already laid out,
translates the view directly with `offsetLeftAndRight` and
`offsetTopAndBottom` instead of calling `setLayoutParams`.
3. Calls `onScrollChanged` after translating so
`ViewTreeObserver.OnScrollChangedListener`s (e.g. `PopupWindow` and ad
viewability trackers) still fire when the view moves without a layout
pass.
4. Simplifies `PlatformViewWrapper.onTouchEvent` to translate touches
using `getLeft()` and `getTop()`, matching `FlutterMutatorView`.

Left is before, right is after. There's still some jitter on the right
because the recording process running on the device seems to push us in
to missing a couple frames, but it's nothing like the bad feedback loop
on the left, and also isn't visible locally when I test.


https://github.com/user-attachments/assets/8295b58f-4e3b-4399-8535-a6520c533220

Fixes flutter#183626

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

Co-authored-by: Gray Mackall <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD engine flutter/engine related. See also e: labels. platform-android Android applications specifically team-android Owned by Android platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Android] Scroll jank with TLHC PlatformViews under merged platform/UI threads

3 participants