React-Native 实现列表顶部随滑动渐变效果

概要

本篇文章的内容在于通过React-Native实现一个列表顶部随滑动渐变的效果。

一开始使用state来保存顶部需要变动样式的值,后来发现由于RN对state状态改变时的处理机制,导致页面呈现的效果不具实时性且不太流畅,所以改进使用了RN自己提供的Animated动画和ScrollView滑动组件。

顶部view区域颜色随滑动渐变

在这里插入图片描述

通过Animated自己附带的事件nativeEvent获取ScrollView的滑动距离

	//声明滑动动画事件
    this.animatedEvent = Animated.event(
      [
        {
          nativeEvent: {
            contentOffset: { y: this.state.navBackColorOffset }  //将y值保存到state中背景色偏移距离的值上
          }
        }
      ]
    )

通过state中保存的偏移距离navBackColorOffset,映射到动画中,定义输入区间和输出区间的值(设置useNativeDriver为true,使用原生驱动提升效率)。

	//生成背景色动画输入输出区间
    const navBackColorOffset = this.state.navBackColorOffset
    this.backgroundAnimated = navBackColorOffset.interpolate({
      inputRange: [0, this.changeNavHeight],
      outputRange: ['#F2F740', '#FA5D4D'],
      extrapolate: 'clamp',  //阻止输出值超过outputRange
      useNativeDriver: true,
    })

当ScrollView开始滑动后,调用onScroll中绑定的this.animatedEvent方法,记录偏移量并保存到state中。

	<ScrollView 
		style={{ flex:1, width:width }}
		scrollEventThrottle={1}
		onScroll={this.animatedEvent}  //滑动监听事件
		showsVerticalScrollIndicator={false}
	>
		...
	</ScrollView>

将this.backgroundAnimated的输出赋值给顶部区域的背景色样式。

        {/* 顶部背景色 */}
        <Animated.View
          style={{
              backgroundColor:this.backgroundAnimated
		  }}
        >
          ...
        </Animated.View>

顶部图片透明度随滑动渐变

在这里插入图片描述

实现原理同上,只不过将View组件替换为Image组件显示,改变的是Image组件的透明度。

	//声明滑动动画事件
    this.animatedEvent = Animated.event(
      [
        {
          nativeEvent: {
            contentOffset: { y: this.state.navOpacityOffset }  //将y值保存到state中透明度偏移距离的值上
          }
        }
      ]
    )

通过state中保存的偏移距离navOpacityOffset,映射到动画中,定义输入区间和输出区间的值。

    //生成透明度动画输入输出区间
    const navOpacityOffset = this.state.navOpacityOffset
    this.navOpacityAnimated = navOpacityOffset.interpolate({
      inputRange: [0, this.changeNavHeight],
      outputRange: [0, 1],
      extrapolate: 'clamp',  //阻止输出值超过outputRange
      useNativeDriver: true,
    })

将this.navOpacityAnimated的输出赋值给顶部区域的透明度。

        {/* 顶部图片透明度 */}
        <Animated.Image
          style={{ 
		      opacity:this.navOpacityAnimated
          }}
          source={require('../../images/house/pic_topbg.png')}  //这里替换成你自己的图片路径
        />

滑动监听

在源码中,你会发现在滑动事件animatedEvent中,有这样一个监听:

      {
        listener: e => {
          //获取ScrollView在y轴上的偏移量
          let y = e.nativeEvent.contentOffset.y;
          if( y > this.changeNavHeight/2 ) {
            //超过设定滑动距离1/2时,状态栏样式为浅色
            StatusBar.setBarStyle('light-content');
          }else{
            //反之,状态栏样式为深色
            StatusBar.setBarStyle('dark-content');
          }
        }
      }

ScrollView滑动的过程中,会不断地调用onSroll事件绑定的方法,那么如果我们想在这个方法中,获取到一些其他的参数,或者是做一些事件或动作,该怎么去取值并处理呢?这里提供了listener监听,通过这个监听,我们可以拿到监听对象e,它包含的内容如下:

{
  nativeEvent: {
    contentInset: {bottom, left, right, top},
    contentOffset: {x, y},
    contentSize: {height, width},
    layoutMeasurement: {height, width},
    zoomScale
  }
}

可以看出这些是ScrollView的布局参数等等,那么这样我们不仅可以拿到x,y的偏移量,还可以拿到内容布局的宽高等等,之后再进行一些处理。(在这里我只是加了个判断修改下状态栏的样式)

