react native高性能组件recyclerlistview

本文介绍了如何使用RecyclerListView在React Native中实现滑动输入组件,涉及数据加载、滚动事件处理和视图切换。展示了如何配置LayoutUtil和DataProvider以适应不同布局需求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

官方文档: https://github.com/Flipkart/recyclerlistview/blob/master/README.md
第一步
npm install --save recyclerlistview
or
yarn add recyclerlistview
第二步
主文件

/*
 * @Author: ChenShy
 * @Date: 2022-06-01 15:42:54
 * @Last Modified by: ChenShy
 * @Description: 标尺滑动输入组件
 */
import React, {Component} from 'react';
import {View, StyleSheet, ActivityIndicator, Dimensions, Text, Image} from 'react-native';
import {sc} from 'react-native-size-matters';
import {RecyclerListView, DataProvider} from 'recyclerlistview';
import {LayoutUtil, ViewSelector, ImageRenderer, DataCall,} from './LayoutUtil';

const {width, height} = Dimensions.get('window');

export default class SetTargetWeight extends Component {
    constructor(props) {
        super(props);
        this.state = {
            dataProvider: new DataProvider((r1, r2) => {
                return r1 !== r2;
            }),
            layoutProvider: LayoutUtil.getLayoutProvider(4),
            images: [],
            count: 0,
            viewType: 0,
            contentOffset: 0,
        };
        this.inProgressNetworkReq = false;
        this.recycler = null;
    }

    componentWillMount() {
        this.fetchMoreData();
    }

    viewChangeHandler = viewType => {
        //We will create a new layout provider which will trigger context preservation maintaining the first visible index
        this.setState({
            layoutProvider: LayoutUtil.getLayoutProvider(viewType),
            viewType: viewType,
        });
    };

    async fetchMoreData() {
        if (!this.inProgressNetworkReq) {
            //To prevent redundant fetch requests. Needed because cases of quick up/down scroll can trigger onEndReached
            //more than once
            this.inProgressNetworkReq = true;
            // const images = await DataCall.get(this.state.count, 20);
            let images = []
            for (let i = 97; i < 250; i++) {
                images.push(i)
            }
            this.inProgressNetworkReq = false;
            this.setState({
                dataProvider: this.state.dataProvider.cloneWithRows(
                    this.state.images.concat(images)
                ),
                images: this.state.images.concat(images),
                count: this.state.count + 20,
            });
        }
    }

    componentDidMount() {
        setTimeout(() => {
            this.recycler.scrollToIndex(100, true) // 定位到第n个
        }, 200);
    }

    rowRenderer = (type, data, index) => {
        //We have only one view type so not checks are needed here
        // return <ImageRenderer imageUrl={data} />;
        return <View style={{
            width: sc(81),
            marginTop: sc(13),
            height: sc(22),
            borderRightColor: '#ABD0E0',
            borderRightWidth: sc(1),
            flexDirection: 'row',
            alignItems: 'flex-end'
        }}>
            {[1, 2, 3, 4, 5, 6, 7, 8, 9,].map((item, idx) => {
                return <View style={{
                    height: sc(13),
                    width: sc(9),
                    borderLeftColor: '#ABD0E0',
                    borderLeftWidth: sc(idx == 0 ? 0 : 1)
                }}>
                    {idx == 0 ? index == 0 ? null : <Text style={{
                        fontSize: sc(12),
                        color: '#999999',
                        width: sc(50),
                        textAlign: 'center',
                        position: 'absolute',
                        zIndex: 100,
                        bottom: sc(-17),
                        left: sc(-25)
                    }}>
                        {data}.{item}</Text> : null}
                </View>
            })}

        </View>
    };
    handleListEnd = () => {
        this.fetchMoreData();

        //This is necessary to ensure that activity indicator inside footer gets rendered. This is required given the implementation I have done in this sample
        this.setState({});
    };
    renderFooter = () => {
        //Second view makes sure we don't unnecessarily change height of the list on this event. That might cause indicator to remain invisible
        //The empty view can be removed once you've fetched all the data
        return this.inProgressNetworkReq
            ? <ActivityIndicator
                style={{margin: 100}}
                size="large"
                color={'black'}
            />
            : <View style={{height: 60}}/>;
    };

