React Native 無限循環(huán)輪播思路一

對于React Native,我想說入坑需謹慎

背景

最近做項目中有一個類似今日頭條小視頻左右滑動可以切換小視頻的需求。對于這個需求如何實現,我首先想到的是用FlatList去解決,但是FlatList擴展性很差,不太適合。然后我想到了VirtualizedList去實現,想了想還是很麻煩,項目很急,自己去一點點寫來不及。如果是用ScrollView去實現此功能,倒是比較容易,但是考慮到列表的數據量可能是成百上千條數據,即使再優(yōu)化,數據量一多,App肯定卡的動不了。后來我發(fā)現react-native-swiper這個庫,它有針對左右滑動長列表的優(yōu)化,嘗試了一下還可以,然后就使用了。功能很快完成了,但是當數據量達到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、這只是實現需求的第一步,后續(xù)會繼續(xù)優(yōu)化、封裝,達到想要的效果
2、如果只想做banner輪播圖展示,將手勢那一塊替換為setInterval就可以了。
3、如果有更好的思路歡迎交流

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 1、通過CocoaPods安裝項目名稱項目信息 AFNetworking網絡請求組件 FMDB本地數據庫組件 SD...
    陽明AGI閱讀 16,003評論 3 119
  • 從今年五月開始抽空學習心藍老師的彩鉛課,一轉眼第八課了。 這個大櫻桃畫了兩次,第一次線稿弄臟了,看著很不舒服,今天...
    vevine閱讀 339評論 2 2
  • 誰都是被上帝咬過一口的蘋果,我希望你能擁抱我無法摒棄的不完美,是這些短處與長處共有的一個我,才剛好來到你身邊。
    瑞妞閱讀 225評論 0 0
  • 14天一閃而過,回想起來,依然記得當時那份既忐忑又興奮的心情。 知道小白訓練營,是看到babe的公號推送,正是自己...
    小太陽_6a9e閱讀 673評論 1 18
  • 這幅畫好有意思呀,你的作畫順序是房子、樹木和人,那個蹲下來拍照的是你,據我所知,你現在好像還沒有男朋友,那這就是對...
    老孫家的大彬閱讀 638評論 1 0