2025-08-19

import React, { useEffect, useRef } from 'react';

import { View, Text, FlatList, Image, StyleSheet, TouchableOpacity, Dimensions, Animated, ScrollView} from 'react-native';

const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);

// 获取屏幕宽度

const { width } = Dimensions.get('window');

const columnWidth = width / 2; // 计算每列宽度,减去总间距

// 左侧菜单数据

const leftMenu = [

  { id: '1', title: '汉堡kshdkjhskjdhkjshkjha21', price: '¥28', image: 'https://picsum.photos/seed/burger/300/200' },

  { id: '2', title: '披dsdsadasdasd萨32', price: '¥58', image: 'https://picsum.photos/seed/pizza/300/200' },

  { id: '3', title: '热dsdasdas狗', price: '¥38', image: 'https://picsum.photos/seed/burger/300/200' },

  { id: '4', title: '香肠', price: '¥18', image: 'https://picsum.photos/seed/pizza/300/200' },

  { id: '5', title: '炸dasdasasdasdas鸡12sawasasasasass', price: '¥68', image: 'https://picsum.photos/seed/burger/300/200' },

  { id: '6', title: '意大利面', price: '¥98', image: 'https://picsum.photos/seed/pizza/300/200' },

  { id: '7', title: '三明治', price: '¥18', image: 'https://picsum.photos/seed/sandwich/300/200' },

];

// 右侧菜单数据(独立于左侧)

const rightMenu = [

  { id: 'a', title: '可1乐', price: '¥6', image: 'https://picsum.photos/seed/coke/300/200' },

  { id: 'b', title: '果wew汁', price: '¥12', image: 'https://picsum.photos/seed/juice/300/200' },

  { id: 'c', title: '咖42dfg啡', price: '¥15', image: 'https://picsum.photos/seed/coffee/300/200' },

  { id: 'd', title: '奶43deds茶', price: '¥18', image: 'https://picsum.photos/seed/milktea/300/200' },

  { id: 'e', title: '冰dasdasdasdasdasas淇淋', price: '¥10', image: 'https://picsum.photos/seed/icecream/300/200' },

  { id: 'f', title: '气泡dswds水', price: '¥8', image: 'https://picsum.photos/seed/soda/300/200' },

  { id: 'g', title: '果汁dasdasdasdasdasdasdasdxZXAdxas2121', price: '¥12', image: 'https://picsum.photos/seed/juice/300/200' },

  { id: 'h', title: '咖44啡', price: '¥15', image: 'https://picsum.photos/seed/coffee/300/200' },

  { id: 'i', title: '奶hjyttyj茶', price: '¥18', image: 'https://picsum.photos/seed/milktea/300/200' },

  { id: 'j', title: '冰淇淋seasa', price: '¥10', image: 'https://picsum.photos/seed/icecream/300/200' },

  { id: 'k', title: '气泡eww水', price: '¥8', image: 'https://picsum.photos/seed/soda/300/200' },

];

// 创建同步滚动的合并数据(取两列中较长的长度)

let mergedData = [];

const maxLength = Math.max(leftMenu.length, rightMenu.length);

for (let i = 0; i < maxLength; i++) {

  mergedData.push({

    rowId: `row-${i}`,

    leftItem: leftMenu[i],    // 可能为undefined(当左侧数据较短时)

    rightItem: rightMenu[i]  // 可能为undefined(当右侧数据较短时)

  });

}

// 渲染单个菜单项

const MenuItem = ({ item, isLeft, onPress, selectItem }) => {

  if (!item) {

    return <View style={[styles.emptyItem, { width: columnWidth}, isLeft? styles.lineStyle:{}]} />;

  }

  const isSlected = selectItem === item.title; // 这里可以根据需要设置选中状态

  return (

    <TouchableOpacity

      style={[styles.menuItem, { width: columnWidth }, isLeft? styles.lineStyle:{}, isSlected ? {backgroundColor: '#e0f7fa'} : {}]}

      onPress={onPress}

    >

      <View style={styles.itemInfo}>

        <Text numberOfLines={3} style={styles.itemTitle}>{item.title}</Text>

      </View>

    </TouchableOpacity>

  );

};