源码

import React, { Component } from 'react'
import {
    Text,
    Image,
    View,
    StyleSheet,
    ScrollView,
    StatusBar,
    Platform,
    Animated,
    Dimensions,
} from 'react-native';
const { width, height } = Dimensions.get('window');
const isAndroid = Platform.OS === 'android';
const statusBarHeight = StatusBar.currentHeight;
export default class HeaderByScroll extends Component {
  constructor(props) {
    super(props)
    this.changeNavHeight = 150;  //决定改变导航栏样式的滑动距离
    this.state={
      navOpacityOffset: new Animated.Value(0),  //改变导航背景图透明度的偏移距离
      navBackColorOffset: new Animated.Value(0),  //改变导航背景颜色的偏移距离
    }
  }

  UNSAFE_componentWillMount(){
    //设置状态栏颜色为透明(针对Android)
    StatusBar.setBackgroundColor('transparent');
    //声明滑动动画事件
    this.animatedEvent = Animated.event(
      [
        {
          nativeEvent: {
            contentOffset: { y: this.state.navBackColorOffset }  //将y值保存到state中背景色偏移距离的值上
            // contentOffset: { y: this.state.navOpacityOffset }  //将y值保存到state中透明度偏移距离的值上
          }
        }
      ],
      {
        listener: e => {
          //获取ScrollView在y轴上的偏移量
          let y = e.nativeEvent.contentOffset.y;
          if( y > this.changeNavHeight/2 ) {
            //超过设定滑动距离1/2时,状态栏样式为浅色
            StatusBar.setBarStyle('light-content');
          }else{
            //反之,状态栏样式为深色
            StatusBar.setBarStyle('dark-content');
          }
        }
      }
    )
    // 生成背景色动画输入输出区间
    const navBackColorOffset = this.state.navBackColorOffset
    this.backgroundAnimated = navBackColorOffset.interpolate({
      inputRange: [0, this.changeNavHeight],
      outputRange: ['#F2F740', '#FA5D4D'],
      extrapolate: 'clamp',  //阻止输出值超过outputRange
      useNativeDriver: true,
    })
    //生成透明度动画输入输出区间
    // const navOpacityOffset = this.state.navOpacityOffset
    // this.navOpacityAnimated = navOpacityOffset.interpolate({
    //   inputRange: [0, this.changeNavHeight],
    //   outputRange: [0, 1],
    //   extrapolate: 'clamp',  //阻止输出值超过outputRange
    //   useNativeDriver: true,
    // })
  }
  
  render(){
    return(
      <View style={{ flex:1, alignItems:'center' }}>
        {/* 顶部背景色 */}
        <Animated.View
          style={[
            styles.header,
            {
              alignItems:'center',
              backgroundColor:this.backgroundAnimated
            }
          ]}
        >
          <Text style={{fontSize:18,marginTop:isAndroid?statusBarHeight:44}}>滑动渐变导航栏</Text>
        </Animated.View>
        {/* 顶部图片透明度 */}
        {/* <Animated.Image
          style={[
            styles.header,
            { 
              opacity:this.navOpacityAnimated
            }
          ]}
          source={require('../../images/house/pic_topbg.png')}  //这里替换成你自己的图片路径
        /> */}
        <ScrollView 
          style={{ flex:1, width:width }}
          scrollEventThrottle={1}
          onScroll={this.animatedEvent}  //滑动监听事件
          showsVerticalScrollIndicator={false}
        >
          <View style={[styles.itemView,{ height:300, backgroundColor:'#19BEB0' }]}>
            <Text style={{fontSize:20}}>区域A</Text>
          </View>
          <View style={[styles.itemView,{ height:300, backgroundColor:'#F2F740' }]}>
            <Text style={{fontSize:20}}>区域B</Text>
          </View>
          <View style={[styles.itemView,{ height:500, backgroundColor:'#FA5D4D' }]}>
            <Text style={{fontSize:20}}>区域C</Text>
          </View>
        </ScrollView>
      </View>
    )
  }
}

const styles = StyleSheet.create({
  header: {
    height: (isAndroid ? statusBarHeight : 44) + 44,  //顶部适配距离,
    width: width,
    position: 'absolute',
    zIndex: 10,
  },
  itemView: {
    width: width,
    alignItems: 'center',
    justifyContent: 'center',
  },
})

注:本文为作者原创,转载请注明作者及出处。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值