RN仿微信通讯录的滑动导航组件

js 复制代码
import React, {useRef, useState} from 'react';
import {View, Text, SectionList, StyleSheet, PanResponder} from 'react-native';

const ContactList = () => {
  const sectionListRef = useRef(null);
  const alphabetRefs = useRef({});
  const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
  const [currentIndex, setCurrentIndex] = useState('');

  const sections = [
    {
      title: 'A',
      data: ['Alice', 'Amy', 'Alice', 'Amy', 'Alice', 'Amy', 'Alice', 'Amy'],
    },
    {title: 'B', data: ['Bob', 'Bill']},
    {title: 'C', data: ['Charlie', 'Chris']},
    {title: 'D', data: ['David']},
    {
      title: 'E',
      data: [
        'Emma',
        'Ella',
        'Emma',
        'Ella',
        'Emma',
        'Ella',
        'Emma',
        'Ella',
        'Emma',
        'Ella',
        'Emma',
        'Ella',
      ],
    },
    {
      title: 'F',
      data: [
        'Fmma',
        'Flla',
        'Fmma',
        'Flla',
        'Fmma',
        'Flla',
        'Fmma',
        'Flla',
        'Fmma',
        'Flla',
        'Fmma',
        'Flla',
      ],
    },
    {
      title: 'G',
      data: [
        'Gmma',
        'Glla',
        'Gmma',
        'Glla',
        'Gmma',
        'Glla',
        'Emma',
        'Glla',
        'Gmma',
        'Ella',
        'Gmma',
        'Glla',
      ],
    },
    {
      title: 'H',
      data: [
        'Hmma',
        'Hlla',
        'Hmma',
        'Hlla',
        'Hmma',
        'Hlla',
        'Hmma',
        'Hlla',
        'Hmma',
        'Hlla',
        'Hmma',
        'Hlla',
      ],
    },
    {
      title: 'I',
      data: [
        'Imma',
        'Illa',
        'Imma',
        'Illa',
        'Imma',
        'Illa',
        'Imma',
        'Illa',
        'Imma',
        'Illa',
        'Imma',
        'Illa',
      ],
    },
    {
      title: 'J',
      data: [
        'Jmma',
        'Jlla',
        'Jmma',
        'Jlla',
        'Jmma',
        'Jlla',
        'Jmma',
        'Jlla',
        'Jmma',
        'Jlla',
        'Jmma',
        'Jlla',
      ],
    },
  ];

  const renderItem = ({item}) => (
    <View style={styles.contactItem}>
      <Text>{item}</Text>
    </View>
  );

  const renderSectionHeader = ({section: {title}}) => (
    <Text style={styles.sectionHeader}>{title}</Text>
  );

  const handlePanResponderMove = (event, gestureState) => {
    let currentLetter = '';
    for (let letter in alphabetRefs.current) {
      if (
        gestureState.moveY >= alphabetRefs.current[letter].start &&
        gestureState.moveY < alphabetRefs.current[letter].end
      ) {
        currentLetter = letter;
        break;
      }
    }
    setCurrentIndex(currentLetter);
    handleScrollToSection(currentLetter);
  };

  const handleScrollToSection = index => {
    const sectionIndex = sections.findIndex(section => section.title === index);
    if (sectionIndex !== -1 && sectionListRef.current) {
      sectionListRef.current.scrollToLocation({
        sectionIndex,
        itemIndex: 0,
        animated: false,
      });
    }
  };

  const panResponder = PanResponder.create({
    onStartShouldSetPanResponder: () => true,
    onPanResponderMove: handlePanResponderMove,
  });

  return (
    <View style={styles.container}>
      {/* Index Navigation */}
      <View {...panResponder.panHandlers} style={styles.indexNav}>
        {alphabet.map(letter => (
          <View
            key={letter}
            onLayout={event => {
              const layout = event.nativeEvent.layout;
              alphabetRefs.current[letter] = {
                start: layout.y,
                end: layout.y + layout.height,
              };
            }}>
            <Text
              style={[
                styles.indexLetter,
                currentIndex === letter && {fontWeight: 'bold'},
              ]}>
              {letter}
            </Text>
          </View>
        ))}
      </View>

      {/* Contact List */}
      <SectionList
        ref={sectionListRef}
        sections={sections}
        renderItem={renderItem}
        renderSectionHeader={renderSectionHeader}
        keyExtractor={(item, index) => item + index}
        scrollEnabled={false}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'row',
  },
  indexNav: {
    width: 30,
    backgroundColor: '#f0f0f0',
  },
  indexLetter: {
    textAlign: 'center',
    marginVertical: 5,
  },
  contactItem: {
    padding: 20,
    borderBottomWidth: 1,
    borderBottomColor: '#ccc',
  },
  sectionHeader: {
    padding: 10,
    backgroundColor: '#eee',
    fontWeight: 'bold',
  },
});

export default ContactList;

上面代码直接运行的效果图(支持滑动导航,或者自行手动滑动)

相关推荐
徐子颐5 分钟前
从 Vibe Coding 到 Agent Coding:Cursor 2.0 开启下一代 AI 开发范式
前端
小月鸭17 分钟前
如何理解HTML语义化
前端·html
jump68040 分钟前
url输入到网页展示会发生什么?
前端
诸葛韩信44 分钟前
我们需要了解的Web Workers
前端
brzhang1 小时前
我觉得可以试试 TOON —— 一个为 LLM 而生的极致压缩数据格式
前端·后端·架构
yivifu1 小时前
JavaScript Selection API详解
java·前端·javascript
这儿有一堆花1 小时前
告别 Class 组件:拥抱 React Hooks 带来的函数式新范式
前端·javascript·react.js
十二春秋1 小时前
场景模拟:基础路由配置
前端
六月的可乐1 小时前
实战干货-Vue实现AI聊天助手全流程解析
前端·vue.js·ai编程
一 乐2 小时前
智慧党建|党务学习|基于SprinBoot+vue的智慧党建学习平台(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·学习