const SyncScrollTwoColumnMenu = ({contentY = 0, selectItem = '', ...props}) => {

    // 用于跟踪滚动位置的动画值

      const scrollY = new Animated.Value(0);

      // 渐变透明度的动画值

      const gradientOpacity = useRef(new Animated.Value(0)).current;

      // 列表内容高度的引用

      const contentHeight = useRef(0);

      // 列表可见区域高度的引用

      const visibleHeight = useRef(0);

      const flatListRef = useRef(null);

      const [selectItemLayoutY, setSelectItemLayoutY] = React.useState(0);


      // 处理滚动事件,计算渐变透明度

      const handleScroll = (event) => {

        // 1. 可以获取滚动位置等信息

        const scrollPosition = event.nativeEvent.contentOffset.y;

        console.log('滚动位置:', scrollPosition, contentHeight.current, visibleHeight.current);

        if (contentHeight.current === 0 || visibleHeight.current === 0) return;


        // 计算滚动到底部的距离

        const bottomDistance = contentHeight.current - visibleHeight.current - scrollPosition;


        // 当距离底部小于50时开始淡出,小于0时完全消失

        if (bottomDistance < 0) {

          gradientOpacity.setValue(0);

        } else if (bottomDistance < 50) {

          gradientOpacity.setValue(bottomDistance / 50);

        } else {

          gradientOpacity.setValue(1);

        }

      };

      const selectIndex = mergedData.findIndex( item => item.leftItem?.title === selectItem || item.rightItem?.title === selectItem);

      console.log('isSlected', selectIndex);



      // 更新内容高度

      const onContentSizeChange = (width, height) => {

        contentHeight.current = height;

        console.log('contentHeight', height);

      };


      // 更新可见区域高度

      const onLayout = (e) => {

        const { height } = e.nativeEvent.layout;

        visibleHeight.current = height;

        console.log('visibleHeight', height);

      };

      useEffect(() => {   

      if (flatListRef.current && selectItemLayoutY>0) {

        console.log('scrollViewRef.current', selectItemLayoutY);

        flatListRef.current?.scrollTo({

          y: selectItemLayoutY, // 目标 Y 坐标(垂直滚动)

          x: 0, // 目标 X 坐标(水平滚动时使用)

          animated: false // 是否启用平滑动画

        });

      } 

    }, [selectItemLayoutY]);

    // 更新可见区域高度

  const onLayoutSelectItem = (e) => {

    const { y } = e.nativeEvent.layout;

    setSelectItemLayoutY(y);

    console.log('selectItemHeight 布局信息:', y);

  };

  // 渲染每一行(包含左右两个独立项)

  const renderRow = ({ item }) => {

    const isSlected =item.leftItem?.title === selectItem || item.rightItem?.title === selectItem;

    return(

      <View style={styles.rowContainer} onLayout={isSlected? onLayoutSelectItem : ()=>{}}>

        <MenuItem

          selectItem={selectItem}

          item={item.leftItem}

          isLeft={true}

          onPress={() => item.leftItem && props.selectAction?.(item.leftItem.title)}

        />

        <MenuItem

          selectItem={selectItem}

          item={item.rightItem}

          isLeft={false}

          onPress={() => item.rightItem &&  props.selectAction?.(item.rightItem.title)}

        />

    </View>

    );

  }

  return (

    <View style={[styles.container, {top: contentY}]} onLayout={onLayout}>

      {/* 列标题 */}

      <View style={styles.headerRow}>

        <Text style={[styles.columnTitle, { width: columnWidth }]}>主餐</Text>

        <Text style={[styles.columnTitle, { width: columnWidth }]}>饮品</Text>

      </View>

      <ScrollView ref={flatListRef} onScroll={handleScroll} onContentSizeChange={onContentSizeChange} scrollEventThrottle={16}>

        {mergedData.map( item => renderRow({item}))}

      </ScrollView>

      {/* 同步滚动的列表 */}

      {/* <AnimatedFlatList

        ref={flatListRef}

        data={mergedData}

        renderItem={renderRow}

        keyExtractor={item => item.rowId}

        showsVerticalScrollIndicator={false}

        onScroll={handleScroll}

        onContentSizeChange={onContentSizeChange}

        scrollEventThrottle={16} // 确保滚动事件足够频繁以更新动画

      /> */}


      {/* 底部渐变层 */}

        <Animated.View

            style={[

            styles.gradientContainer,

            {

                opacity: gradientOpacity,

            },

            ]}

        >

            <View

            style={styles.gradient}

            />

        </Animated.View>

    </View>

  );

};

