ReactNative 水平滚动视图在 material-top-tabs 中的嵌套滚动视图问题

8

我目前正在尝试将一个水平的ScrollView放置在react-navigation material-top-tabs的内容中(它也可以水平滚动)。

预期行为:

当在水平ScrollView内拖动时,仅ScrollView应受影响并滚动

当前行为:

有时当在水平ScrollView内拖动时,整个顶部选项卡都会滚动。(选项卡被切换),这对用户体验来说是一场噩梦。

你知道有没有任何方式可以使其按照预期工作?

App Nested ScrollView

代码片段:

Navigation.js

// Importing Top Tabs creator
import { createMaterialTopTabNavigator } from "@react-navigation/material-top-tabs";

...

// Creating the Tab Navigator
const Tab = createMaterialTopTabNavigator();

...

// Configure Navigator
<Tab.Navigator
  initialRouteName="Tab 1"
  screenOptions={{
    headerShadowVisible: false,
    headerStyle: {
      backgroundColor: colors.background,
    },
  }}
  // Using custom TabBar component
  tabBar={(props) => <TabBar {...props} />}
>
  // The horizontal ScrollView is in "Tab 1"
  <Tab.Screen
    name="Tab 1"
    component={Screen1}
    options={{
      headerShown: false,
      unmountOnBlur: true,
    }}
  />
  ...
  <Tab.Screen
    name="Tab 4"
    component={Screen4}
    options={{
      headerShown: false,
        unmountOnBlur: true,
      }}
    />
</Tab.Navigator>

HorizontalScrollView.js

<ScrollView
  style={{
    display: "flex",
    flexDirection: "row",
    backgroundColor: colors.background,
    paddingHorizontal: 10,
  }}
  horizontal
  showsHorizontalScrollIndicator={false}
  overScrollMode="never"
>
  ...
</ScrollView>

在ScrollView中使用onScroll: 'horizontal'。如果可以,请告诉我它是否有效。 - Jagroop
你是指传递 horizontal={true} 属性吗?还是你真的是指 onScroll 函数? - Christian Aichner
在使用FlatList时遇到了同样的问题,所以将导入方式更改为react-native-gesture-handler。然而,只能在Android上解决。 - Annie Tan
嗨,克里斯蒂安,我已经为同样的问题苦苦挣扎了好几天。你可能找到了解决方案吗? - Can
嗨,Mehmet,很遗憾我已经转换并采取了不同的方法。或许嵌套滚动视图并不是最好的用户体验。 - Christian Aichner
2个回答

3

我也曾经为这个问题苦恼了几天。据我所知,这个问题不仅限于材料顶部选项卡或页面视图。如果您将水平滚动视图或平面列表放入垂直滚动视图中,则父垂直滚动视图有时会窃取水平手势。

我注意到水平滚动视图上调用了onResponderTerminate回调。React Native提供了一种通过传递onResponderTerminationRequest={(event) => false}属性来防止终止的方法,但似乎没有任何作用。终止回调仍然被调用。第一个这方面的错误报告已经有6年之久,我也没有找到任何可行的解决方法。

一个临时的解决方法是使用react-native-gesture-handler和react-native-reanimated构建自己的滚动视图。下面是如何制作一个完整示例。请注意,必须从react-native-reanimated导入Animated。如果需要使用来自react-native的Animated,请使用别名导入 --> import {Animated as Anim} from 'react-native';

import React from 'react';
import { Animated as Anim, ScrollView, Text, View } from 'react-native';
import Animated, {
   useAnimatedStyle,
   useSharedValue,
   withDecay,
   withSpring
} from "react-native-reanimated";
import { Gesture, GestureDetector } from 'react-native-gesture-handler';

export const MyTab = () => {
   // How many points the view is scrollable.
   // In other words, how many points the view overflows.
   // You might need to dynamically update this
   // This can be calculated by subtracting screen width from the view width.
   // If the view doesn't overflow, set this to zero.
   const SCROLLABLE_WIDTH = 200;

   const animatedX = useSharedValue(0);
   const xContext = useSharedValue(0);
   const animStyle = useAnimatedStyle(() => {
      return {
         transform: [{ translateX: animatedX.value }]
      }
   })

   const panGesture = React.useMemo(() => Gesture.Pan()
      .activeOffsetX([-17, 17]) // 17 is the optimal value. Anything higher might cause a tab change
      .activeOffsetY([-22, 22]) // Allows the vertical scrollview to take over when swiping vertically
      .maxPointers(1)
      .onStart((e) => {
         xContext.value = animatedX.value;
      })
      .onUpdate((e) => {
         const target = xContext.value + e.translationX;
         if (target > 0) {
            animatedX.value = target * 0.3;
         }
         else if (target < -SCROLLABLE_WIDTH) {
            animatedX.value = -SCROLLABLE_WIDTH + (SCROLLABLE_WIDTH + target) * 0.3;
         }
         else {
            animatedX.value = target;
         }
      })
      .onEnd((e) => {
         const target = xContext.value + e.translationX;
         if (target > 0) {
            animatedX.value = withSpring(0, { mass: 0.3, stiffness: 110 });
         }
         else if (target < -SCROLLABLE_WIDTH) {
            animatedX.value = withSpring(-SCROLLABLE_WIDTH, { mass: 0.3, stiffness: 110 });
         }
         else {
            animatedX.value = withDecay({
               velocity: e.velocityX,
               clamp: [-SCROLLABLE_WIDTH, 0], // optionally define boundaries for the animation
            });
         }
      }),
      [] // Set here your useStates required in the gesture
   );

   return (
      <ScrollView style={{}}>
         <GestureDetector
            gesture={panGesture}
         >
            {/* If no static container is set, a tab change might initialize
            If you need to hide overflow set overflow: "hidden" to the container style */}
            <Animated.View style={{ marginVertical: 50 }}>
               <Animated.View
                  style={[{
                     flexDirection: "row"
                  },
                     animStyle
                  ]}>
                  <Text style={{ color: "#abcdef", fontSize: 28, marginHorizontal: 20 }}>
                     {"Horizontally\nscrollable\nitem 1"}
                  </Text>
                  <Text style={{ color: "#abcdef", fontSize: 28, marginHorizontal: 20 }}>
                     {"Horizontally\nscrollable\nitem 2"}
                  </Text>
                  <Text style={{ color: "#abcdef", fontSize: 28, marginHorizontal: 20 }}>
                     {"Horizontally\nscrollable\nitem 3"}
                  </Text>
                  <Text style={{ color: "#abcdef", fontSize: 28, marginHorizontal: 20 }}>
                     {"Horizontally\nscrollable\nitem 4"}
                  </Text>
               </Animated.View>
            </Animated.View>
         </GestureDetector>
      </ScrollView>
   )
}

0
我成功的方法是使用从 'react-native-gesture-handler' 导入的 ScrollView 组件,而不是 'react-native'。
我不需要进行任何额外的设置,它运行良好。
import {ScrollView} from 'react-native-gesture-handler';

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接