React Native 无限循环轮播思路一

对于React Native,我想说入坑需谨慎

背景

最近做项目中有一个类似今日头条小视频左右滑动可以切换小视频的需求。对于这个需求如何实现,我首先想到的是用FlatList去解决,但是FlatList扩展性很差,不太适合。然后我想到了VirtualizedList去实现,想了想还是很麻烦,项目很急,自己去一点点写来不及。如果是用ScrollView去实现此功能,倒是比较容易,但是考虑到列表的数据量可能是成百上千条数据,即使再优化,数据量一多,App肯定卡的动不了。后来我发现react-native-swiper这个库,它有针对左右滑动长列表的优化,尝试了一下还可以,然后就使用了。功能很快完成了,但是当数据量达到200条以后,就明显感觉到卡顿了,App上线后用户反馈并不好。既然这些组件都不能很好的解决长列表的问题,那我自己写一个滑动组件。

效果

效果图

思路

1、每次展示列表中的三条数据
2、三条数据插入方式如图,其实是5条数据,第一条和最后一条分别为第三条数据和第一条数据(随便一画有点难看):


图片插入方式

3、每一条数据都为屏幕宽度"const {width} = Dimensions.get('window')",总宽度度为5倍宽度"width * 5",当然这个宽度可以自定义。
4、首先展示第一条数据(数字为1的数据),若向做滑动到最后为1条数据的时候,在动画完成后,将位置重置为数字为1的地方,这样就实现了左滑功能,右滑动反之。
5、需要是用手势PanResponder与动画Animated,来实现滑动拖拽与动画效果

代码

import React, {Component} from 'react';
import {View, Animated, Dimensions, PanResponder, Image} from 'react-native';

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

class SwiperView extends Component {
  constructor(props){
    super(props);
    this.state={
      sports: new Animated.Value(-width), // 设置初始值
    }
    this.startTimestamp = 0 // 拖拽开始时间戳(用于计算滑动速度)
    this.endTimestamp = 0 // 拖拽结束时间戳用于计算滑动速度)
    this.page = 1 // 首次展示第一条数据(page 最小值为0,即从0开始,1为第二个条目)
  }
  componentWillMount () {
    this.panResponder()
  }

  panResponder () {
    this._panResponder = PanResponder.create({
      onMoveShouldSetPanResponder: (evt, gestureState) => true,
      onPanResponderTerminationRequest: (evt, gestureState) => true,
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
      onPanResponderGrant: (evt, gestureState) => {
        // 滑动开始,记录时间戳
        this.startTimestamp = evt.nativeEvent.timestamp
      },
      onPanResponderMove: (evt, gestureState) => {
        // 滑动横向距离
        let x = gestureState.dx
        // 实时改变滑动位置
        if (x > 0) {
          this.setState({
            sports: new Animated.Value(-this.page * width + x)
          })
        } else {
          this.setState({
            sports: new Animated.Value(x - this.page * width)
          })
        }
      },
      onPanResponderRelease: (evt, gestureState) => {
        // 滑动结束时间戳
        this.endTimestamp = evt.nativeEvent.timestamp
        // 滑动距离,根据滑动距离与时间戳计算是否切换到下一个条目
        let x = gestureState.dx
        if (x > 0) {
          // 滑动距离大于屏幕1半,开启动画,滑动到下一个界面,或者滑动速度很快,并且滑动距离大于20,也滑动到下一个条目
          if (x > width / 2 || (this.endTimestamp - this.startTimestamp < 300 && x > 20)) {
            this.page -= 1
          }
          Animated.timing(
              this.state.sports,
              {
                  toValue: -this.page * width,
                  duration: 200
              }
          ).start((state) => {
            // 动画完成,判断是否需要重置位置
            if (state.finished) {
              if (this.page <= 0) {
                this.page = 3
                this.setState({
                  sports: new Animated.Value(-3 * width)
                })
              }
            }
          });
        } else {
          x = Math.abs(x)
          // 滑动距离大于屏幕1半,开启动画,滑动到下一个界面,或者滑动速度很快,并且滑动距离大于20,也滑动到下一个条目
          if (x > width / 2 || (this.endTimestamp - this.startTimestamp < 300)) {
            this.page += 1
          }
          Animated.timing(
              this.state.sports,
              {
                  toValue: -this.page * width,
                  duration: 200
              }
          ).start((state) => {
            // 动画完成,判断是否需要重置位置
            if (state.finished) {
              if (this.page >= 4) {
                this.page = 1
                this.setState({
                  sports: new Animated.Value(-width * this.page)
                })
              }
            }
          });
        }
      },
      onShouldBlockNativeResponder: (evt, gestureState) => {
        return false
      }
    })
  }
  render(){
    return (
        <Animated.View
          style={{...this.props.style, left:this.state.sports}}
          {...this._panResponder.panHandlers}
        >
            {this.props.children}
        </Animated.View>
    );
  }
}

export default class App extends Component {
  render() {
    return (
      <View style={[{width:width,height:'100%'}]}>
        <SwiperView style={{width:width * 4,height:'100%',flexDirection:"row"}}>
              <Image source={require('./assets/3.jpeg')} style={[{width,height:'100%',backgroundColor:"#FFF"}]} />
              <Image source={require('./assets/1.jpeg')} style={[{width,height:'100%',backgroundColor:"red"}]} />
              <Image source={require('./assets/2.jpeg')} style={[{width,height:'100%',backgroundColor:"green"}]} />
              <Image source={require('./assets/3.jpeg')} style={[{width,height:'100%',backgroundColor:"#FFF"}]} />
              <Image source={require('./assets/1.jpeg')} style={[{width,height:'100%',backgroundColor:"red"}]} />
        </SwiperView>
      </View>
    );
  }
}

总结

1、这只是实现需求的第一步,后续会继续优化、封装,达到想要的效果
2、如果只想做banner轮播图展示,将手势那一块替换为setInterval就可以了。
3、如果有更好的思路欢迎交流

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明AI阅读 16,054评论 3 119
  • 从今年五月开始抽空学习心蓝老师的彩铅课,一转眼第八课了。 这个大樱桃画了两次,第一次线稿弄脏了,看着很不舒服,今天...
    vevine阅读 2,720评论 2 2
  • 谁都是被上帝咬过一口的苹果,我希望你能拥抱我无法摒弃的不完美,是这些短处与长处共有的一个我,才刚好来到你身边。
    瑞妞阅读 1,607评论 0 0
  • 14天一闪而过,回想起来,依然记得当时那份既忐忑又兴奋的心情。 知道小白训练营,是看到babe的公号推送,正是自己...
    小太阳_6a9e阅读 3,843评论 1 18
  • 这幅画好有意思呀,你的作画顺序是房子、树木和人,那个蹲下来拍照的是你,据我所知,你现在好像还没有男朋友,那这就是对...
    老孙家的大彬阅读 3,836评论 1 0

友情链接更多精彩内容