Android: hook into pre draw and attach listener to deliver safe area

Instead of relying only on setOnApplyWindowInsetsListener() and
trying to guess when to try and deliver root decor insets in case
the setOnApplyWindowInsetsListener() doesn't deliver the view's
insets early on, a cleaner way is to hook into
addOnAttachStateChangeListener() and OnPreDrawListener() listeners.
With this approach we guarantee that at least in one of those
cases, especially OnPreDrawListener(), we would be guaranteed
to get the insets when the view is attached. With this approach
we only need to get once such event and from there forward
we rely still on setOnApplyWindowInsetsListener().

Fixes: QTBUG-135808
Pick-to: 6.9 6.10
Change-Id: I05bf3009eb9a33f104d01d29e7f02d780900fc66
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
This commit is contained in:
Assam Boudjelthia 2025-08-29 23:30:54 +03:00
parent 8a969b400e
commit 357580c225
2 changed files with 53 additions and 32 deletions

View File

@ -15,8 +15,8 @@ import android.view.MotionEvent;
import android.view.Surface; import android.view.Surface;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowInsets; import android.view.WindowInsets;
import android.os.Build; import android.os.Build;
import java.util.HashMap; import java.util.HashMap;
@ -30,6 +30,7 @@ class QtWindow extends QtLayout implements QtSurfaceInterface {
private GestureDetector m_gestureDetector; private GestureDetector m_gestureDetector;
private final QtEditText m_editText; private final QtEditText m_editText;
private final QtInputConnection.QtInputConnectionListener m_inputConnectionListener; private final QtInputConnection.QtInputConnectionListener m_inputConnectionListener;
private boolean m_firstSafeMarginsDelivered = false;
private static native void setSurface(int windowId, Surface surface); private static native void setSurface(int windowId, Surface surface);
private static native void safeAreaMarginsChanged(Insets insets, int id); private static native void safeAreaMarginsChanged(Insets insets, int id);
@ -75,46 +76,71 @@ class QtWindow extends QtLayout implements QtSurfaceInterface {
}); });
m_gestureDetector.setIsLongpressEnabled(true); m_gestureDetector.setIsLongpressEnabled(true);
}); });
registerSafeAreaMarginsListener();
} }
@UsedFromNativeCode void registerSafeAreaMarginsListener()
void registerSafeAreaMarginsListner(boolean isTopLevel, boolean isSameWindowAndScreenSize)
{ {
if (!(getContext() instanceof QtActivityBase)) if (!(getContext() instanceof QtActivityBase))
return; return;
setOnApplyWindowInsetsListener((view, insets) -> { setOnApplyWindowInsetsListener((view, insets) -> {
Insets safeInsets = getSafeInsets(view, insets); WindowInsets windowInsets = view.onApplyWindowInsets(insets);
Insets safeInsets = getSafeInsets(this, windowInsets);
safeAreaMarginsChanged(safeInsets, getId()); safeAreaMarginsChanged(safeInsets, getId());
return getConsumedInsets(insets); m_firstSafeMarginsDelivered = true;
return windowInsets;
}); });
// NOTE: if the window size fits the screen geometry (i.e. edge-to-edge case), // If the window is attached, try to directly deliver root insets
// assume this window is the main window and initialize its safe margins with if (isAttachedToWindow()) {
// the insets of the decor view. WindowInsets insets = getRootWindowInsets();
if (isTopLevel && isSameWindowAndScreenSize) { if (insets != null) {
QtNative.runAction(() -> { safeAreaMarginsChanged(getSafeInsets(this, insets), getId());
// NOTE: The callback onApplyWindowInsetsListener() is not being triggered during m_firstSafeMarginsDelivered = true;
// startup, so this is a Workaround to get the safe area margins at startup. }
// Initially, set the root view insets to the current window, then if the insets } else { // Otherwise request it upon attachement
// change later, we can rely on setOnApplyWindowInsetsListener() being called. addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
View decorView = ((Activity) getContext()).getWindow().getDecorView(); @Override
WindowInsets rootInsets = decorView.getRootWindowInsets(); public void onViewAttachedToWindow(View view) {
Insets rootSafeInsets = getSafeInsets(decorView, rootInsets); view.removeOnAttachStateChangeListener(this);
safeAreaMarginsChanged(rootSafeInsets, getId()); view.requestApplyInsets();
}
@Override
public void onViewDetachedFromWindow(View view) {}
}); });
} }
QtNative.runAction(() -> requestApplyInsets()); // Further, tag into pre draw to deliver safe area margins early on
} if (!m_firstSafeMarginsDelivered) {
ViewTreeObserver.OnPreDrawListener listener = new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if (m_firstSafeMarginsDelivered) {
getViewTreeObserver().removeOnPreDrawListener(this);
return true;
}
@SuppressWarnings("deprecation") if (isAttachedToWindow()) {
WindowInsets getConsumedInsets(WindowInsets insets) WindowInsets insets = getRootWindowInsets();
{ if (insets != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) getViewTreeObserver().removeOnPreDrawListener(this);
return WindowInsets.CONSUMED; safeAreaMarginsChanged(getSafeInsets(QtWindow.this, insets), getId());
else m_firstSafeMarginsDelivered = true;
return insets.consumeSystemWindowInsets(); return true;
}
}
requestApplyInsets();
return true;
}
};
getViewTreeObserver().addOnPreDrawListener(listener);
}
} }
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")

View File

@ -97,11 +97,6 @@ void QAndroidPlatformWindow::initialize()
} }
qCDebug(lcQpaWindow) << "Window" << m_nativeViewId << "using surface container type" qCDebug(lcQpaWindow) << "Window" << m_nativeViewId << "using surface container type"
<< static_cast<int>(m_surfaceContainerType); << static_cast<int>(m_surfaceContainerType);
const bool isSameWindowAndScreenSize = geometry().size() == screen()->geometry().size();
m_nativeQtWindow.callMethod("registerSafeAreaMarginsListner",
window->isTopLevel(), isSameWindowAndScreenSize);
} }
QAndroidPlatformWindow::~QAndroidPlatformWindow() QAndroidPlatformWindow::~QAndroidPlatformWindow()