feat: Add react-native-screens patch to improve the bottom tab click performance (#3763)

This commit is contained in:
hublot
2023-11-06 16:47:35 +08:00
committed by GitHub
parent c6771ec949
commit 967a5d1ee0
4 changed files with 277 additions and 42 deletions

View File

@@ -19,7 +19,7 @@ import type { Animated, StyleProp, ViewStyle } from 'react-native';
import type { EdgeInsets } from 'react-native-safe-area-context';
const DEFAULT_TABBAR_HEIGHT = 63;
const COMPACT_TABBAR_HEIGHT = 40;
const COMPACT_TABBAR_HEIGHT = 49;
const COMPACT_PAD_TABBAR_HEIGHT = 54;
type Options = {
@@ -116,55 +116,65 @@ export default function MobileBottomTabBar({
}
};
const renderItemContent = (renderActive: boolean) => (
<Stack
testID="Mobile-AppTabBar-TabItem-Icon"
alignItems="center"
gap="$0.5"
pt="$0.5"
mb={insets.bottom}
borderRadius="$2"
justifyContent="center"
style={[
StyleSheet.absoluteFill,
horizontal
? {
flexDirection: 'row',
}
: {
flexDirection: 'column',
},
{
opacity: isActive === renderActive ? 1 : 0,
},
]}
>
<Icon
// @ts-expect-error
name={options?.tabBarIcon?.(renderActive) as ICON_NAMES}
color={renderActive ? '$icon' : '$iconSubdued'}
size="$7"
/>
{options?.tabBarLabel?.length ? (
<Text
variant="$headingXxs"
color={renderActive ? '$text' : '$textSubdued'}
numberOfLines={1}
>
{options?.tabBarLabel}
</Text>
) : null}
</Stack>
);
return (
<Stack
testID="Mobile-AppTabBar-TabItem"
minWidth="$24"
p="$1"
h="100%"
key={route.name}
backgroundColor={backgroundColor}
bg={backgroundColor}
hoverStyle={{ backgroundColor: '$bgHover' }}
onPress={onPress}
>
<Stack
testID="Mobile-AppTabBar-TabItem-Icon"
alignItems="center"
py="$0.5"
mb="$0"
gap="$0.5"
onPress={onPress}
hoverStyle={{ backgroundColor: '$bgHover' }}
borderRadius="$2"
justifyContent="center"
key={route.name}
style={
horizontal
? {
flexDirection: 'row',
}
: {
flexDirection: 'column',
}
}
>
<Icon
// @ts-expect-error
name={options?.tabBarIcon?.(isActive) as ICON_NAMES}
color={isActive ? '$icon' : '$iconSubdued'}
size="$7"
/>
{options?.tabBarLabel?.length ? (
<Text
variant="$headingXxs"
color={isActive ? '$text' : '$textSubdued'}
numberOfLines={1}
>
{options?.tabBarLabel}
</Text>
) : null}
</Stack>
{renderItemContent(false)}
{renderItemContent(true)}
</Stack>
);
}),
[
insets.bottom,
backgroundColor,
descriptors,
horizontal,

View File

@@ -35,10 +35,10 @@ const TabMe = () => {
<YStack>
<Button
onPress={() => {
navigation.switchTab(TabRoutes.Me);
navigation.switchTab(TabRoutes.Home);
}}
>
<Button></Button>
</Button>
<MeJotaiDemo />
</YStack>

View File

@@ -1,4 +1,4 @@
import { Button, Stack } from '@onekeyhq/components';
import { Button, Stack, Text } from '@onekeyhq/components';
import { Layout } from '../../../utils/Layout';
import { NavigationFocusTools } from '../../../utils/NavigationTools';
@@ -55,6 +55,16 @@ const DemoRootMe = () => {
</Stack>
),
},
{
title: 'BottomTab 渲染卡顿测试',
element: (
<Stack>
{new Array(1000).fill({}).map((_, index) => (
<Text>1000 View BottomTab {index}</Text>
))}
</Stack>
),
},
]}
/>
);

View File

