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

第一种,适合数据量少的情况

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={true} // 是否支持列表滑动
      />
    </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;

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

在这里插入图片描述
第二种,适合数据量大的情况

提供一个数据结构给大家(原本是个json文件,里面大概有五千条数据,下面这种方法适用于数据量过多情况下使用,这个插件就是可以实现长列表渲染可以极高的提升性能)

[
  [
    {
      nickname: 'Adaline Weimann',
      avator: 'resource/image/adminapi/default/default_avatar.png',
    },
  ],
  [
    {
      nickname: 'Robin Powlowski',
      avator: 'resource/image/adminapi/default/default_avatar.png',
    },
  ],
  [
    {
      nickname: 'Carmel Rice',
      avator: 'resource/image/adminapi/default/default_avatar.png',
    },
  ],
  [
    {
      nickname: 'Angelita Thiel',
      avator: 'resource/image/adminapi/default/default_avatar.png',
    },
  ],
  [
    {
      nickname: 'Enrico Murray MD',
      avator: 'resource/image/adminapi/default/default_avatar.png',
    },
  ],
  [
    {
      nickname: 'Filomena Lind DDS',
      avator: 'resource/image/adminapi/default/default_avatar.png',
    },
  ],
];

实现代码如下(这里没有做侧滑,其他的都正常,功能是划到每个分区会把当前字母置顶,就这样,渲染速度极快(可能开头会卡0.5-1秒左右,后面完全不会有卡顿了):

组件的官方网站

import React, {useRef, useState, useEffect} from 'react';
import {
  View,
  Text,
  StyleSheet,
  PanResponder,
  Animated,
  SafeAreaView,
} from 'react-native';
import BigList from 'react-native-big-list'; // yarn add react-native-big-list
import {groupDataByFirstLetter} from './data.json'; // 这个就是那个五千条数据,格式就是我上面的那种格式
const AlphabetSidebar = () => {
  // 这个是列表的js逻辑代码
  const data = groupDataByFirstLetter();
  const aaa = useRef(null);
  console.log('groupDataByFirstLetter()', groupDataByFirstLetter());
  const [letter, setLetter] = useState([
    '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',
  ]);
  console.log('data', data);

  const renderItem = ({item, index}) => {
    // console.log('index', index);
    return (
      <>
        <Text style={styles.item}>{item.nickname}</Text>
      </>
    );
  };

  const renderSectionHeader = props => {
    // console.log('item3', props);
    return (
      <>
        <Text style={styles.item}>{letter[props]}</Text>
      </>
    );
  };

  const renderSectionFooter = props => {
    // console.log('item4', props);
    return (
      <>
        <Text style={styles.item}>{55}</Text>
      </>
    );
  };

  const send = () => {
    // 跳转到第几个字母的位置(索引号为字母的索引号)
    aaa.current.scrollToSection({section: 25, animated: true});
  };

  return (
    <SafeAreaView style={styles.container}>
      {/* 中间列表的代码 */}
      <BigList
        ref={aaa}
        sections={data}
        renderItem={renderItem}
        renderSectionHeader={renderSectionHeader}
        renderSectionFooter={renderSectionFooter}
        itemHeight={50}
        sectionHeaderHeight={50} // Required to show section header
        sectionFooterHeight={10} // Required to show section footer
      />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  item: {
    padding: 15,
    fontSize: 18
  },
});

export default AlphabetSidebar;

可以跟我这个博客结合起来做一个微信侧边栏索引的案例

import React, {useRef, useEffect, useState} from 'react';
import {Dimensions, StyleSheet, View, Text, SafeAreaView} from 'react-native';
import BigList from 'react-native-big-list';
import {groupDataByFirstLetter} from './data.json'; // 这个就是那个五千条数据,格式就是我上面的那种格式
// 获取屏幕宽高
let ScreenWidth = Dimensions.get('window').width;
let ScreenHeight = Dimensions.get('window').height;
// 始终返回true的函数,用于设置触摸响应
const returnTrue = () => true;
const TestScreen = () => {
  // 这个是列表的js逻辑代码
  const data = groupDataByFirstLetter();
  const aaa = useRef(null);
  console.log('groupDataByFirstLetter()', groupDataByFirstLetter());
  const [letter, setLetter] = useState([
    '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',
  ]);
  console.log('data', data);

  const renderItem = ({item, index}) => {
    // console.log('index', index);
    return (
      <>
        <Text style={styles.item}>{item.nickname}</Text>
      </>
    );
  };

  const renderSectionHeader = props => {
    // console.log('item3', props);
    return (
      <>
        <Text style={styles.item}>{letter[props]}</Text>
      </>
    );
  };

  const renderSectionFooter = props => {
    // console.log('item4', props);
    return (
      <>
      </>
    );
  };

  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]); // 在当前触摸点上面
      // 滚动到当前位置
      aaa.current.scrollToSection({section: indexNav, animated: true});
    } 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 style={styles.container2}>
      {/* 中间列表的代码 */}
      <BigList
        ref={aaa}
        sections={data}
        renderItem={renderItem}
        renderSectionHeader={renderSectionHeader}
        renderSectionFooter={renderSectionFooter}
        itemHeight={50}
        sectionHeaderHeight={50} // Required to show section header
        sectionFooterHeight={10} // Required to show section footer
      />

      <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: 25, textAlign: 'center'}}>{item}</Text>
            </View>
          );
        })}
      </View>
    </SafeAreaView>
  );
};