const styles = StyleSheet.create({

  container: {

    position: 'absolute',

    top:0,

    left:0,

    right:0,

    // width: '100%',

    height:300,

    backgroundColor: 'white',

    paddingBottom:20,

    zIndex: 1000

  },

  headerRow: {

    flexDirection: 'row',

    justifyContent: 'space-between'

  },

  columnTitle: {

    fontSize: 20,

    fontWeight: 'bold',

    color: '#333',

    borderBottomWidth: 1,

    borderBottomColor: '#ccc',

  },

  rowContainer: {

    height:50,

    flexDirection: 'row',

  },

  menuItem: {

    backgroundColor: 'white'

  },

  lineStyle: {

    borderRightWidth: 1,

    borderRightColor: '#eee',

  },

  emptyItem: {

    backgroundColor: 'white',

  },

  itemImage: {

    width: '100%',

    height: 120,

  },

  itemInfo: {

    flex: 1,

    justifyContent: 'center',

    alignItems: 'center',

    borderBottomWidth: 1,

    borderBottomColor: '#eee'

  },

  itemTitle: {

    paddingHorizontal:12,

    fontSize: 18,

    color: '#333',

    textAlign: 'center'

  },

  itemPrice: {

    fontSize: 11,

    color: '#ff6b35'

  },

  gradientContainer: {

    position: 'absolute',

    left: 0,

    right: 0,

    bottom: 0,

    height: 40, // 渐变高度

    pointerEvents: 'none', // 确保渐变层不影响列表交互

  },

  gradient: {

    flex: 1,

    backgroundColor: 'green',

    // backgroundColor: 'linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%)',

  },

});

export default SyncScrollTwoColumnMenu;

// import React, { useEffect, useRef } from 'react';

// import { View, Text, FlatList, Image, StyleSheet, TouchableOpacity, Dimensions, Animated, ScrollView} from 'react-native';

// const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);

// // 获取屏幕宽度

// const { width } = Dimensions.get('window');

// const columnWidth = width / 2; // 计算每列宽度,减去总间距

// // 左侧菜单数据

// const leftMenu = [

//  { id: '1', title: '汉堡kshdkjhskjdhkjshkjha21', price: '¥28', image: 'https://picsum.photos/seed/burger/300/200' },

//  { id: '2', title: '披dsdsadasdasd萨32', price: '¥58', image: 'https://picsum.photos/seed/pizza/300/200' },

//  { id: '3', title: '热dsdasdas狗', price: '¥38', image: 'https://picsum.photos/seed/burger/300/200' },

//  { id: '4', title: '香肠', price: '¥18', image: 'https://picsum.photos/seed/pizza/300/200' },

//  { id: '5', title: '炸dasdasasdasdas鸡12sawasasasasass', price: '¥68', image: 'https://picsum.photos/seed/burger/300/200' },

//  { id: '6', title: '意大利面', price: '¥98', image: 'https://picsum.photos/seed/pizza/300/200' },

//  { id: '7', title: '三明治', price: '¥18', image: 'https://picsum.photos/seed/sandwich/300/200' },

// ];

// // 右侧菜单数据(独立于左侧)

// const rightMenu = [

//  { id: 'a', title: '可乐', price: '¥6', image: 'https://picsum.photos/seed/coke/300/200' },

//  { id: 'b', title: '果汁', price: '¥12', image: 'https://picsum.photos/seed/juice/300/200' },

//  { id: 'c', title: '咖啡', price: '¥15', image: 'https://picsum.photos/seed/coffee/300/200' },

//  { id: 'd', title: '奶茶', price: '¥18', image: 'https://picsum.photos/seed/milktea/300/200' },

//  { id: 'e', title: '冰dasdasdasdasdasas淇淋', price: '¥10', image: 'https://picsum.photos/seed/icecream/300/200' },

//  { id: 'f', title: '气泡水', price: '¥8', image: 'https://picsum.photos/seed/soda/300/200' },

//  { id: 'g', title: '果汁dasdasdasdasdasdasdasdxZXAdxas2121', price: '¥12', image: 'https://picsum.photos/seed/juice/300/200' },

//  { id: 'h', title: '咖啡', price: '¥15', image: 'https://picsum.photos/seed/coffee/300/200' },

//  { id: 'i', title: '奶茶', price: '¥18', image: 'https://picsum.photos/seed/milktea/300/200' },

