material-top-tabs 中的 ReactNative Horizontal ScrollView(嵌套 ScrollView)

问题描述 投票:0回答:2

我目前正在尝试在 react-navigaion Material-top-tabs 的内容中定位水平 ScrollView (也水平滚动)。

预期行为:

在水平ScrollView内拖动时,只有ScrollView会受到影响并滚动

当前行为:

有时在水平 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>
android reactjs react-native react-navigation react-native-tabnavigator
2个回答
3
投票

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

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

一个临时的解决方法是使用react-native-gesture-handler 和react-native-reanimated 构建自己的滚动视图。下面是如何制作的完整示例。请注意,Animated 必须从react-native-reanimated 导入。如果需要使用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”而不是“react-native”导入的 ScrollView 组件。

我不需要做任何额外的设置,它工作得很好。

import {ScrollView} from 'react-native-gesture-handler';
© www.soinside.com 2019 - 2024. All rights reserved.