mirror of
https://github.com/alexgo-io/onekey-monorepo.git
synced 2026-04-29 20:25:27 +08:00
feat: Add react-native-screens patch to improve the bottom tab click performance (#3763)
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -35,10 +35,10 @@ const TabMe = () => {
|
||||
<YStack>
|
||||
<Button
|
||||
onPress={() => {
|
||||
navigation.switchTab(TabRoutes.Me);
|
||||
navigation.switchTab(TabRoutes.Home);
|
||||
}}
|
||||
>
|
||||
<Button>切换到首页</Button>
|
||||
切换到首页
|
||||
</Button>
|
||||
<MeJotaiDemo />
|
||||
</YStack>
|
||||
|
||||
@@ -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>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
215
patches/react-native-screens+3.26.0.patch
Normal file
215
patches/react-native-screens+3.26.0.patch
Normal 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];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user