react-native 实现列表吸顶效果
实现列表吸顶效果,这里是使用RN自带组件SectionList自带的特性。
RN 结合 MobX之后的生命周期使用
在说明SectionList实现列表吸顶功能之前,先爆料承接上一篇文章 在使用MobX时候,生命周期问题。在方法componentDidUpdate()中请求后台的方法莫名无故的一直请求数据,页面卡顿!!
就拿上面的Gif效果图来说。因为每进行一次页面的渲染就会调用react的生命周期 componentWillUpdate() 和 componentDidUpdate() 如果我们误把页面数据的请求方法放到了方法componentDidUpdate()中,然后你就会发现,数据请求方法一直不听的在像后台请求数据。页面略显卡顿,因为起初进入到这个生命周期中,调用请求后台数据的方法,请求到之后,会赋值让页面重新渲染,然后有回调用方法componentDidUpdate() 然后就导致了死循环的无限请求和渲染中!解决方案,是在生命周期方法componentDidMount()中处理。
SectionList 在安卓和iOS系统上的差异
`SectionList 的 方法scrollToLocation()
SectionList的这个方法,在使用上iOS平台达到预期效果,但是Android平台则不能!实现的效果是上图Gif图右下角悬浮的一个上箭头,点击页面回滚到顶部。
实现方式也很简单,动态计算SectionList的Header组件View的高度,回调并调用回滚方法scrollToLocation()。像这样
中间去调节这个bug,而且成功了,我改动了下参数去掉了参数viewPosition,然后运行在安卓上成功了。但是后来不知怎么的,又不行了!目前还没有好的解决方案。
`SectionList 的 props属性stickySectionHeadersEnabled
SectionList在使用的时候section都会吸顶停留(像上图Gif,横向的秒杀组件),原因是iOS的系统特性,默认就可以的。但是换做在Android上则不能,直接就像FlatList一样不能实现吸顶效果。原因是在安卓平台,需要我们手动进行设置 stickySectionHeadersEnabled=true。
实现SectionList吸顶效果
看部分实现源码——列表主控件
<SectionList
style={{ flex:1}}
ref={sctionList => (this._sectionList = sctionList)}
keyExtractor={(item, index) => "SectionList" + index}
renderItem={this._renderSectionItem}
stickySectionHeadersEnabled={true}
showsVerticalScrollIndicator={false}
ListHeaderComponent={this._renderHeader}
onEndReachedThreshold={0.01}
onEndReached={this._fetchMoreListData}
onScroll={(ee) => {
this.isShowUpward = ee.nativeEvent.contentOffset.y > 150
// this.isShowUpward = ee.nativeEvent.contentOffset.y > 100; //是否显示返回顶部按钮
this.flatListNum = ee.nativeEvent.contentOffset.y;
if(this.flatListNum > 5 && this.state.colorX == true){
this.setState({colorX:false})
}else if(this.flatListNum <= 5 && this.state.colorX == false){
this.setState({colorX:true})
}
this.bar && this.bar.onScroll(ee);
}}
refreshControl={
<RefreshControl
refreshing={this.isRefreshing}
onRefresh={this._refreshControl}
tintColor="#000"
title={this.refreshTitle}
titleColor="#666"
/>
}
renderSectionHeader={({ section }) => (
<HorizontalScroll
ref={hscroll => (this._hscroll = hscroll)}
scrollTabs={this.state.scrollTabs}
onPress={hindex => {
this.clickIndex = hindex;
//这里响应了用户点击抢购时间按钮,要进行请求数据
this._fetchMoreListData(false);
}}
/>
)}
sections={this.state.sectionListData}
/>
源码中,第42行 传入列表数据,数据结构是这样的。只有一条数据来实现的!
产生下面的效果
SectionLIst本身就有setction吸顶的特点,正好利用这一特点。通过实现SetctionList 的一条数据。
第8行代码实现的是这个列表的Header,
第5行代码实现了列表item单元格元素,
第31行代码实现了要吸顶的view即setcion,使用FlatList作为横向滑动。
实现TitleBar跟随滑动渐变的动画
请看源码片段中的第11行到21行,当向下滑动列表,SetcionList的API回调方法onScroll被调用。即this.bar && this.bar.onScroll(ee);
被调用实现渐变的方法。TitleBar动画组件定义
<AnimatedBackground ref={ref => this.bar = ref} colors={this.bgColors} >
<MyTitleBar />
</AnimatedBackground>
this.bar && this.bar.onScroll(ee);结合 TitleBar动画组件可知我们要进行对该操作的手势处理。
从官方文档来看,想要实现滑动渐变,即让你滑动的距离映射到动画值。在这里的功能实现上,调用this.bar && this.bar.onScroll(ee);
则可以实现。
由此,滑动的距离 就和 this.state.color(即:color=new Animated.Value()
)产生了映射。然后通过插值动画来实现效果,如下代码
<Animated.View style={{
backgroundColor:this.state.color.interpolate({
inputRange: [0, isIphoneX ? 88 : 64],
outputRange: ['#F3474600', '#F34746FF']
})
}}>
{this.props.children}
</Animated.View>
整个实现过程就是
,TitleBar的背景渐变通过插值动画来实现背景的渐变通过在['#F3474600', '#F34746FF']
这两种颜色值之间进行渐变变换。对应渐变滑动值=0,对应是色值#F3474600;滑动值=88或64,对应是色值#F34746FF。而在滑动值0到88之间,色值也在[’#F3474600’, ‘#F34746FF’] 两种颜色间渐变。而插值实现背景的渐变需要this.state.color[color=Animated.Value()]和滑动距离constentOffset 产生映射,即通过方法Animated.event()来实现。而控制映射的时机,则是在列表滑动,通过在回调方法onScroll中调用!
实现TitleBar跟随轮播渐变的动画
而实现,轮播图轮播,TitleBar跟随渐变。从实现角度看,轮播变动是水平切换滚动,滚动距离要与颜色值产生映射。实现代码在x轴:
onScrollX = (e) => {
Animated.event([{
nativeEvent: {
contentOffset: {
x: this.state.colorx
}
}
}])
}
然后使用插值变化实现代码
<Animated.View style={{
backgroundColor: this.state.colorx.interpolate({
inputRange: this.props.colors.map((_, i) => (i) * c.fitPx(710)),
outputRange: this.props.colors
})
}}>
{this.props.children}
</Animated.View>
代码和竖直方向滚动的渐变实现很类似,这个时候只需在轮播组件上调用方法,使水平滚动距离和颜色值产生映射的方法即可。
水平轮播滚动距离和颜色值产出映射,在TitleBar的背景style上进行插值器调用,Animated.View作为背景,在轮播过程中就会执行渐变动画。
完整的TitleBar渐变背景组件实现代码(这个是对双向的渐变动画控制实现)
export class AnimatedBackground extends Component {
state = {
color: new Animated.Value(0),
colorx:new Animated.Value(0)
}
onScrollX = (e) => {
Animated.event([{
nativeEvent: {
contentOffset: {
x: this.state.colorx
}
}
}])
}
onScroll = Animated.event([{
nativeEvent: {
contentOffset: {
y: this.state.color
}
}
}])
render() {
// colorX=是否是水平滑动
return (
<Animated.View style={{
backgroundColor: this.props.colorX ? this.state.colorx.interpolate({
inputRange: this.props.colors.map((_, i) => (i) * c.fitPx(710)),
outputRange: this.props.colors
}):this.state.color.interpolate({
inputRange: [0, isIphoneX ? 88 : 64],
outputRange: ['#F3474600', '#F34746FF']
})
}}>
{this.props.children}
</Animated.View>
)
}
}