//  { id: 'j', title: '冰淇淋', price: '¥10', image: 'https://picsum.photos/seed/icecream/300/200' },

//  { id: 'k', title: '气泡水', price: '¥8', image: 'https://picsum.photos/seed/soda/300/200' },

// ];

// // 创建同步滚动的合并数据(取两列中较长的长度)

// let mergedData = [];

// const maxLength = Math.max(leftMenu.length, rightMenu.length);

// for (let i = 0; i < maxLength; i++) {

//  mergedData.push({

//    rowId: `row-${i}`,

//    leftItem: leftMenu[i],    // 可能为undefined(当左侧数据较短时)

//    rightItem: rightMenu[i]  // 可能为undefined(当右侧数据较短时)

//  });

// }

// // 渲染单个菜单项

// const MenuItem = ({ item, isLeft, onPress, selectItem }) => {

//  if (!item) {

//    return <View style={[styles.emptyItem, { width: columnWidth}, isLeft? styles.lineStyle:{}]} />;

//  }

//  const isSlected = selectItem === item.title; // 这里可以根据需要设置选中状态

//  return (

//    <TouchableOpacity

//      style={[styles.menuItem, { width: columnWidth }, isLeft? styles.lineStyle:{}, isSlected ? {backgroundColor: '#e0f7fa'} : {}]}

//      onPress={onPress}

//    >

//      <View style={styles.itemInfo}>

//        <Text numberOfLines={3} style={styles.itemTitle}>{item.title}</Text>

//      </View>

//    </TouchableOpacity>

//  );

// };

// const SyncScrollTwoColumnMenu = ({contentY = 0, selectItem = '', ...props}) => {

//    // 用于跟踪滚动位置的动画值

//      const scrollY = new Animated.Value(0);

//      // 渐变透明度的动画值

//      const gradientOpacity = useRef(new Animated.Value(1)).current;

//      // 列表内容高度的引用

//      const contentHeight = useRef(0);

//      // 列表可见区域高度的引用

//      const visibleHeight = useRef(0);

//      // select item 高度的引用

//      const selectItemHeight = useRef(0);

//    const scrollViewRef = useRef(null);

//    const [selectItemLayoutY, setSelectItemLayoutY] = React.useState(0);


//      // 处理滚动事件,计算渐变透明度

//      const handleScroll = (event) => {

//        // 1. 可以获取滚动位置等信息

//        const scrollPosition = event.nativeEvent.contentOffset.y;

//        console.log('滚动位置:', scrollPosition);

//        // 2. 可以处理动画(例如滚动时导航栏透明度变化)

//        Animated.event(

//          [{ nativeEvent: { contentOffset: { y: scrollY } } }],

//          { useNativeDriver: false }

//        )(event);

//      };

//      // const selectIndex = mergedData.findIndex( );

//      // console.log('isSlected', selectIndex);


//    // 监听滚动位置变化,更新渐变透明度

//    useEffect(() => {

//      const scrollListener = scrollY.addListener(({ value }) => {

//        if (contentHeight.current === 0 || visibleHeight.current === 0) return;


//        // 计算滚动到底部的距离

//        const bottomDistance = contentHeight.current - visibleHeight.current - value;


//        // 当距离底部小于50时开始淡出,小于0时完全消失

//        if (bottomDistance < 0) {

//          gradientOpacity.setValue(0);

//        } else if (bottomDistance < 50) {

//          gradientOpacity.setValue(bottomDistance / 50);

//        } else {

//          gradientOpacity.setValue(1);

//        }

//      });


//      return () => {

//        scrollY.removeAllListeners();

//      };

//    }, []);

//    useEffect(() => {   

//      if (scrollViewRef.current && selectItemLayoutY>0) {

//        console.log('scrollViewRef.current', selectItemLayoutY);

//        scrollViewRef.current?.scrollTo({

//          y: selectItemLayoutY, // 目标 Y 坐标(垂直滚动)

//          x: 0, // 目标 X 坐标(水平滚动时使用)

//          animated: false // 是否启用平滑动画

//        });

//      } 

//    }, [selectItemLayoutY]);


//      // 更新内容高度

//      const onContentSizeChange = (width, height) => {

//        contentHeight.current = height;

//      };


//  // 更新可见区域高度

//  const onLayout = (e) => {

