ReactNative: 使用Animted API實現向上滾動時隱藏Header組件

想先推薦一下近期在寫的一個React Native項目,名字叫 Gakki :是一個 Mastodon的第三方客戶端 (Android App)

預覽html

rn.gif

寫在前面


原本我也不想造這個輪子的,奈何沒找到合適的組件。只能本身上了~react

思路很清楚: 監聽滾動事件,動態修改Header組件和Content組件的top值(固然,他們默認都是position:relative)。git

接下來實現的時候遇到了問題,我第一個版本是經過動態設置state來實現,即:github

/**
 * 每次滾動時,從新設置headerTop的值
 */
onScroll = event =>{
    const y = event.nativeEvent.contentOffset.y
    if (y >= 270) return
    // headerTop便是Header和Content的top樣式對應的值
    this.setState({
        headerTop: y
    })
}

這樣雖然能實現,可是效果很差:明顯能夠看到在上滑的過程當中,Header組件一卡一卡地向上方移動(一點都不流暢)。react-native

由於就只能另尋他法了:動畫api

React Native 提供了兩個互補的動畫系統:用於建立精細的交互控制的動畫 Animated和用於全局的佈局動畫 LayoutAnimation (筆者注:此次沒有用到它)

Animated 相關API介紹


首先,這兒有一個簡單「逐漸顯示」動畫的DEMO,須要你先看完(文檔很簡單明瞭且註釋清楚,不必Copy過來)。函數

在看懂了DEMO的基礎上,咱們還須要瞭解兩個關鍵的API才能實現完整的效果:佈局

1. interpolateflex

插值函數。用來對不一樣類型的數值作映射處理。動畫

固然,這是文檔說明:

Each property can be run through an interpolation first. An interpolation maps input ranges to output ranges, typically using a linear interpolation but also supports easing functions. By default, it will extrapolate the curve beyond the ranges given, but you can also have it clamp the output value.

翻譯:

每一個屬性能夠先通過插值處理。插值對輸入範圍和輸出範圍之間作一個映射,一般使用線性插值,但也支持緩和函數。默認狀況下,若是給定數據超出範圍,他也能夠自行推斷出對於的曲線,但您也可讓它箝位輸出值(P.S. 最後一句可能翻譯錯誤,由於沒搞懂clamp value指的是什麼, sigh...)

舉個例子:

在實現一個圖片旋轉動畫時,輸入值只能是這樣的:

this.state = {
  rotate: new Animated.Value(0) // 初始化用到的動畫變量
}

...

// 這麼映射是由於style樣式須要的是0deg這樣的值,你給它0這樣的值,它可不能正常工做。由於一定須要一個映射處理。
this.state.rotate.interpolate({ // 將0映射成0deg,1映射成360deg。固然中間的數據也是如此映射。
  inputRange: [0, 1],
  outputRange: ['0deg', '360deg']
})

2. Animated.event

通常動畫的輸入值都是默認設定好的,好比前面DEMO中的逐漸顯示動畫中的透明度:開始是0,最後是1。這是已經寫死了的。

但若是有些動畫效果須要的不是寫死的值,而是動態輸入的呢,好比:手勢(上滑、下滑,左滑,右滑...)、其它事件。

那就用到了Animated.event

直接看一個將滾動事件的y值(滾動條距離頂部高度)和咱們的動畫變量綁定起來的例子:

// 這段代碼表示:在滾動事件觸發時,將event.nativeEvent.contentOffset.y 的值動態綁定到this.state.headerTop上
// 和最前面我經過this.setState動態設置的目的同樣,但交給Animated.event作就不會形成視覺上的卡頓了。
onScroll={Animated.event([
   {
      nativeEvent: {
        contentOffset: { y: this.state.headerTop }
      }
   }
])}

關於API更多的說明請移步文檔

完整代碼


import React, { Component } from 'react'
import { StyleSheet, Text, View, Animated, FlatList } from 'react-native'

class List extends Component {
  render() {
    // 模擬列表數據
    const mockData = [
      '富強',
      '民主',
      '文明',
      '和諧',
      '自由',
      '平等',
      '公正',
      '法治',
      '愛國',
      '敬業',
      '誠信',
      '友善'
    ]

    return (
      <FlatList
        onScroll={this.props.onScroll}
        data={mockData}
        renderItem={({ item }) => (
          <View style={styles.list}>
            <Text>{item}</Text>
          </View>
        )}
      />
    )
  }
}

export default class AnimatedScrollDemo extends Component {
  constructor(props) {
    super(props)
    this.state = {
      headerTop: new Animated.Value(0)
    }
  }

  componentWillMount() {
    // P.S. 270,217,280區間的映射是告訴interpolate,全部大於270的值都映射成-50
    // 這樣就不會致使Header在上滑的過程當中一直向上滑動了
    this.top = this.state.headerTop.interpolate({
      inputRange: [0, 270, 271, 280],
      outputRange: [0, -50, -50, -50]
    })

    this.animatedEvent = Animated.event([
      {
        nativeEvent: {
          contentOffset: { y: this.state.headerTop }
        }
      }
    ])
  }

  render() {
    return (
      <View style={styles.container}>
        <Animated.View style={{ top: this.top }}>
          <View style={styles.header}>
            <Text style={styles.text}>linshuirong.cn</Text>
          </View>
        </Animated.View>
        {/* 在oHeader組件上移的同時,列表容器也須要同時向上移動,須要注意。 */}
        <Animated.View style={{ top: this.top }}>
          <List onScroll={this.animatedEvent} />
        </Animated.View>
      </View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1
  },
  list: {
    height: 80,
    backgroundColor: 'pink',
    marginBottom: 1,
    alignItems: 'center',
    justifyContent: 'center',
    color: 'white'
  },
  header: {
    height: 50,
    backgroundColor: '#3F51B5',
    alignItems: 'center',
    justifyContent: 'center'
  },
  text: {
    color: 'white'
  }
})
相關文章
相關標籤/搜索