美文网首页RN-组件的简单使用
RN-SectionList-实现含有索引功能的通讯录

RN-SectionList-实现含有索引功能的通讯录

作者: 精神病患者link常 | 来源:发表于2017-08-28 14:24 被阅读364次

    本文内容
    1、SectionList 的简单使用
    2,右侧索引的实现
    3,实现sectionList按索引滚动的方法

    5F8363F3-92EA-4231-806B-F2CAA748605C.png

    关于右侧索引滑动实现的问题

    <View style={styles.sectionItemViewStyle}
                      ref="sectionItemView"
                      onStartShouldSetResponder={()=>true} // 在用户开始触摸的时候(手指刚刚接触屏幕的瞬间),是否愿意成为响应者?
                      onMoveShouldSetResponder={()=>true} // :如果View不是响应者,那么在每一个触摸点开始移动(没有停下也没有离开屏幕)时再询问一次:是否愿意响应触摸交互呢?
                      onResponderGrant={this.responderGrant} // View现在要开始响应触摸事件了。这也是需要做高亮的时候,使用户知道他到底点到了哪里
                      onResponderMove={this.responderMove} // 用户正在屏幕上移动手指时(没有停下也没有离开屏幕)
                      onResponderRelease={this.responderRelease} // 触摸操作结束时触发,比如"touchUp"(手指抬起离开屏幕)
                >
                    {sectionItem}
                </View>
    

    有三个方法可以利用
    1,用户手指按下去 (改变背景色)
    2,用户手指移动(不离开屏幕)(获取移动到那个一个确定的位置,进而改变sectionList选中的section)
    3,用户手指抬起 (改变背景色)

    · onResponderMove(event) 此方法会返回一个pageY的参数,此参数是当前手指在屏幕的Y坐标

    ·右侧索引表的A~Z 每一个Item的高度都是固定的,所以根据pageY和item的高度,可以计算出当前手指在哪个item的范围内

     /*手指滑动,触发事件*/
        scrollSectionList(event) {
            const touch = event.nativeEvent.touches[0];
    
            // 手指滑动范围 从 A-Q  范围从50 到 50 + sectionItemHeight * cities.length
            if (touch.pageY - statusHeight >= sectionTopBottomHeight && touch.pageY <= statusHeight + sectionTopBottomHeight + sectionItemHeight * cities.length) {
    
                //touch.pageY 从顶部开始,包括导航条 iOS 如此,如果是android 则具体判断
                const index = (touch.pageY - statusHeight - sectionTopBottomHeight) / sectionItemHeight;
    
                console.log(parseInt(index));
    
                // 默认跳转到 第 index 个section  的第 1 个 item
                this.refs.sectionList.scrollToLocation({animated: true, itemIndex: 0, sectionIndex: parseInt(index)});
    
            }
        }
    
    

    完整代码

    /**
     * Created by mymac on 2017/8/24.
     */
    /**
     * Created by mymac on 2017/8/24.
     */
    /**
     * Sample React Native App
     * https://github.com/facebook/react-native
     * @flow
     */
    
    import React, { Component } from 'react';
    import {
        AppRegistry,
        StyleSheet,
        Text,
        View,
        SectionList,
        Dimensions,
        TouchableOpacity,
    } from 'react-native';
    
    const {width, height} = Dimensions.get('window');
    
    import cities from './city.json';
    const statusHeight = 20;
    const rowHeight = 44;
    const separatorHeight = 1;
    const headerHeight = 24;
    const sesctionWidth = 20;
    const sectionTopBottomHeight = 50;
    const sectionItemHeight = (height - statusHeight - sectionTopBottomHeight * 2) / cities.length;
    
    const touchDownBGColor = '#999999';
    const touchUpBGColor = 'transparent';
    
    export default class flatList extends Component {
    
        constructor(props){
            super(props);
    
            this.state={
                data: [],
                refreshing: false,
                isTouchDown: false,
            };
    
            this.renderItem = this.renderItem.bind(this);
            this.separatorComponent = this.separatorComponent.bind(this);
            this.listFooterComponent = this.listFooterComponent.bind(this);
            this.listHeaderComponent = this.listHeaderComponent.bind(this);
    
            this.sectionItemView = this.sectionItemView.bind(this);
            this.itemLayout = this.itemLayout.bind(this);
    
            this.responderGrant = this.responderGrant.bind(this);
            this.responderMove = this.responderMove.bind(this);
            this.responderRelease = this.responderRelease.bind(this);
            this.scrollSectionList = this.scrollSectionList.bind(this);
        }
    
        componentDidMount() {
            setTimeout(()=>{
                this.setState({
                    data: cities,
                });
    
                // 数据结构示例
                // { key1: "A", data: [{ name: "阿童木" }, { name: "阿玛尼" }, { name: "爱多多" }] },
    
            }, 2000)
        }
    
        /*section header*/
        renderSectionHeader(info){
            return (
                    <Text style={styles.sectionStyle}>
                        {info.section.key}
                    </Text>
    
            )
        }
    
        /*row*/
        renderItem(info){
            /*
            * index   0
            * item   { name: "阿童木" }  要显示的值
            * section  当前section 的整个数据
            * */
    
            return (
    
                <Text style={styles.rowStyle}>
                    {info.item.name}
                </Text>
    
            )
        }
        /*分割线*/
        separatorComponent(){
            return <View style={styles.separtorStyle}/>
        }
    
        /*底部组件*/
        listFooterComponent(){
            return this.state.data.length !== 0 ? <View>
                    <Text style={styles.sectionHeaderFooterStyle}>我是底部组件</Text>
                </View> : null;
        }
    
        /*头部组件*/
        listHeaderComponent(){
            return this.state.data.length !== 0 ? <View>
                    <Text style={styles.sectionHeaderFooterStyle}>我是头部组件</Text>
                </View> : null;
        }
    
        /*没有数据时显示的组件*/
        listEmptyComponent() {
            return (
                <View style={styles.noDataViewStyle}>
                    <Text style={styles.noDataSubViewStyle}>
                        暂时没有数据,先等2秒
                    </Text>
                </View>
            )
        }
    
        /*刷新*/
        refresh(){
            this.setState({
                refreshing: true,
            });
            setTimeout(()=>{
                this.setState({
                    refreshing: false,
                });
            },2000);
        }
    
        /*用户手指开始触摸*/
        responderGrant(event){
            this.scrollSectionList(event);
    
            this.setState({
                isTouchDown: true,
            })
        }
    
        /*用户手指在屏幕上移动手指,没有停下也没有离开*/
        responderMove(event){
            this.scrollSectionList(event);
    
            this.setState({
                isTouchDown: true,
            })
        }
    
        /*用户手指离开屏幕*/
        responderRelease(event){
            this.setState({
                isTouchDown: false,
            })
        }
    
        /*手指滑动,触发事件*/
        scrollSectionList(event) {
            const touch = event.nativeEvent.touches[0];
    
            // 手指滑动范围 从 A-Q  范围从50 到 50 + sectionItemHeight * cities.length
            if (touch.pageY - statusHeight >= sectionTopBottomHeight && touch.pageY <= statusHeight + sectionTopBottomHeight + sectionItemHeight * cities.length) {
    
                //touch.pageY 从顶部开始,包括导航条 iOS 如此,如果是android 则具体判断
                const index = (touch.pageY - statusHeight - sectionTopBottomHeight) / sectionItemHeight;
    
                console.log(parseInt(index));
    
                // 默认跳转到 第 index 个section  的第 1 个 item
                this.refs.sectionList.scrollToLocation({animated: true, itemIndex: 0, sectionIndex: parseInt(index)});
    
            }
        }
    
        /*右侧索引*/
        sectionItemView(){
    
            const sectionItem = cities.map((item, index)=>{
                return <Text key={index}
                             style={
                                 [styles.sectionItemStyle,
                                 {backgroundColor: this.state.isTouchDown ? touchDownBGColor : touchUpBGColor}]
                             }
                    >
                    {item.key}
                    </Text>
            });
    
            return(
                <View style={styles.sectionItemViewStyle}
                      ref="sectionItemView"
                      onStartShouldSetResponder={()=>true} // 在用户开始触摸的时候(手指刚刚接触屏幕的瞬间),是否愿意成为响应者?
                      onMoveShouldSetResponder={()=>true} // :如果View不是响应者,那么在每一个触摸点开始移动(没有停下也没有离开屏幕)时再询问一次:是否愿意响应触摸交互呢?
                      onResponderGrant={this.responderGrant} // View现在要开始响应触摸事件了。这也是需要做高亮的时候,使用户知道他到底点到了哪里
                      onResponderMove={this.responderMove} // 用户正在屏幕上移动手指时(没有停下也没有离开屏幕)
                      onResponderRelease={this.responderRelease} // 触摸操作结束时触发,比如"touchUp"(手指抬起离开屏幕)
                >
                    {sectionItem}
                </View>
            );
        }
    
        /*每一项的高度(rowHeight)和其在父组件中的偏移量(offset)和位置(index)
        * length :  当前rowItem的高度
        * offset : 当前rowItem在父组件中的偏移量(包括rowItem的高度 + 分割线的高度 + section的高度)
        * index  :  当前rowItem的位置
        *
        * 如果需要手动的跳转。则必须实现此方法
        * */
        itemLayout(data, index) {
            return {length: rowHeight, offset: (rowHeight + separatorHeight) * index + headerHeight, index};
        }
    
        // http://www.jianshu.com/p/09dd60d7b34f
    
        render() {
    
            return (
                <View style={{flex: 1, marginTop: statusHeight}}>
    
                    <SectionList
                        ref="sectionList"
                        renderSectionHeader={this.renderSectionHeader} // sectionHeader
                        renderItem={this.renderItem} // rowItem
                        sections={this.state.data} // 数据源
                        // getItemLayout={this.itemLayout} // 在知道高度的情况下,得到每个item的位置 , 由于我改了源码,在源码中跳转指定了位置信息,此处不实现该方法
                        keyExtractor={(item, index)=> item.code} // 每个item都有唯一的key
                        ItemSeparatorComponent={this.separatorComponent} // 分割线
                        ListHeaderComponent={this.listHeaderComponent} // 头部组件
                        ListFooterComponent={this.listFooterComponent} // 尾部组件
                        ListEmptyComponent={this.listEmptyComponent()} // 没有数据时显示的组件
                        refreshing={this.state.refreshing} // 是否刷新 ,自带刷新控件
                        onRefresh={()=>{
                                  this.refresh();
                              }} // 刷新方法,写了此方法,下拉才会出现  刷新控件,使用此方法必须写 refreshing
                        // horizontal={true}
                    />
    
                    {this.sectionItemView()}
                </View>
            );
        }
    }
    
    const styles = StyleSheet.create({
        sectionStyle:{
            color: 'black',
            backgroundColor: '#f5f5f5',
            paddingLeft: 20,
            height: headerHeight,
            lineHeight: headerHeight,
        },
        rowStyle:{
            height: rowHeight,
            lineHeight: rowHeight,
            width: width,
            marginLeft: 30,
            color: 'black',
        },
        separtorStyle:{
            height: separatorHeight,
            backgroundColor: '#f5f5f5'
        },
        sectionHeaderFooterStyle:{
            alignItems: 'center',
            textAlign: 'center',
            height: sectionTopBottomHeight,
            backgroundColor: 'red',
            lineHeight: sectionTopBottomHeight,
        },
        noDataViewStyle:{
            backgroundColor: 'red',
            flex: 1,
            height: height
        },
        noDataSubViewStyle:{
            alignItems: 'center',
            textAlign: 'center',
            lineHeight: height,
            color: 'white'
        },
        sectionItemViewStyle:{
            position: 'absolute',
            width: sesctionWidth,
            height: height - statusHeight,
            right: 0,
            top: 0,
            paddingTop: sectionTopBottomHeight,
            paddingBottom: sectionTopBottomHeight,
        },
        sectionItemStyle:{
            textAlign: 'center',
            alignItems: 'center',
            height: sectionItemHeight,
            lineHeight: sectionItemHeight
        },
    
    });
    
    AppRegistry.registerComponent('RNProjectTestApp', () => flatList);
    
    

    数据结构,摘取其中部分

    其中 key 这个字段 ,最好是带上,且必须是唯一的
    data 这个字段是必须的,源码里面就是 .data 出来得到数据的

    [
        {
            "key": "A",
            "data": [
                {
                    "name": "阿拉善盟",
                }
            ]
        },
        {
            "key": "B",
            "data": [
                {
                    "name": "北京市",
                }
            ]
        },
        {
            "key": "C",
            "data": [
                {
                    "name": "承德市",
                }
            ]
        },
        {
            "key": "D",
            "data": [
                {
                    "name": "大同市",
                }
            ]
        }
    ]
    

    重中之重的重点:

      this.refs.sectionList.scrollToLocation({animated: true, itemIndex: 0, sectionIndex: parseInt(index)});
    

    sectionList 只包含一个 scrollToLocation 的方法,实现方法
    路径:

    node_modules/react-native/Libraries/Lists/VirtualizedSectionList.js,代码格式化后大概在150行的位置
    
      scrollToLocation(params: {
        animated?: ?boolean,
        itemIndex: number,
        sectionIndex: number,
        viewPosition?: number,
      }) {
        
        let index = params.itemIndex + 1;
        for (let ii = 0; ii < params.sectionIndex; ii++) {
          index += this.props.sections[ii].data.length + 2;
        }
    
        const toIndexParams = {
          ...params,
          index,
        };
        this._listRef.scrollToIndex(toIndexParams);
    }
    
    

    本人脑子不够用,实在没有看懂这里面的逻辑,for 里面的 +2 ,实在是没有看懂,而且用了这个 scrollToLocation 方法,并不能真正的跳转到我想跳转的位置,测试了好几天,很尴尬😝

    本人的解决办法,重新改造了新的 scrollToLocation 的方法,

    scrollToLocation(params: {
        animated?: ?boolean,
        itemIndex: number,
        sectionIndex: number,
        viewPosition?: number,
      }) {
        // 44 = rowItem 的高度
        const rowItemHeight = 44;
    
        // 24 = sectionItem 的高度
        const sectionItemHeight = 24;
    
        // 50 = 头部item 的高度
        const sectionHeaderHeight = 50;
    
        // 1 = rowItem 之间的分割线的高度
        const separatorHeight = 1;
    
        // 当前 section 之前的 section 的 rowItem(分割线) 的总高度 , 默认为头部高度
        let upRowHeight = sectionHeaderHeight;
    
        for (let ii = 0; ii < params.sectionIndex; ii++) {
          // rowItem 的高度 + 分割线的高度 + sectionHeader 的高度,
          upRowHeight += this.props.sections[ii].data.length * rowItemHeight + (this.props.sections[ii].data.length - 1) * separatorHeight + sectionItemHeight;
        }
    
        // 当前需要显示的 rowItem 偏移量   所有rowItem的高度 + 所有分割线的高度
        let downHeight = params.itemIndex * rowItemHeight + params.itemIndex * separatorHeight;
    
        // 滚动到具体的位置
        this._listRef.scrollToOffset({ animated: true, offset: upRowHeight + downHeight })
    }
    

    ·原本的 scrollToLocation 的方法内部是根据 scrollToIndex 来跳转的

    ·我的 scrollToLocation 的方法内部是根据 scrollToOffset 来跳转的

    由于改了源码,所以缺点很明显。。。。

    但我是实在理解不了原来的 scrollToLocation 方法里面的逻辑,希望有大神能注意到这里的话,麻烦给讲解下😝~O(∩_∩)O谢谢

    相关文章

      网友评论

      • 258029da5a6c:为什么我写的只能在ios上正常跳转,安卓上点击没反应
        258029da5a6c:@西海燃风 scrollToSection(letter, index) {
        console.log('1111111')
        this.sectionList.scrollToLocation({animated: true,sectionIndex:index, itemIndex:0, viewOffset:26})
        }在这个函数部分加入测试也是有输出的
        258029da5a6c:这是我的那行代码this.sectionList.scrollToLocation({sectionIndex:index, itemIndex:0, viewOffset:26})
        258029da5a6c:就在 this.refs.sectionList.scrollToLocation({animated: true, itemIndex: 0, sectionIndex: parseInt(index)});这个部分,也不知道原因
      • 不进则退:数据源返回的字段不是data额
      • KooHead:我这边也是跳转不到正确的地方,然后我把getItemLayout删除掉,然后,scrollToLocation({
        animated: true,
        sectionIndex: index,
        itemIndex: 0,
        viewOffset: HEADER_HEIGHT // 头部高度
        }) 可以准确跳转了。
        大唐帝国:是的,我随便写了个50也可以了
      • ugpass:谢谢你的文章,我按照你的文章,然后在scrollToLocation方法中添加viewOffset的头部视图高度就可以准确跳转了
      • 浩神:我写了一篇文章。。。不过还没写完。。。

      本文标题:RN-SectionList-实现含有索引功能的通讯录

      本文链接:https://www.haomeiwen.com/subject/ylhedxtx.html