前言: 一周又过去了,一直在赶需求,除了自己利用空余时间学习一下外压根就没时间去研究新东西,唉~程序猿就是这样,活到老学到老!! 废话不多说了,公司产品觉得某电商的商品详情页面很nice,问我们能不能实现(宝宝心里苦),于是研究了一波,下面把我研究的东西分享一下,小伙伴有啥好的实现方式还谢谢分享一下哦~拜谢!
先看一下最终实现的效果:
ios/android
简单来说就是分为两个部分(上下),两个都是(scrollview、flatlist)等滑动组件,第一个scrollview滑动到底部的时候,继续上拉显示第二个scrollview,第二个scrollview下拉到顶部的时候,继续下拉回到第一个scrollview并且第一个scrollview回到顶部.
下面说一下我的大体思路:
第一种方式:
利用rn手势,然后监听scrollview的滑动事件,我们都知道rn中的事件传递都是从父控件一层一层往下传递,所以父控件能够拦截scrollview也就是子控件的能力,当scrollview滑动到底部或者顶部的时候拦截scrollview的事件,把事件给父控件,然后通过控制父控件的垂直偏移量首先分页功能,说了那么多理论的东西小伙伴估计都累了,我们后面结合代码一起来说一下.
第二种方式:
封装一个native的组件实现上拉和下拉的操作,rn只需要做一些简单的监听就可以.
两种方式对比来看,能用rn实现最好,因为rn存在的目的也就是为了实现跨平台目的,都用native实现了还用rn干嘛!! 话虽然这样说,但是rn还是给开发人员提供了自定义的方法,用第二种方式实现有点就是性能和体验上要优于rn实现,我接下来会结合两种方式来实现.
先说一下第一种方式
第一步(把页面分为上下两部分):
render() {
return (
<View
style={[styles.container]}
>
{/*第一部分*/}
<Animated.View
style={[styles.container1,{
marginTop:this._aniBack.interpolate({
inputRange:[0,1],
outputRange:[0,-SCREEN_H],
})
}]}
{
...this._panResponder.panHandlers}
>
<Animated.View
ref={(ref) => this._container1 = ref}
style={
{
width: SCREEN_W, height: SCREEN_H,
marginTop:this._aniBack1.interpolate({
inputRange:[0,1],
outputRange:[0,-100],
})}}
>
<ScrollView
ref={(ref)=>this._scroll1=ref}
bounces={false}
scrollEventThrottle={10}
onScroll={this._onScroll.bind(this)}
overScrollMode={'never'}
>
{this._getContent()}
</ScrollView>
</Animated.View>
<View style={
{
width:SCREEN_W,height:100,backgroundColor:'white',alignItems:'center',justifyContent:'center'}}>
<Text>上拉查看详情</Text>
</View>
</Animated.View>
{/*第二部分*/}
<View
style={styles.container2}
{
...this._panResponder2.panHandlers}
>
<Animated.View
ref={(ref) => this._container2 = ref}
style={
{
width:SCREEN_W,height:100,backgroundColor:'white',alignItems:'center',justifyContent:'center',
marginTop:this._aniBack2.interpolate({
inputRange:[0,1],
outputRange:[-100,0],
})
}}
>
<Text>下拉回到顶部</Text>
</Animated.View>
<View
style={
{
width: SCREEN_W, height: SCREEN_H,}}
>
<ScrollView
ref={(ref)=>this._scroll2=ref}
bounces={false}
scrollEventThrottle={10}
onScroll={this._onScroll2.bind(this)}
overScrollMode={'never'}
>
{this._getContent()}
</ScrollView>
</View>
</View>
</View>
);
}
代码我待会会贴出来,原理很简单,我大体说一下我的实现思路,运行代码你会发现,页面是显示了一个红色页面(也就是上部分).
让我们把第一个页面的marginTop调为-SCREEN_H(屏幕高度)的时候,我们会看到第二屏蓝色页面
所以我们只需要在第一个红色页面的scrollview滑动到底部的时候,然后拦截事件,手指抬起的时候,让第一个页面的marginTop从(0到-屏幕高度)的转变,我们同时给个动画实现.那么问题来了,我们该怎么监听scrollview到达顶部或者底部呢?我们又该怎么拦截scrollview的事件呢?
监听scrollview到达顶部或者底部:
到达顶部我们都知道,当scrollview的y轴偏移量=0的时候我们就认为scrollview到达顶部了,转为代码就是:
_onScroll2(event){
this._reachEnd2=false;
let y = event.nativeEvent.contentOffset.y;
if(y<=0){
//到达顶部了
this._reachEnd2=true;
}
}
到达底部也就是当(子控件的高度=y轴滑动的距离+父控件的高度)的时候,转为代码为:
_onScroll(event){
this._reachEnd1=false;
let y = event.nativeEvent.contentOffset.y;
let height = event.nativeEvent.layoutMeasurement.height;
let contentHeight = event.nativeEvent.contentSize.height;
if (contentHeight > height && (y + height >= contentHeight)) {
//到达顶部了
this._reachEnd1=true;
}
}
父控件拦截子控件的事件:
我们在onMoveShouldSetPanResponderCapture返回true,父控件就是拦截掉滑动事件,然后交给自己处理(onPanResponderMove),那么我们红色页面(也就是第一页)的scrollview到达底部的时候,再往上拉的时候,我们拦截事件
_handleMoveShouldSetPanResponderCapture(event: Object, gestureState: Object,): boolean {
console.log('_handleMoveShouldSetPanResponderCapture');
console.log(gestureState.dy);
//当滑动到底部并且继续往上拉的时候
return this._reachEnd1&&gestureState.dy<0;
}
我们第二个页面(也就是蓝色页面)当scrollview滑动到顶部并且继续往下拉的时候,拦截事件:
_handleMoveShouldSetPanResponderCapture2(event: Object, gestureState: Object,): boolean {
console.log(gestureState.dy);
console.log('_handleMoveShouldSetPanResponderCapture2');
//当滑动到顶部并且继续往下拉的时候
return this._reachEnd2&&gestureState.dy>=0;
}
好啦~我们第一个页面的父控件拿到滑动事件后,我们继续往上拉,也就是把往上拉的距离赋给我们的“上拉查看详情“组件了:
_handlePanResponderMove(event: Object, gestureState: Object): void {
//防止事件拦截不准,我们把scrollview的scrollEnabled:false设置为false
this._scroll1.setNativeProps({
scrollEnabled:false
})
let nowLeft =gestureState.dy*0.5;
//控制一个页面的“上拉查看详情“组件显示
this._container1.setNativeProps({
marginTop:nowLeft
})
console.log(‘_handlePanResponderMove’,gestureState.dy);
<Animated.View
ref={(ref) => this._container1 = ref}
style={
{
width: SCREEN_W, height: SCREEN_H,
marginTop:this._aniBack1.interpolate({
inputRange:[0,1],
outputRange:[0,-100],
})}}
>
<ScrollView
ref={(ref)=>this._scroll1=ref}
bounces={false}
scrollEventThrottle={10}
onScroll={this._onScroll.bind(this)}
overScrollMode={'never'}
>
{this._getContent()}
</ScrollView>
</Animated.View>
<View style={
{
width:SCREEN_W,height:100,backgroundColor:'white',alignItems:'center',justifyContent:'center'}}>
<Text>上拉查看详情</Text>
</View>
代码很简单,我就不一一解