//    const {height} = e.nativeEvent.layout;

//    visibleHeight.current = height;

//  };

//  // 更新可见区域高度

//  const onLayoutSelectItem = (e) => {

//    const { y } = e.nativeEvent.layout;

//    setSelectItemLayoutY(y);

//    console.log('selectItemHeight 布局信息:', y);

//  };

//  // 渲染每一行(包含左右两个独立项)

//  const renderRow = ({ item }) => {

//    const isSlected =item.leftItem?.title === selectItem || item.rightItem?.title === selectItem;

//    return(

//      <View style={styles.rowContainer} onLayout={isSlected? onLayoutSelectItem : null}>

//        <MenuItem

//          selectItem={selectItem}

//          item={item.leftItem}

//          isLeft={true}

//          onPress={() => item.leftItem && props.selectAction?.(item.leftItem.title)}

//        />

//        <MenuItem

//          selectItem={selectItem}

//          item={item.rightItem}

//          isLeft={false}

//          onPress={() => item.rightItem &&  props.selectAction?.(item.rightItem.title)}

//        />

//    </View>

//    );

//  }

//  return (

//    <View style={[styles.container, {top: contentY}]} onLayout={onLayout}>

//      {/* 列标题 */}

//      <View style={styles.headerRow}>

//        <Text style={[styles.columnTitle, { width: columnWidth }]}>主餐</Text>

//        <Text style={[styles.columnTitle, { width: columnWidth }]}>饮品</Text>

//      </View>

//      <ScrollView ref={scrollViewRef} onScroll={handleScroll} onContentSizeChange={onContentSizeChange} scrollEventThrottle={16}>

//        {mergedData.map( item => renderRow({item}))}

//      </ScrollView>

//      {/* 同步滚动的列表 */}

//      {/* <AnimatedFlatList

//        ref={flatListRef}

//        data={mergedData}

//        renderItem={renderRow}

//        keyExtractor={item => item.rowId}

//        showsVerticalScrollIndicator={false}

//        onScroll={handleScroll}

//        onContentSizeChange={onContentSizeChange}

//        scrollEventThrottle={16} // 确保滚动事件足够频繁以更新动画

//      /> */}


//      {/* 底部渐变层 */}

//        <Animated.View

//            style={[

//            styles.gradientContainer,

//            {

//                opacity: gradientOpacity,

//            },

//            ]}

//        >

//            <View

//            style={styles.gradient}

//            />

//        </Animated.View>

//    </View>

//  );

// };

// const styles = StyleSheet.create({

//  container: {

//    position: 'absolute',

//    top:0,

//    left:0,

//    right:0,

//    // width: '100%',

//    height:300,

//    backgroundColor: 'white',

//    paddingBottom:20,

//    zIndex: 1000

//  },

//  headerRow: {

//    flexDirection: 'row',

//    justifyContent: 'space-between'

//  },

//  columnTitle: {

//    fontSize: 20,

//    fontWeight: 'bold',

//    color: '#333',

//    borderBottomWidth: 1,

//    borderBottomColor: '#ccc',

//  },

//  rowContainer: {

//    flexDirection: 'row',

//  },

//  menuItem: {

//    backgroundColor: 'white'

//  },

//  lineStyle: {

//    borderRightWidth: 1,

//    borderRightColor: '#eee',

//  },

//  emptyItem: {

//    backgroundColor: 'white',

//  },

//  itemImage: {

//    width: '100%',

//    height: 120,

//  },

//  itemInfo: {

//    flex: 1,

//    justifyContent: 'center',

//    alignItems: 'center',

//    borderBottomWidth: 1,

//    borderBottomColor: '#eee'

//  },

//  itemTitle: {

//    paddingHorizontal:12,

//    paddingVertical:8,

//    fontSize: 18,

//    color: '#333',

//    textAlign: 'center'

//  },

//  itemPrice: {

//    fontSize: 11,

//    color: '#ff6b35'

//  },

//  gradientContainer: {

//    position: 'absolute',

//    left: 0,

//    right: 0,

//    bottom: 0,

//    height: 40, // 渐变高度

//    pointerEvents: 'none', // 确保渐变层不影响列表交互

//  },

//  gradient: {

//    flex: 1,

//    backgroundColor: 'green',

//    // backgroundColor: 'linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%)',

//  },

// });

// export default SyncScrollTwoColumnMenu;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容