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;