遇到一个需求,需要做到一个2行列表的滑动,并且要求有底部的指示器,实际就是一个 banner ,由于原本代码是每行4个,分成2行,无法滚动,现在要在原有基础上改造成一行5个可滚动,故而没有大动代码,只是在外层嵌套 ScrollView
,利用滚动的属性模拟出了 banner 的效果:
方案的主要思路在于 ScrollView 的滚动监听,每次滚动开始之前,通过 onScrollBeginDrag
方法初始化总的偏移量:
onScrollBeginDrag = {event => {
this.setState({
totalOffset:0
})
}
}
在滚动过程中,滚动开始前onScrollBeginDrag
执行,然后可能有一个或多个 onScroll
执行,拖拽动作结束之后会执行 onScrollEndDrag
,但是此时 onScroll
还可能执行,因为拖拽完成之后页面可能因为惯性继续滑。
关于 React-Native
触摸事件的生命周期,这里有一篇文章可以了解下:
https://blog.csdn.net/liu__520/article/details/53676834
通过 event.nativeEvent.contentOffset.x
拿到当前一个 onScroll 的偏移量,
因为在 onScrollEndDrag
后,仍旧有 onScroll
事件,故而没有使用 onScrollEndDrag
中判断累计滑动的方式来实现,而是在 onScroll
中计算。
通过拿取当前onScroll
中的event.nativeEvent.contentOffset.x
叠加到总偏移量中,如果总偏移量(多个 onScroll 偏移量的总和) 超过单个 item宽度或者单个 onScroll 偏移量大于 item宽度,代表滑动到新的页面了,需要更新底部圆点指示器的颜色(通过改变state中curPageIndex
的值):
onScroll = { event => {
// 拿到当前一次滚动的x偏移量
const currentOffset = event.nativeEvent.contentOffset.x;
const dif = currentOffset - (this.offset || 0);
//由于一次滚动可能有多个onScroll事件,所以需要拿到当前总的偏移量,去计算新的总偏移量
let curTotal = this.state.totalOffset
// 一屏有4个item,itemWidth等于1/4的屏幕宽度
let itemWith = deviceWidth/4
if (dif < 0) {//小于0,代表往右滑,内容左移,页数-1
let total = curTotal - dif
this.setState({
totalOffset:curTotal-dif
})
//如果一次滑动里,所有的偏移量大于item宽度;或者单个偏移量大于item宽度,则更新页面index
if (total >= itemWith || currentOffset>=itemWith) {
this.setState({
curPageIndex:0
})
}
} else { //大于0,代表左滑,内容右移,页数+1
let total = curTotal + dif
this.setState({
totalOffset:curTotal+dif
})
if (total >= itemWith || currentOffset>= itemWith) {
this.setState({
curPageIndex:1
})
}
}
this.offset = currentOffset;
}}
render
代码如下:
render() {
//是否第一页
let isFirstPage = this.state.curPageIndex === 0
let list1 = [] // 第一行的item列表
let list2 = [] // 第二行的item列表
return (
<ScrollView style = {{flex:1}}
horizontal = {true}
onScrollBeginDrag = {event => {
this.setState({
totalOffset:0
})
}
}
onScroll = { event => {
const currentOffset = event.nativeEvent.contentOffset.x;
const dif = currentOffset - (this.offset || 0);
let curTotal = this.state.totalOffset
let itemWith = deviceWidth/4
if (dif < 0) {
let total = curTotal - dif
this.setState({
totalOffset:curTotal-dif
})
if (total >= itemWith || currentOffset>=itemWith) {
this.setState({
curPageIndex:0
})
}
} else {
let total = curTotal + dif
this.setState({
totalOffset:curTotal+dif
})
if (total >= itemWith || currentOffset>= itemWith) {
this.setState({
curPageIndex:1
})
}
}
this.offset = currentOffset;
}}
showsHorizontalScrollIndicator = {false}>
<View style = {{flex:1,flexDirection:'column'}}>
<View style = {{flex:1,flexDirection:'row',flexWrap:'nowrap'}}>
<View style={{flexDirection: 'row',justifyContent:'space-between'}}>
{list1.map((item,index) => {
//第一排 Icon 和文字的 item
return (
<IconItem />
)
})}
View>
View>
<View style = {{flex:1,flexDirection:'row',flexWrap:'nowrap'}}>
<View style={{flexDirection: 'row',justifyContent:'space-between'}}>
{list2.map((item,index) => {
return (
//第二排 Icon 和文字的 item
<IconItem />
)
})}
View>
View>
View>
ScrollView>
<View style = {{flex:1,flexDirection:'row',justifyContent:'center',paddingBottom:10}}>
<View style = {{width:5,height:5,backgroundColor:isFirstPage ? '#999999':'#f4f4f4',borderRadius:2.5,marginRight:3}}/>
<View style = {{width:5,height:5,backgroundColor:isFirstPage ? '#f4f4f4':'#999999',borderRadius:2.5,marginLeft:3}}/>
View>
)
}