針對猴急一些的同窗,能夠先在這個 Expo網站在線運行下demo看看效果 。javascript
完整的代碼,在 Github倉庫 。html
下拉刷新,是一個很常見的交互方式。React-Native(如下簡稱RN)內置的 FlatList
是支持下拉刷新組件的,經過設置 refreshControl 屬性便可。一般咱們不只僅須要定製下拉組件,還須要在下拉過程當中,下拉組件執行一些動畫,好比在咱們場景下,公司logo會隨着下拉的幅度,不一樣的筆畫仍是顯現出顏色。這就須要咱們的下拉組件,知道當前下拉的幅度,以此來計算咱們動畫執行的進度。顯然,RN官方的 refreshControl
並不能知足咱們的需求。前端
看到有兩個已經存在的開源包 react-native-pull-refresh 和 react-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.refreshTriggerHeight
和 props.refreshingHoldHeight
都是可選的,若是用戶不傳,默認爲 props.headerHeight
。
上面其實還省略了一些工做,最重要的,就是在容器下拉過程當中,怎麼把下拉距離(下拉進度)傳給用戶的自定義 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 。
最後,據說,微交互動畫,使用 lottie 和 RN 更配哦。
原本想嘗試用 AE 作一個公司logo的 lottie
動畫的,奈何沒hold住……
完整的代碼在github上:github.com/sophister/r… 。
人人貸大端技術博客中心
最後廣而告之。 歡迎訪問 人人貸大前端技術博客中心
裏面有關 nodejs
react
react native
小程序 前端工程化等相關的技術文章陸續更新中,歡迎訪問和吐槽~