// 样式定义
const styles = StyleSheet.create({
  container: {
    position: 'absolute',
    top: 40,
    right: 0,
    width: 80,
    height: 676, // 26*26
    backgroundColor: 'pink',
    zIndex: 100,
  },
  container2: {
    flex: 1,
    width: Dimensions.get('window').width,
    height: Dimensions.get('window').height,
    position: 'relative',
  },
  item: {
    padding: 15,
    fontSize: 18,
  },
  oit: {
    flexShrink: 0,
    height: 26, // 高度自己慢慢调整
    width: '100%',
  },
});

export default TestScreen;

效果图

在这里插入图片描述
支持点击索引和滑动选择索引

附赠一个方法
该方法可以在数组内部按照title的首字母进行匹配,按照a-z的顺序,结果会有26个小数组,如果匹配到了数据则放入数据中,否则不放入,这样处理的数据结构[[…],[…],[…]] 适用于第二种方式大列表渲染
代码如下:

let arr = [
  {
    "id": 9999,
    "title": "Geneva Barton I",
    "description": "Amet quia sit corrupti incidunt et voluptas."
  },
  {
    "id": 10000,
    "title": "Rickey Gislason",
    "description": "Recusandae sunt maxime autem culpa aliquid."
  }
];

function groupByFirstLetter(arr) {
  let result = {};

  arr.forEach(item => {
    let firstLetter = item.title[0].toLowerCase();
    if (!result[firstLetter]) {
      result[firstLetter] = [];
    }
    result[firstLetter].push(item);
  });

  // 创建包含所有字母的数组,即使没有匹配到的字母也要包含
  let allLetters = 'abcdefghijklmnopqrstuvwxyz'.split('');
  let groupedArray = allLetters.map(letter => result[letter] || []);

  return groupedArray;
}

console.log(groupByFirstLetter(arr));

再加一个方法,该方法可以将特殊字符和表情等首位非字母的放到第二十七个数组内,即是微信通讯录的#键

function groupByFirstLetter(arr) {
  let result = {};
  let specialChars = '!@#$%^&*()_+{}|:"<>?`~-=[]\;\',./';

  arr.forEach(item => {
    let firstChar = item.title[0];
    let firstLetter = firstChar.toLowerCase();

    if (/[a-z]/i.test(firstLetter)) {
      if (!result[firstLetter]) {
        result[firstLetter] = [];
      }
      result[firstLetter].push(item);
    } else {
      if (!result['specialChars']) {
        result['specialChars'] = [];
      }
      result['specialChars'].push(item);
    }
  });

  // 创建包含所有字母的数组,即使没有匹配到的字母也要包含
  let allLetters = 'abcdefghijklmnopqrstuvwxyz'.split('');
  let groupedArray = allLetters.map(letter => result[letter] || []);

  // 添加特殊字符组
  groupedArray.push(result['specialChars'] || []);

  return groupedArray;
}

// 模拟数据
let arr = [
  {
    "id": 9999,
    "title": "Geneva Barton I",
    "description": "Amet quia sit corrupti incidunt et voluptas."
  },
  {
    "id": 10000,
    "title": "Rickey Gislason",
    "description": "Recusandae sunt maxime autem culpa aliquid."
  },
  {
    "id": 10001,
    "title": "12345",
    "description": "Special Character"
  },
  {
    "id": 10002,
    "title": "!@#$%",
    "description": "Special Characters"
  }
];

console.log(groupByFirstLetter(arr));

这个是通过RN将汉字提取拼音首字母的方法,各位可参考汉语分字母排序

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

萧寂173

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值