From f371fe5b293af72f61e2302de76a152efa909c0b Mon Sep 17 00:00:00 2001 From: tboba Date: Tue, 3 Sep 2024 17:27:30 +0200 Subject: [PATCH 1/2] Request layout to properly calculate position of toolbar subviews below Android API 29 Co-authored-by: Kacper Kafara --- .../com/swmansion/rnscreens/CustomToolbar.kt | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt b/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt index 7168a4c6fd..0682ebc063 100644 --- a/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt +++ b/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt @@ -2,11 +2,50 @@ package com.swmansion.rnscreens import android.annotation.SuppressLint import android.content.Context +import android.os.Build import androidx.appcompat.widget.Toolbar +import com.facebook.react.modules.core.ChoreographerCompat +import com.facebook.react.modules.core.ReactChoreographer // This class is used to store config closer to search bar @SuppressLint("ViewConstructor") // Only we construct this view, it is never inflated. open class CustomToolbar( context: Context, val config: ScreenStackHeaderConfig, -) : Toolbar(context) +) : Toolbar(context) { + private var isLayoutEnqueued = false + private val layoutCallback: ChoreographerCompat.FrameCallback = + object : ChoreographerCompat.FrameCallback() { + override fun doFrame(frameTimeNanos: Long) { + isLayoutEnqueued = false + measure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY), + ) + layout(left, top, right, bottom) + } + } + + override fun requestLayout() { + super.requestLayout() + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { + // Below Android API 29, layout is not being requested when subviews are being added to the layout, + // leading to having their subviews in position 0,0 of the toolbar (as Android don't calculate + // the position of each subview, even if Yoga has correctly set their width and height). + // This is mostly the issue, when windowSoftInputMode is set to adjustPan in AndroidManifest. + // Thus, we're manually calling the layout **after** the current layout. + @Suppress("SENSELESS_COMPARISON") // mLayoutCallback can be null here since this method can be called in init + if (!isLayoutEnqueued && layoutCallback != null) { + isLayoutEnqueued = true + // we use NATIVE_ANIMATED_MODULE choreographer queue because it allows us to catch the current + // looper loop instead of enqueueing the update in the next loop causing a one frame delay. + ReactChoreographer + .getInstance() + .postFrameCallback( + ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE, + layoutCallback, + ) + } + } + } +} From e3972d789da0e52f8df29aeacbafaeaae5adaa98 Mon Sep 17 00:00:00 2001 From: tboba Date: Tue, 3 Sep 2024 17:44:24 +0200 Subject: [PATCH 2/2] Add test case --- apps/src/tests/Test2332.tsx | 86 +++++++++++++++++++++++++++++++++++++ apps/src/tests/index.ts | 2 +- 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 apps/src/tests/Test2332.tsx diff --git a/apps/src/tests/Test2332.tsx b/apps/src/tests/Test2332.tsx new file mode 100644 index 0000000000..9728439194 --- /dev/null +++ b/apps/src/tests/Test2332.tsx @@ -0,0 +1,86 @@ +/** + * + * IMPORTANT! READ BEFORE TESTING! + * Remember to switch windowSoftInputMode to `adjustPan` in AndroidManifest.xml file. + * + */ + +import React, { useLayoutEffect } from 'react'; +import { NavigationContainer } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { useNavigation } from '@react-navigation/native'; +import { Button, Text, TextInput, View } from 'react-native'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { HeaderButton } from '@react-navigation/elements'; +type RootStackNavigatorParamsList = { + Home: undefined; + Details: undefined; +}; +const Stack = createNativeStackNavigator(); +const HomeScreen = () => { + const navigation = + useNavigation>(); + const onHandlePress = () => { + navigation.navigate('Details'); + }; + return ( + + HomeScreen + +