在 ScrollView 上显示 FlastList

Display FlastList over a ScrollView

概览

我想在垂直滚动的 FlatList 中显示文本列表。这个 FlatList 中的每个项目都可以有一个可变的背景图像,并且它们应该与每个 FlatList 项目保持同步。我把背景放在一个水平的ScrollView中。

方法一——理想方案

这是我的理想解决方案的模型,1 个 FlatList 和 1 个 ScrollView。 FlastList 具有绝对位置并显示在 ScrollView 之上。

我已经完成了那个设计 here in an Expo Snack。问题是 ScrollView 停止工作。

方法二

我也尝试过将 FlatList 放在 ScrollView 中。然后每个图像都有它自己的 FlatList。问题是你有多个 FlastLists,然后你必须尝试在用户滚动时保持它们同步 left/right.

方法三

然后我尝试解决这个问题,只多次显示 FlatList 组件的一个实例。这有同样的问题。他们不保持同步。您可以看到 here in an Expo Snack.

的示例

我可以通过保存显示文本的索引并自动将它们滚动到正确的偏移量来使方法 2 和 3 起作用。我实际上是在本地构建的,但问题是可能会有延迟。如果你快速滚动,你会看到它试图赶上。我的 FlatList 可以有 100 个项目,所以我认为延迟来自于尝试同步 100 个 FlatList。我也对走这条路犹豫不决,因为我真的不想要嵌套重复的 FlatList。我觉得这很快就会成为一个大的性能问题。

一段时间以来,我一直在尝试解决这个问题,测试了不同的样式、位置和方法。没有运气。提前致谢。

我的理解是你有一个项目列表。上下滚动是您循环浏览这些项目的方式,左右滑动将更改每个项目的背景。如果是这种情况,那么您只需要一个用于垂直滚动的 FlatList,以及一些用于左右滑动的滑动手势检测器。

它可能看起来像这样:

import React, {useState, useCallback} from 'react';
import { 
  Text,
  View,
  StyleSheet,
  FlatList,
  ScrollView,
  Dimensions,
  // if you intend to support both potrait and landscape modes
  // useWindowDimensions will be more useful than Dimensions
  useWindowDimensions,
  ImageBackground
} from 'react-native';
import Constants from 'expo-constants';
import {
  GestureDetector,
  Gesture,
  GestureHandlerRootView,
  Directions
} from 'react-native-gesture-handler';
const backgroundIMages = [
  'https://parispeaceforum.org/wp-content/uploads/2021/10/NET-ZERO-SPACE-INITIATIVE-1.png',
  'https://images.pexels.com/photos/7311920/pexels-photo-7311920.jpeg?cs=srgb&dl=pexels-alberlan-barros-7311920.jpg&fm=jpg',
  'https://lh3.googleusercontent.com/YGJ77qN9KiwctZgfqV8Bf3hNo0rZvcFaPKDTkvtS6kVbtwyCS80Pm6dpXzJCCLZE1Q'];
const data = [{
    text: 'test 1',
    imageIndex:0
  },{
    text: 'test 2',
    imageIndex:0
  },{
    text: 'test 3',
    imageIndex:0
  }];
  
export default function App() {
  // because the data has a background that can change
  // it needs to become a state variable
  const [itemData, setItemData] = useState(data);
  // keep track of what item is on screen so only that
  const [currentIndex, setCurrentIndex] = useState(0);
  const currentItem = itemData[currentIndex];
 
  
  // wrapped in useCallback to avoid the "onViewableItemsChanged 
  // cannot change on the fly" error
  // when new item comes on screen change currentIndex
  const onViewableItemsChanged = useCallback(({viewableItems})=>{
    // viewabilityConfig filters what items of the list appears here
    // currently the item has to be 80% on screen, so only one item will
    // be viewable at a time
    let itemOnScreen = viewableItems[0]
    if(!itemOnScreen)
      return
    setCurrentIndex(itemOnScreen.index)
  },[])
  // onRight swipes decrement background for currentItem in view
  const flingRight = Gesture.Fling().direction(Directions.RIGHT).onStart(()=>{
    let newIndex = currentItem.imageIndex -1;
    if(newIndex < 0)
      newIndex = backgroundIMages.length -1
    const newItems = [...itemData];
    currentItem.imageIndex = newIndex
    newItems[currentIndex] = currentItem
    setItemData(newItems)
  })
  // onLeft swipe incremet background for currentItem in view
  const flingLeft = Gesture.Fling().direction(Directions.LEFT).onStart(()=>{
    let newIndex = currentItem.imageIndex +1;
    if(newIndex >= backgroundIMages.length)
      newIndex = 0
    const newItems = [...itemData];
    currentItem.imageIndex = newIndex
    newItems[currentIndex] = currentItem
    setItemData(newItems)
  })
  const fling = Gesture.Exclusive(flingLeft,flingRight)
  return (
    <GestureHandlerRootView style={{flex:1}}>
    <View style={styles.container}>
      <ImageBackground
        style={styles.imgBackground}
        source={{uri:backgroundIMages[currentItem.imageIndex]}}
        resizeMode="cover"
      >
        <GestureDetector gesture={fling}>
          <FlatList
            style={[styles.flatList]}
            data={itemData}
            renderItem={({ item }) => (
              <View style={styles.flatListView}>
                <Text style={{color: 'red', fontSize: 20}}>{item.text}</Text>
              </View>
            )}
            viewabilityConfig={{
              itemVisiblePercentThreshold:80
            }}
            onViewableItemsChanged={ onViewableItemsChanged }
            extraData={itemData}
          />
        </GestureDetector>
      </ImageBackground>
    </View>
    </GestureHandlerRootView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    paddingTop: Constants.statusBarHeight,
    backgroundColor: '#ecf0f1',
    padding: 8,
  },
  imgBackground: {
    flex:1
  },
  flatList: {
    flex:1
  },
  flatListView: {
    height: Dimensions.get('window').height,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

试一试here

@PhantomSpooks - 感谢您的指导。你给了我关于使用手势的想法,所以我花了最后两天时间来构建它。

这是一个有效的 Expo Snack 示例,正是我想要的。也不需要任何外部包,不过,我可能会在未来将其作为一个包发布。此外,在该示例中,如果您尝试单击和拖动,则 FlatList 的垂直滚动不适用于 Web 选项卡,但使用鼠标滚轮可以。不过我觉得这很正常。

我现在看到的唯一问题是,如果您快速滑动 left/right,背景图像会发生偏移。我现在没有时间调查那个。我无法在 Android 模拟器上的 React Native non-expo 项目中复制它。可能只是因为它有点滞后,让 UI 赶上了。