@@ -0,0 +1,215 @@
diff --git a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt
index 401d57e..3b1ca6a 100644
--- a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt
+++ b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt
@@ -14,6 +14,7 @@ import com.facebook.react.ReactRootView
import com.facebook.react.bridge.ReactContext
import com.facebook.react.modules.core.ChoreographerCompat
import com.facebook.react.modules.core.ReactChoreographer
+import com.facebook.react.views.view.ReactViewGroup
import com.swmansion.rnscreens.Screen.ActivityState
open class ScreenContainer(context: Context?) : ViewGroup(context) {
@@ -35,6 +36,7 @@ open class ScreenContainer(context: Context?) : ViewGroup(context) {
}
}
private var mParentScreenFragment: ScreenFragmentWrapper? = null
+ private var shouldBreakJSUpdateCount = 0;
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
var i = 0
@@ -214,6 +216,49 @@ open class ScreenContainer(context: Context?) : ViewGroup(context) {
super.onAttachedToWindow()
mIsAttached = true
setupFragmentManager()
+
+
+ mScreenFragments.find { it.screen.activityState == ActivityState.ON_TOP }
+ ?: return
+
+ val parentView = parent as? ViewGroup
+ if (parentView == null || parentView.childCount != 2) {
+ return
+ }
+
+ val tabBar = (parentView.getChildAt(1) as? ViewGroup)?.getChildAt(0) as? ViewGroup ?: return
+
+ for (index in 0 until tabBar.childCount) {
+ val tabBarItem = tabBar.getChildAt(index) as? ReactViewGroup ?: return
+
+ tabBarItem.setOnInterceptTouchEventListener { viewGroup, motionEvent ->
+ if (mScreenFragments[index].screen.activityState == ActivityState.ON_TOP) {
+ false
+ }
+ mScreenFragments.forEach {
+ it.screen.setActivityState(ActivityState.INACTIVE)
+ }
+ mScreenFragments[index].screen.setActivityState(ActivityState.ON_TOP)
+
+ for (itemIndex in 0 until tabBar.childCount) {
+ var item = tabBar.getChildAt(itemIndex) as? ViewGroup
+ if (item == null || item.childCount != 2) {
+ item = (tabBar.getChildAt(itemIndex) as? ViewGroup)?.getChildAt(0) as? ViewGroup
+ if (item == null || item.childCount != 2) {
+ continue
+ }
+ }
+ (item.getChildAt(0) as? ReactViewGroup)?.setOpacityIfPossible(if (itemIndex == index) 0f else 1f)
+ (item.getChildAt(1) as? ReactViewGroup)?.setOpacityIfPossible(if (itemIndex == index) 1f else 0f)
+ }
+ shouldBreakJSUpdateCount += 1;
+ createTransaction().let {
+ topScreen?.fragment?.let { fragment -> detachScreen(it, fragment) }
+ attachScreen(it, mScreenFragments[index].fragment)
+ }
+ true
+ }
+ }
}
/** Removes fragments from fragment manager that are attached to this container */
@@ -312,6 +357,10 @@ open class ScreenContainer(context: Context?) : ViewGroup(context) {
}
open fun onUpdate() {
+ if (shouldBreakJSUpdateCount > 0) {
+ shouldBreakJSUpdateCount = 0
+ return
+ }
createTransaction().let {
// detach screens that are no longer active
val orphaned: MutableSet<Fragment> = HashSet(
diff --git a/node_modules/react-native-screens/ios/RNSScreen.mm b/node_modules/react-native-screens/ios/RNSScreen.mm
index 1ed4914..50a73b1 100644
--- a/node_modules/react-native-screens/ios/RNSScreen.mm
+++ b/node_modules/react-native-screens/ios/RNSScreen.mm
@@ -2,6 +2,7 @@
#import "RNSScreen.h"
#import "RNSScreenContainer.h"
+#import "RNSScreenNavigationContainer.h"
#import "RNSScreenWindowTraits.h"
#ifdef RCT_NEW_ARCH_ENABLED
@@ -456,8 +457,91 @@ - (BOOL)isMountedUnderScreenOrReactRoot
#undef RNS_EXPECTED_VIEW
}
+- (void)addGestureToTabBarItem {
+ UIView *superview = self.superview.superview.superview.superview.superview;
+ if (superview.subviews.count == 2) {
+ UIView *tabBarView = [[superview.subviews[1] subviews] firstObject];
+ for (UIView *view in tabBarView.subviews) {
+ for (UIGestureRecognizer *gestureRecognizer in view.gestureRecognizers) {
+ [view removeGestureRecognizer:gestureRecognizer];
+ }
+ UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(switchTabFromGesture:)];
+ [view addGestureRecognizer:tapGestureRecognizer];
+ }
+ }
+}
+
+- (void)switchTabFromGesture:(UITapGestureRecognizer *)gesture {
+ NSInteger index = [gesture.view.superview.subviews indexOfObject:gesture.view];
+ if (index == NSNotFound) {
+ return;
+ }
+
+ id _containerView = [gesture.view.superview.superview.superview.subviews firstObject];
+ if (![_containerView isKindOfClass:[RNSScreenNavigationContainerView class]]) {
+ return;
+ }
+ RNSScreenNavigationContainerView *containerView = (RNSScreenNavigationContainerView *)_containerView;
+
+ id _navigationController = containerView.subviews.firstObject.nextResponder;
+ if (![_navigationController isKindOfClass:[UINavigationController class]]) {
+ return;
+ }
+
+ UINavigationController *navigationController = (UINavigationController *)_navigationController;
+ id _subviews = navigationController.view.superview.reactSubviews;
+ if (![[_subviews firstObject] isKindOfClass:[RNSScreenView class]]) {
+ return;
+ }
+ NSArray<RNSScreenView *> *screenViews = (NSArray<RNSScreenView *> *)_subviews;
+
+ if (index < 0 || index >= screenViews.count) {
+ return;
+ }
+
+ if (screenViews[index].activityState == RNSActivityStateOnTop) {
+ return;
+ }
+
+
+ [screenViews enumerateObjectsUsingBlock:^(RNSScreenView *obj, NSUInteger idx, BOOL *stop) {
+ obj.activityState = (idx == index) ? RNSActivityStateOnTop : RNSActivityStateInactive;
+ }];
+ containerView.shouldBreakJSUpdateCount += 1;
+ UIViewController *controller = screenViews[index].controller;
+ [navigationController setViewControllers:@[controller] animated:NO];
+
+ [gesture.view.superview.subviews enumerateObjectsUsingBlock:^(__kindof UIView *obj, NSUInteger idx, BOOL *stop) {
+ NSArray<UIView *> *opacityViewList = obj.subviews;
+ if ([opacityViewList count] != 2) {
+ opacityViewList = [opacityViewList firstObject].subviews;
+ if ([opacityViewList count] != 2) {
+ return;
+ }
+ }
+ if (index == idx) {
+ opacityViewList[0].alpha = 0;
+ opacityViewList[1].alpha = 1;
+ } else {
+ opacityViewList[0].alpha = 1;
+ opacityViewList[1].alpha = 0;
+ }
+ }];
+}
+
+- (RNSScreenContainerView *)findContainerViewForView:(UIView *)view {
+ while (view && ![view isKindOfClass:[RNSScreenContainerView class]]) {
+ view = view.superview;
+ }
+ return (RNSScreenContainerView *)view;
+}
+
+
- (void)didMoveToWindow
{
+ if (self.activityState == 2) {
+ [self addGestureToTabBarItem];
+ }
// For RN touches to work we need to instantiate and connect RCTTouchHandler. This only applies
// for screens that aren't mounted under RCTRootView e.g., modals that are mounted directly to
// root application window.
diff --git a/node_modules/react-native-screens/ios/RNSScreenNavigationContainer.h b/node_modules/react-native-screens/ios/RNSScreenNavigationContainer.h
index defd0d5..f6521f5 100644
--- a/node_modules/react-native-screens/ios/RNSScreenNavigationContainer.h
+++ b/node_modules/react-native-screens/ios/RNSScreenNavigationContainer.h
@@ -9,6 +9,8 @@
@interface RNSScreenNavigationContainerView : RNSScreenContainerView
+@property (nonatomic, assign) NSInteger shouldBreakJSUpdateCount;
+
@end
@interface RNSScreenNavigationContainerManager : RNSScreenContainerManager
diff --git a/node_modules/react-native-screens/ios/RNSScreenNavigationContainer.mm b/node_modules/react-native-screens/ios/RNSScreenNavigationContainer.mm
index f10671e..20e0d45 100644
--- a/node_modules/react-native-screens/ios/RNSScreenNavigationContainer.mm
+++ b/node_modules/react-native-screens/ios/RNSScreenNavigationContainer.mm
@@ -29,7 +29,11 @@ - (void)updateContainer
if (screen.activityState == RNSActivityStateOnTop) {
// there should never be more than one screen with `RNSActivityStateOnTop`
// since this component should be used for `tabs` and `drawer` navigators
- [(RNSContainerNavigationController *)self.controller setViewControllers:@[ screen.controller ] animated:NO];
+ if (self.shouldBreakJSUpdateCount > 0) {
+ self.shouldBreakJSUpdateCount = 0;
+ } else {
+ [(RNSContainerNavigationController *)self.controller setViewControllers:@[ screen.controller ] animated:NO];
+ }
[screen notifyFinishTransitioning];
}
}