此效果仅适用Android app,ios不行(需例外写效果)
效果示例图
示例代码
import React, {useRef, useState} from 'react';
import {
ScrollView,
StyleSheet,
View,
Dimensions,
Animated,
Text,
PanResponder,
FlatList,
} from 'react-native';
import {pxToPd} from '../../common/js/device';
const TabOne = () => {
//手势滑动区域节点
const animatedViewRef = useRef(null);
//单个切换页面的宽度
const deviceWidth = Dimensions.get('window').width;
// 默认显示下标的页面
let currentIndexRef = useRef(0);
//滑动的距离
const defaultMove = -currentIndexRef.current * deviceWidth;
const pan = useRef(new Animated.Value(defaultMove)).current;
const panResponder = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => true,
//处理手势移动事件,其中使用了`dx`参数来表示在x轴上的移动距离
onPanResponderMove: (evt, gestureState) => {
//获取当前滚动区域有几个孩子节点
const count = animatedViewRef.current._children.length;
//每次移动的距离
const moveX = -currentIndexRef.current * deviceWidth;
//当移动到最左侧或者最右侧时,禁止拖动
const start = currentIndexRef.current == 0 && gestureState.dx > 0;
const end = currentIndexRef.current == count - 1 && gestureState.dx < 0;
if (start || end) {
// 禁止继续拖动
return false;
}
if (Math.abs(gestureState.dx) < 50) {
return false;
}
pan.setValue(moveX + gestureState.dx);
},
//处理手势释放时的逻辑
onPanResponderRelease: (_, gestureState) => {
//获取当前滚动区域有几个孩子节点
const count = animatedViewRef.current._children.length;
//当手指拖动区域大于100的时候,开始切换页面
if (Math.abs(gestureState.dx) > 100) {
let newPageIndex = currentIndexRef.current;
if (gestureState.dx > 0) {
newPageIndex = Math.max(0, currentIndexRef.current - 1);
} else {
newPageIndex = Math.min(count - 1, currentIndexRef.current + 1);
}
const moveX = -newPageIndex * deviceWidth;
currentIndexRef.current = newPageIndex;
pan.setValue(moveX);
} else {
pan.setValue(-currentIndexRef.current * deviceWidth);
}
},
}),
).current;
return (
<>
<View style={styles.pagesWrap}>
<ScrollView>
<View style={styles.pagesHeader}>
<Animated.View
ref={animatedViewRef}
style={{
width: deviceWidth * 4,
flex: 1,
flexDirection: 'row',
transform: [{translateX: pan}],
onStartShouldSetResponderCapture: () => false, // 禁止拦截触摸事件
}}
{...panResponder.panHandlers}>
<View style={{width: deviceWidth, backgroundColor: 'red'}}>
<Text>item One</Text>
</View>
<View style={{width: deviceWidth}}>
<Text>item Two</Text>
</View>
<View style={{width: deviceWidth, backgroundColor: 'green'}}>
<Text>item Three</Text>
</View>
<View style={{width: deviceWidth}}>
<Text>item Four</Text>
</View>
</Animated.View>
</View>
<View style={styles.pagesBody}></View>
<View style={styles.pagesFooter}></View>
</ScrollView>
</View>
</>
);
};
const TabTwo = () => {
const [dataList, setDataList] = useState([
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
title: 'First Item',
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
title: 'Second Item',
},
{
id: '58694a0f-3da1-471f-bd96-145571e29d72',
title: 'Third Item',
},
{
id: '58694a0f-3da1-471f-bd96-145571e29d73',
title: 'Four Item',
},
{
id: '58694a0f-3da1-471f-bd96-145571e29d74',
title: 'Five Item',
},
{
id: '58694a0f-3da1-471f-bd96-145571e29d75',
title: 'Six Item',
},
{
id: '58694a0f-3da1-471f-bd96-145571e29d76',
title: 'Seven Item',
},
{
id: '58694a0f-3da1-471f-bd96-145571e29d77',
title: 'eight Item',
},
]);
return (
<>
<View style={styles.pagesWrap}>
<View style={styles.pagesHeader}>
<Text>two</Text>
</View>
<FlatList
data={dataList}
renderItem={({item}) => (
<View style={styles.item}>
<Text style={styles.title}>{item.title}</Text>
</View>
)}
keyExtractor={item => item.id}
ListHeaderComponent={ListHeaderComponent}
ListFooterComponent={ListFooterComponent}
/>
</View>
</>
);
};
const ListHeaderComponent = () => {
//手势滑动区域节点
const animatedViewRef = useRef(null);
//单个切换页面的宽度
const deviceWidth = Dimensions.get('window').width;
// 默认显示下标的页面
let currentIndexRef = useRef(0);
//滑动的距离
const defaultMove = -currentIndexRef.current * deviceWidth;
const pan = useRef(new Animated.Value(defaultMove)).current;
const panResponder = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: (evt, gestureState) => {
console.log('[sdf]');
// 判断是否为滑动事件
return Math.abs(gestureState.dx) > 5 || Math.abs(gestureState.dy) > 5;
},
//处理手势移动事件,其中使用了`dx`参数来表示在x轴上的移动距离
onPanResponderMove: (evt, gestureState) => {
//获取当前滚动区域有几个孩子节点
const count = animatedViewRef.current._children.length;
//每次移动的距离
const moveX = -currentIndexRef.current * deviceWidth;
//当移动到最左侧或者最右侧时,禁止拖动
const start = currentIndexRef.current == 0 && gestureState.dx > 0;
const end = currentIndexRef.current == count - 1 && gestureState.dx < 0;
if (start || end) {
// 禁止继续拖动
return false;
}
if (Math.abs(gestureState.dx) < 50) {
return false;
}
pan.setValue(moveX + gestureState.dx);
},
//处理手势释放时的逻辑
onPanResponderRelease: (_, gestureState) => {
if (Math.abs(gestureState.dx) < 5 && Math.abs(gestureState.dy) < 5) {
// 如果移动距离小于5,则判断为点击事件
console.log('Click Event', currentIndexRef.current);
} else {
// 否则,判断为滑动事件
console.log('Swipe Event');
}
//获取当前滚动区域有几个孩子节点
const count = animatedViewRef.current._children.length;
//当手指拖动区域大于100的时候,开始切换页面
if (Math.abs(gestureState.dx) > 100) {
let newPageIndex = currentIndexRef.current;
if (gestureState.dx > 0) {
newPageIndex = Math.max(0, currentIndexRef.current - 1);
} else {
newPageIndex = Math.min(count - 1, currentIndexRef.current + 1);
}
const moveX = -newPageIndex * deviceWidth;
currentIndexRef.current = newPageIndex;
pan.setValue(moveX);
} else {
pan.setValue(-currentIndexRef.current * deviceWidth);
}
},
}),
).current;
return (
<>
<View style={styles.pagesFooter}>
<Animated.View
ref={animatedViewRef}
style={{
width: deviceWidth * 4,
flex: 1,
flexDirection: 'row',
transform: [{translateX: pan}],
onStartShouldSetResponderCapture: () => false, // 禁止拦截触摸事件
}}
{...panResponder.panHandlers}>
<View style={{width: deviceWidth, backgroundColor: 'red'}}>
<Text>header item One</Text>
</View>
<View style={{width: deviceWidth}}>
<Text>header item Two</Text>
</View>
<View style={{width: deviceWidth, backgroundColor: 'green'}}>
<Text>header item Three</Text>
</View>
<View style={{width: deviceWidth}}>
<Text>header item Four</Text>
</View>
</Animated.View>
</View>
</>
);
};
const ListFooterComponent = () => {
//手势滑动区域节点
const animatedViewRef = useRef(null);
//单个切换页面的宽度
const deviceWidth = Dimensions.get('window').width;
// 默认显示下标的页面
let currentIndexRef = useRef(0);
//滑动的距离
const defaultMove = -currentIndexRef.current * deviceWidth;
const pan = useRef(new Animated.Value(defaultMove)).current;
const panResponder = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: (evt, gestureState) => {
console.log('[sdf]');
// 判断是否为滑动事件
return Math.abs(gestureState.dx) > 5 || Math.abs(gestureState.dy) > 5;
},
//处理手势移动事件,其中使用了`dx`参数来表示在x轴上的移动距离
onPanResponderMove: (evt, gestureState) => {
//获取当前滚动区域有几个孩子节点
const count = animatedViewRef.current._children.length;
//每次移动的距离
const moveX = -currentIndexRef.current * deviceWidth;
//当移动到最左侧或者最右侧时,禁止拖动
const start = currentIndexRef.current == 0 && gestureState.dx > 0;
const end = currentIndexRef.current == count - 1 && gestureState.dx < 0;
if (start || end) {
// 禁止继续拖动
return false;
}
if (Math.abs(gestureState.dx) < 50) {
return false;
}
pan.setValue(moveX + gestureState.dx);
},
//处理手势释放时的逻辑
onPanResponderRelease: (_, gestureState) => {
if (Math.abs(gestureState.dx) < 5 && Math.abs(gestureState.dy) < 5) {
// 如果移动距离小于5,则判断为点击事件
console.log('Click Event');
} else {
// 否则,判断为滑动事件
console.log('Swipe Event');
}
//获取当前滚动区域有几个孩子节点
const count = animatedViewRef.current._children.length;
//当手指拖动区域大于100的时候,开始切换页面
if (Math.abs(gestureState.dx) > 100) {
let newPageIndex = currentIndexRef.current;
if (gestureState.dx > 0) {
newPageIndex = Math.max(0, currentIndexRef.current - 1);
} else {
newPageIndex = Math.min(count - 1, currentIndexRef.current + 1);
}
const moveX = -newPageIndex * deviceWidth;
currentIndexRef.current = newPageIndex;
pan.setValue(moveX);
} else {
pan.setValue(-currentIndexRef.current * deviceWidth);
}
},
}),
).current;
return (
<>
<View style={styles.pagesFooter}>
<Animated.View
ref={animatedViewRef}
style={{
width: deviceWidth * 4,
flex: 1,
flexDirection: 'row',
transform: [{translateX: pan}],
onStartShouldSetResponderCapture: () => false, // 禁止拦截触摸事件
}}
{...panResponder.panHandlers}>
<View style={{width: deviceWidth, backgroundColor: 'red'}}>
<Text>footer item One</Text>
</View>
<View style={{width: deviceWidth}}>
<Text>footer item Two</Text>
</View>
<View style={{width: deviceWidth, backgroundColor: 'green'}}>
<Text>footer item Three</Text>
</View>
<View style={{width: deviceWidth}}>
<Text>footer item Four</Text>
</View>
</Animated.View>
</View>
</>
);
};
const TestSectionList = () => {
const scrollViewRef = useRef(null);
let currentIndexRef = useRef(0);
// 滚动视图的水平偏移量
const scrollX = useState(new Animated.Value(0))[0];
// 页面切换时的处理函数
const handlePageChange = event => {
const offsetX = event.nativeEvent.contentOffset.x;
const pageIndex = Math.round(offsetX / Dimensions.get('window').width);
currentIndexRef.current = pageIndex;
};
return (
<>
<View style={styles.sectionList}>
<ScrollView
ref={scrollViewRef}
horizontal
pagingEnabled
bounces={false}
showsHorizontalScrollIndicator={false}
onScroll={Animated.event(
[{nativeEvent: {contentOffset: {x: scrollX}}}],
{
useNativeDriver: false,
listener: handlePageChange,
},
)}
scrollEventThrottle={16}>
<TabOne />
<TabTwo />
</ScrollView>
</View>
</>
);
};
const styles = StyleSheet.create({
sectionList: {
borderColor: 'red',
borderWidth: pxToPd(1),
borderStyle: 'solid',
flex: 1,
},
pagesWrap: {
borderColor: 'blue',
borderWidth: pxToPd(1),
borderStyle: 'solid',
flex: 1,
width: Dimensions.get('window').width,
},
pagesHeader: {
borderColor: 'blue',
borderWidth: pxToPd(1),
borderStyle: 'solid',
width: '100%',
height: pxToPd(200),
marginTop: pxToPd(20),
},
pagesBody: {
borderColor: 'blue',
borderWidth: pxToPd(1),
borderStyle: 'solid',
width: '93.6%',
marginLeft: '3.2%',
height: pxToPd(1200),
marginTop: pxToPd(20),
},
pagesFooter: {
borderColor: 'blue',
borderWidth: pxToPd(1),
borderStyle: 'solid',
width: '100%',
height: pxToPd(200),
marginTop: pxToPd(20),
marginBottom: pxToPd(20),
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
},
title: {
fontSize: 32,
},
pagesItem: {
borderColor: 'blue',
borderWidth: pxToPd(1),
borderStyle: 'solid',
width: Dimensions.get('window').width,
backgroundColor: 'blue',
},
});
export default TestSectionList;