React Native 自定義下拉刷新組件

React Native 自定義下拉刷新組件 PullToRefresh

針對猴急一些的同窗,能夠先在這個 Expo網站在線運行下demo看看效果javascript

完整的代碼,在 Github倉庫html

下拉刷新,是一個很常見的交互方式。React-Native(如下簡稱RN)內置的 FlatList 是支持下拉刷新組件的,經過設置 refreshControl 屬性便可。一般咱們不只僅須要定製下拉組件,還須要在下拉過程當中,下拉組件執行一些動畫,好比在咱們場景下,公司logo會隨着下拉的幅度,不一樣的筆畫仍是顯現出顏色。這就須要咱們的下拉組件,知道當前下拉的幅度,以此來計算咱們動畫執行的進度。顯然,RN官方的 refreshControl並不能知足咱們的需求。前端

看到有兩個已經存在的開源包 react-native-pull-refreshreact-native-ptr-control ,基本都有2年左右歷史了,並且我也確實沒看懂,爲何要用到 兩個 ScrollView 嵌套來實現。java

直觀上來看,我應該只須要有一個 ScrollView 就能夠了,我監聽下拉距離,從新render自定義的下拉組件。嗯,按照這個思路,嘗試擼一個試試。node

一步步實現自定義下拉

首先,咱們提供的是一個容器(命名爲 PullToRefresh 吧 ),內部是用戶經過 children 傳進來的 FlatList,這樣也方便用戶修改,在須要自定義下拉刷新的場景下,用咱們這個容器把已經存在的 FlatList 包起來就能夠了,改動也挺小。固然,由於是自定義下拉刷新header,確定還須要用戶把自定義的下拉刷新header組件傳進來,就命名爲 props.HeaderComponent 吧,到這一步,咱們容器內render出來的DOM結構,大概是這樣的:react

<View>
  <Animated.View><HeaderComponent /></Animated.View> <FlatList /> </View>
複製代碼

最外層的 View 的展現區域,和用戶本身的 FlatList 徹底同樣。那麼問題來了,咱們的下拉刷新 HeaderComponent 在默認狀況下,應該是不可見的,是在用戶下拉過程當中,逐漸的從上到下進入容器的可視區域。那就默認把 HeaderComponent 絕對定位到容器可視區域的外邊吧,但是往上移動多大呢,這就須要用戶告訴咱們容器一個下拉組件的高度了,props.headerHeight ,到這一步,容器渲染出來的樣式大概以下:git

<View>
  <Animated.View style={{position: 'absolute', top: - this.props.headerHeight}}> <HeaderComponent /> </Animated.View> <FlatList /> </View>
複製代碼

完成了初始的DOM結構樣式,接下來容器下拉時機的問題。github

首先,何時用戶下拉,是觸發咱們容器的下拉操做,而不是內部的 FlatList 的默認下拉呢?這個好像比較直接,當內部的 FlatList 已經下拉到頂部,不能再繼續下拉時,用戶的下拉動做,就應該觸發容器的下拉。那麼,咱們就須要知道內部的 FlatList 的當前下拉位置,這能夠經過 FlatList 的 onScroll 屬性來獲取當前 FlatList 的滾動距離。小程序

什麼時機觸發容器的下拉肯定了,那在容器下拉過程當中,咱們須要更新哪些組件呢?1) 自定義header組件確定要更新,將最新的下拉距離傳給header組件。2) 若是隻是將header組件往下移動,咱們的 FlatList 不動,那麼自定義header會遮擋住 FlatList 的內容,這不是咱們想要的;所以,在容器下拉過程當中,內部的 FlatList 位置也須要響應的往下移動。react-native

若是咱們用容器的 state.containerTop 這個 Animated.Value 來保存當前容器下拉的距離,那麼目前咱們容器render的DOM結果大概以下:

const headerStyle = {
  position: 'absolute',
  left: 0,
  width: '100%',
  top: -this.props.headerHeight,
  transform: [{ translateY: this.state.containerTop }],
};
<View>
  <Animated.View style={[{ flex: 1, transform: [{ translateY: this.state.containerTop }] }]}>
  	<FlatList />
  </Animated.View>
  <Animated.View style={headerStyle}>
  	<HeaderComponent />
  </Animated.View>
</View>
複製代碼

這樣,基本就完成了容器下拉過程當中,自定義header和內部的FlatList同步下拉了。

下拉動做實現了,那下拉到什麼位置,能夠觸發刷新呢?這就須要用戶再傳遞一個觸發刷新的下拉距離,就叫 props.refreshTriggerHeight 吧,當用戶鬆開時,若是當前下拉距離 >= props.refreshTriggerHeight ,就會調用用戶傳入的刷新函數 props.onRefresh 。一般,用戶若是下拉的距離比較大,鬆開手指時觸發了刷新動做,這時候會整個組件會先回跳到一個刷新中的位置,這個位置,用戶能夠經過 props.refreshingHoldHeight 來指定。props.refreshTriggerHeightprops.refreshingHoldHeight 都是可選的,若是用戶不傳,默認爲 props.headerHeight

One More Thing

上面其實還省略了一些工做,最重要的,就是在容器下拉過程當中,怎麼把下拉距離(下拉進度)傳給用戶的自定義 HeaderComponent ?上面容器上的 state.containerTop 其實就是當前容器下拉距離,只不過這是一個 Animated.Value ,咱們 不能 讀取到它當前的值。所以,我在容器上添加了一個 實例屬性 this.containerTranslateY 來保存當前容器下拉的距離,咱們會監聽 state.containerTop 值的變化,在回調函數裏,修改 this.containerTranslateY

等等!!containerTranslateY爲何沒有放到容器的 state 上呢?不該該是 this.state.containerTranslateY 麼??嗯,剛開始我確實是放在 state 上的,而後在用戶下拉容器過程當中,經過在容器上 setState,觸發容器從新render,而後把 containerTranslateY 傳遞過header。可是,這樣經過容器上 setState 觸發header更新的方式,在我測試中,發現頁面會比較卡頓。所以,在用戶下拉容器過程當中,並沒有去修改容器的 state ,而是經過 方法調用 的命令方式,將用戶當前下拉距離傳給了header組件。這裏可能還能夠怎麼優化一下吧。I'm not sure.

所以,用戶的自定義header組件,必須 暴露一個實例方法 setProgress 來接收容器下拉過程當中的一些參數,目前這個方法的簽名是這樣的:

// pullDistance 表示容器下拉的距離;percent 表明下拉的進度,[0, 1]
setProgress({pullDistance, percent}){}
複製代碼

完整的 header 組件demo,請參考 expo上的運行demo

The End

最後,據說,微交互動畫,使用 lottie 和 RN 更配哦。

原本想嘗試用 AE 作一個公司logo的 lottie 動畫的,奈何沒hold住……

完整的代碼在github上:github.com/sophister/r…

相關連接

廣告時間

人人貸大端技術博客中心

最後廣而告之。 歡迎訪問 人人貸大前端技術博客中心

裏面有關 nodejs react react native 小程序 前端工程化等相關的技術文章陸續更新中,歡迎訪問和吐槽~

相關文章
相關標籤/搜索