有一个需求就是点击某一个地方跳转到指定的 section 头部,我想很简单,于是我就是开始写了,因为我知道 sectionList 中有一个方法可以直接用:
这个就可以满足上面的需求,但是 UI 告诉我,不仅仅是这样,滑动的时候也会跟着变,于是我找呀找没有找到,于是我开始封装,我知道有一个方法能够获取当前组件距离屏幕顶部的距离:
然后我就想到我在每一个 section 头部组件中获取当前头部组件距离顶部的距离,这样滑动的时候就可以知道什么时候对应的标题发生改变。下面是源码:
//@ts-check
import React, {useCallback, useRef, useState} from 'react';
import {View, SectionList} from 'react-native';
import styles from './styles';
const sectionTops = []; // section距离上边的距离
let sectionListTop = 0; // sectionList 距离上边的距离
/**
* @typedef {{
* Suspension: (currentIndex: number, jumpToCurrentIndex: (index: number) => void) => React.ReactElement | null,
* uniqueSectionKey: string,
* distanceTop: number,
* suspensionStyle?: import('react-native').ViewStyle
* }
* & import('react-native').SectionListProps
* } SuspensionListProps
* @param {SuspensionListProps} param0 props
*/
const SuspensionList = ({
onScroll,
uniqueSectionKey, // 这个对应的是section所在的下标,不然会出现问题
sections,
renderSectionHeader,
Suspension, // 悬浮组件
onScrollBeginDrag,
distanceTop, // 这个是悬浮的组件距离上面组件的距离
onMomentumScrollEnd,
ListHeaderComponent,
suspensionStyle, // 悬浮组件的样式
...props
}) => {
const refs = [];
const sectionListRef = useRef();
const containerRef = useRef();
const [selectedTabIndex, setTabIndex] = useState(0);
const [currentY, setCurrentY] = useState(0);
const onLayout = useCallback(
(sectionIndex) => {
// 当绘制完成后,记录每一个头部距离顶部的距离
refs[sectionIndex]?.measure((x, y, width, height, pageX, pageY) => {
sectionTops[sectionIndex] = pageY;
});
},
[refs],
);
// 将所有的section组件的ref保存起来
const getSectionHeaderRef = useCallback(
(sectionIndex, el) => {
refs[sectionIndex] = el;
},
[refs],
);
const _renderSectionHeader = useCallback(
({section}) => {
const sectionIndex = section[uniqueSectionKey];
return (
<View
onLayout={() => onLayout(sectionIndex)}
ref={(el) => getSectionHeaderRef(sectionIndex, el)}>
{renderSectionHeader?.({section})}
</View>
);
},
[getSectionHeaderRef, onLayout, uniqueSectionKey, renderSectionHeader],
);
const _onScroll = useCallback(
(nativeEvent) => {
const {
nativeEvent: {
contentOffset: {y},
},
} = nativeEvent;
setCurrentY(y);
onScroll?.(nativeEvent);
// @ts-ignore
if (!sectionListRef.current.isSlide) {
return;
}
// 如果小于当前section下一个距离上面的高度,那么就设置,如果都不满足就是最后一个
for (let i = 1; i < sectionTops.length; i++) {
if (y <= sectionTops[i] - distanceTop) {
setTabIndex(i - 1);
break;
} else {
setTabIndex(sectionTops.length - 1);
}
}
},
[onScroll, distanceTop],
);
const jumpToCurrentIndex = useCallback(
(index) => {
// @ts-ignore
sectionListRef.current?.scrollToLocation({
itemIndex: 0,
sectionIndex: index,
viewPosition: 0,
viewOffset: distanceTop - sectionListTop,
});
setTabIndex(index);
},
[distanceTop],
);
const SuspensionView = useCallback(() => {
return (
<View style={[styles.suspensionStyle, suspensionStyle]}>
{Suspension?.(selectedTabIndex, jumpToCurrentIndex)}
</View>
);
}, [Suspension, jumpToCurrentIndex, selectedTabIndex, suspensionStyle]);
const _onScrollBeginDrag = useCallback(
(event) => {
onScrollBeginDrag?.(event);
// @ts-ignore
sectionListRef.current.isSlide = true;
},
[onScrollBeginDrag],
);
const _onMomentumScrollEnd = useCallback(
(event) => {
onMomentumScrollEnd?.(event);
// @ts-ignore
sectionListRef.current.isSlide = false;
},
[onMomentumScrollEnd],
);
const _ListHeaderComponent = useCallback(() => {
// @ts-ignore
return ListHeaderComponent?.(selectedTabIndex, jumpToCurrentIndex) || null;
}, [ListHeaderComponent, selectedTabIndex, jumpToCurrentIndex]);
const _onLayout = useCallback(() => {
// @ts-ignore
containerRef.current?.measure?.((x, y, width, height, pageX, pageY) => {
sectionListTop = pageY;
});
}, []);
return (
<View style={styles.container} onLayout={_onLayout} ref={containerRef}>
{currentY >= distanceTop && SuspensionView()}
<SectionList
sections={sections}
onScroll={_onScroll}
onScrollBeginDrag={_onScrollBeginDrag}
ListHeaderComponent={_ListHeaderComponent}
ref={sectionListRef}
onMomentumScrollEnd={_onMomentumScrollEnd}
renderSectionHeader={_renderSectionHeader}
{...props}
/>
</View>
);
};
export default SuspensionList;
下面是 styles.js 文件:
import {StyleSheet} from 'react-native';
const styles = StyleSheet.create({
container: {flex: 1},
suspensionStyle: {
position: 'absolute',
zIndex: 9,
width: '100%',
},
});
export default styles;
就这样就得到了封面大图的效果。