    handleScroll = (e) => {
        console.log(e.nativeEvent.contentOffset.x);
        this.setState({
            contentOffset: (e.nativeEvent.contentOffset.x),
        })
    }

    render() {
        //Only render RLV once you have the data
        return (
            <View style={styles.container}>
                {/* <ViewSelector
          viewType={this.state.viewType}
          viewChange={this.viewChangeHandler}
        /> */}
                {this.state.count >= 0
                    ? <>
                        <View style={{
                            width: sc(64),
                            height: sc(95),
                            position: 'absolute',
                            top: 0,
                            zIndex: 99,
                            // backgroundColor: 'red',
                            left: (width - sc(64)) / 2,
                            alignItems: 'center',
                            borderWidth: sc(1),
                            borderColor: 'red'
                        }}>
                            <Text style={{
                                height: sc(40),
                                fontSize: sc(26),
                                textAlign: 'center',
                                color: '#22B49C',
                                width: sc(64)
                            }}>{this.state.contentOffset}</Text>
                            <Image style={{width: sc(19.6), height: sc(35), backgroundColor: 'rgba(0,0,0,.1)'}}
                                   resizeMode={'contain'}
                                   source={require('../../../../assets/images/common/Tape.png')}/>
                        </View>
                        <View style={{
                            height: sc(52),
                            marginTop: sc(41),
                            marginHorizontal: sc(10),
                            justifyContent: 'center'
                        }}>
                            <RecyclerListView
                                ref={ref => this.recycler = ref}
                                isHorizontal={true}
                                style={{flex: 1, backgroundColor: '#F6FDFF',}}
                                // contentContainerStyle={{ margin: 30 }}
                                onEndReached={this.handleListEnd}
                                dataProvider={this.state.dataProvider}
                                layoutProvider={this.state.layoutProvider}
                                rowRenderer={this.rowRenderer}
                                renderFooter={this.renderFooter}
                                onScroll={this.handleScroll}
                                renderAheadOffset={1000}
                            />
                        </View>
                    </>
                    : null}
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        // marginTop: sc(30),
        // alignItems: 'stretch',
        // justifyContent: 'space-between',
    },
});

配置文件LayoutUtil.js

import React from 'react';
import {Image, Platform, View, TouchableHighlight, Text} from 'react-native';
import {LayoutProvider} from 'recyclerlistview';
import {Dimensions} from 'react-native';
import {sc} from 'react-native-size-matters';

const {width, height} = Dimensions.get('window');

const isIOS = Platform.OS === 'ios';

export class LayoutUtil {
    static getWindowWidth() {
        // To deal with precision issues on android
        return Math.round(Dimensions.get('window').width * 1000) / 1000 - 6; //Adjustment for margin given to RLV;
    }

