React Native 仿微信通讯录侧边栏滑动选择效果:
完整代码:
import React, {useRef, useEffect} from 'react'; import {Dimensions, StyleSheet, View, Text, SafeAreaView} from 'react-native'; // 获取屏幕宽高 let ScreenWidth = Dimensions.get('window').width; let ScreenHeight = Dimensions.get('window').height; // 始终返回 true 的函数,用于设置触摸响应 const returnTrue = () => true; const TestScreen = () => { const alphabet = [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ]; // 创建 ref 来存储 select_null 和 measure 的值 const selectNull = useRef(null); const measure = useRef({}); // 创建 ref 来引用 sectionItem 的 view const sectionItemRef = useRef(null); // 修复 sectionItem 的尺寸测量 const fixSectionItemMeasure = () => { const sectionItem = sectionItemRef.current; if (!sectionItem) { return; } setTimeout(() => { sectionItem.measure((x, y, width, height, pageX, pageY) => { // 存储测量的数据 measure.current = { y: pageY, width, height, }; }); }, 0); }; // 根据触摸点定位字母 const getZM = (topHeight, currentHeight) => { const navItemHeight = 26; // 字母栏的高度(要在样式里面也设置高度,这里的高度是根据样式动态改变的,字母栏样式的高是多少,这里就写多少) // 计算触摸点在导航中的索引 const indexNav = Math.ceil((currentHeight - topHeight) / navItemHeight) - 1; // ((当前距离屏幕距离-父容器距离屏幕距离) / 元素高度) - 1 = (当前的索引元素距离父容器顶部距离 / 元素高度) - 1 = 第 n 个字母 - 1 = 当前字母的索引值 if (alphabet[indexNav]) { console.log(alphabet[indexNav]); // 在当前触摸点上面 return alphabet[indexNav]; // 返回当前字母 } else { console.log('null'); // 不在触摸点打印下 null } }; // 处理触摸事件,该事件在用户触摸屏幕时调用 const selectNav = e => { const ev = e.nativeEvent.touches[0]; // 获取多个触摸事件的第一个。比如你放了三根手指在屏幕上,他只算第一个放到屏幕上的 const targetY = ev.pageY; // 动态获取屏幕上触摸点垂直方向的距离 const localY = ev.locationY; // 第一次触摸到屏幕上距离顶部的距离 const {y, width, height} = measure.current; // 获取当前的容器距离顶部的距离(这里因为父容器是 top:20,所以这里获取的是 20) console.log('y', y, targetY); // 调用 getZM 函数进行字母定位 getZM(y, targetY); // 定位字母,并且触发相应的事件 }; // 在组件挂载时修复尺寸,该方法在组件第一次渲染后执行 useEffect(() => { fixSectionItemMeasure(); }, []); // 在组件更新时也修复尺寸,该方法在组件更新后执行 useEffect(() => { fixSectionItemMeasure(); }); // 渲染函数组件 return ( <SafeAreaView> <View style={{ width: ScreenWidth, height: ScreenHeight, position: 'relative', }}> <View ref={sectionItemRef} // 将当前 View 的引用存储到 sectionItemRef 中,以便后续可以通过 ref 访问它 style={styles.container} // 设置 View 的样式,使用前面定义的 styles.container 样式 onStartShouldSetResponder={returnTrue} // 定义当用户开始触摸屏幕时是否应该成为响应者的条件 onMoveShouldSetResponder={returnTrue} // 定义当用户移动手指时是否应该成为响应者的条件 onResponderGrant={selectNav} // 当响应器被激活时(即用户开始触摸屏幕)调用的函数 onResponderMove={selectNav} // 当响应器在激活状态下移动时调用的函数 onResponderRelease={selectNull.current} // 当响应器被释放(即用户停止触摸屏幕)时调用的函数 > {alphabet.map((item, index) => { return ( <View style={styles.oit} key={index}> <Text style={{lineHeight: 26, textAlign: 'center'}}> {item} </Text> </View> ); })} </View> </View> </SafeAreaView> ); }; // 样式定义 const styles = StyleSheet.create({ container: { position: 'absolute', top: 20, right: 0, width: 80, height: 676, // 26*26 26 个单词,每个单词 26 高度 backgroundColor: 'pink', }, oit: { flexShrink: 0, height: 26, // 高度自己慢慢调整(如果使用了 flex 布局,一定要看好自己的高度是否被正确设置 ) width: '100%', }, }); export default TestScreen;