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'
  }
})
相关文章
相关标签/搜索