    static getLayoutProvider(type) {
        switch (type) {
            case 0:
                return new LayoutProvider(
                    () => {
                        return 'VSEL'; //Since we have just one view type
                    },
                    (type, dim, index) => {
                        const columnWidth = LayoutUtil.getWindowWidth() / 3;
                        switch (type) {
                            case 'VSEL':
                                if (index % 3 === 0) {
                                    dim.width = 3 * columnWidth;
                                    dim.height = 300;
                                } else if (index % 2 === 0) {
                                    dim.width = 2 * columnWidth;
                                    dim.height = 250;
                                } else {
                                    dim.width = columnWidth;
                                    dim.height = 250;
                                }
                                break;
                            default:
                                dim.width = 0;
                                dim.heigh = 0;
                        }
                    }
                );
            case 1:
                return new LayoutProvider(
                    () => {
                        return 'VSEL';
                    },
                    (type, dim) => {
                        switch (type) {
                            case 'VSEL':
                                dim.width = LayoutUtil.getWindowWidth() / 2;
                                dim.height = 250;
                                break;
                            default:
                                dim.width = 0;
                                dim.heigh = 0;
                        }
                    }
                );
            case 4:
                return new LayoutProvider(
                    () => {
                        return 'VSEL';
                    },
                    (type, dim) => {
                        switch (type) {
                            case 'VSEL':
                                // dim.width = LayoutUtil.getWindowWidth() / 2;
                                dim.width = sc(81);
                                dim.height = height;
                                break;
                            default:
                                dim.width = 0;
                                dim.heigh = 0;
                        }
                    }
                );
            case 2:
                return new LayoutProvider(
                    () => {
                        return 'VSEL';
                    },
                    (type, dim) => {
                        switch (type) {
                            case 'VSEL':
                                dim.width = LayoutUtil.getWindowWidth();
                                dim.height = 200;
                                break;
                            default:
                                dim.width = 0;
                                dim.heigh = 0;
                        }
                    }
                );
            default:
                return new LayoutProvider(
                    () => {
                        return 'VSEL';
                    },
                    (type, dim) => {
                        switch (type) {
                            case 'VSEL':
                                dim.width = LayoutUtil.getWindowWidth();
                                dim.height = 300;
                                break;
                            default:
                                dim.width = 0;
                                dim.heigh = 0;
                        }
                    }
                );
        }
    }
}

export class DataCall {
    // Just simulating incremental loading, don't infer anything from here
    static async get(start, count) {
        const responseHusky = await fetch('https://dog.ceo/api/breed/husky/images');
        const responseBeagle = await fetch(
            'https://dog.ceo/api/breed/beagle/images'
        );

        const responseJsonHusky = await responseHusky.json();
        const responseJsonBeagle = await responseBeagle.json();

        const fullData = responseJsonHusky.message.concat(
            responseJsonBeagle.message
        );

        const filteredData = fullData.slice(
            start,
            Math.min(fullData.length, start + count)
        );
        return filteredData;
    }
}

export class ImageRenderer extends React.Component {
    shouldComponentUpdate(newProps) {
        console.log(this.props.imageUrl);
        return this.props.imageUrl !== newProps.imageUrl;
    }

    componentWillUpdate() {
        //On iOS while recycling till the new image is loaded the old one remains visible. This forcefully hides the old image.
        //It is then made visible onLoad
        if (isIOS && this.imageRef) {
            this.imageRef.setNativeProps({
                opacity: 0,
            });
        }
    }

    handleOnLoad = () => {
        if (isIOS && this.imageRef) {
            this.imageRef.setNativeProps({
                opacity: 1,
            });
        }
    };

    render() {
        return (
            <View
                style={{
                    flex: 1,
                    // margin: 3,
                    // backgroundColor: 'yellow',
                    // height: 40,
                }}>
                <Image
                    ref={ref => {
                        this.imageRef = ref;
                    }}
                    style={{
                        flex: 1,
                    }}
                    onLoad={this.handleOnLoad}
                    source={{uri: this.props.imageUrl}}
                />
            </View>
        );
    }
}

export class ViewSelector extends React.Component {
    constructor(props) {
        super(props);
        this.currentView = 0;
    }

    shouldComponentUpdate(newProps) {
        return this.props.viewType !== newProps.viewType;
    }

    onPressHandler = () => {
        this.currentView = (this.currentView + 1) % 4;
        this.props.viewChange(this.currentView);
    };

    render() {
        return (
            <TouchableHighlight
                style={{
                    height: 60,
                    paddingTop: 20,
                    backgroundColor: 'black',
                    alignItems: 'center',
                    justifyContent: 'space-around',
                }}
                onPress={this.onPressHandler}>
                <Text style={{color: 'white'}}>
                    Tap to Change View Type: {this.props.viewType}
                </Text>
            </TouchableHighlight>
        );
    }
}


这本来是想用来做滑动输入组件的
但是还没做完,具体滚动值还没来得及换算,以后有时间再完善

呈现的效果图

2022年06月02日16:51:52

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值