原生的应用经常会有页面嵌套列表,滚动列表能够改变列表大小,然后还能支持列表内下拉刷新等功能。看了很多的小程序好像都没有这个功能,难道这个算是原生独享的吗,难道是由于手势冲突无法实现吗,冷静的思考了一下,又看了看小程序的手势文档(文档地址),感觉我又行了。
实现效果如下:
页面区域及支持手势
- 红色的是列表未展开时内容展示,无手势支持
- 绿色部分是控制部分,支持上拉下拉手势,对应展开列表及收起列表
- 蓝色列表部分,支持上拉下拉手势,对应展开列表,上拉下拉刷新等功能
- 浅蓝色部分是展开列表后的小界面内容展示,无手势支持
原理实现
主要是根据事件系统的事件来自行处理页面应当如何响应,原理其实同原生的差不多。
主要涉及 touchstart、touchmove、touchend、touchcancel 四个
另外的scrollview的手势要借助于 scroll-y、refresher-enable 属性来实现。
之后便是稀疏平常的数学加减法计算题环节。根据不同的内容点击计算页面应当如何绘制显示。具体的还是看代码吧,解释起来又要吧啦吧啦了。
Talk is cheap, show me the code
代码部分
wxml
<!--index.wxml-->
<view>
<view class="header" style="opacity: {{headerOpacity}};height:{{headerHeight}}px;"></view>
<view
class="toolbar"
data-type="toolbar"
style="bottom: {{scrollHeight}}px;height:{{toolbarHeight}}px;"
catch:touchstart="handleToolbarTouchStart"
catch:touchmove="handleToolbarTouchMove"
catch:touchend="handleToolbarTouchEnd"
catch:touchcancel="handleToolbarTouchEnd"></view>
<scroll-view
class="scrollarea"
type="list"
scroll-y="{{scrollAble}}"
refresher-enabled="{{scrollAble}}"
style="height: {{scrollHeight}}px;"
bind:touchstart="handleToolbarTouchStart"
bind:touchmove="handleToolbarTouchMove"
bind:touchend="handleToolbarTouchEnd"
bind:touchcancel="handleToolbarTouchEnd"
bindrefresherrefresh="handleRefesh"
refresher-triggered="{{refreshing}}"
>
<view class="item" wx:for="{{[1,2,3,4,5,6,7,8,9,0,1,1,1,1,1,1,1]}}">
</view>
</scroll-view>
<view
class="mini-header"
style="height:{{miniHeaderHeight}}px;"
wx:if="{{showMiniHeader}}">
</view>
</view>
ts
// index.ts
// 获取应用实例
const app = getApp<IAppOption>()
Component({
data: {
headerOpacity: 1,
scrollHeight: 500,
windowHeight: 1000,
isLayouting: false,
showMiniHeader: false,
scrollAble: false,
refreshing: false,
toolbarHeight: 100,
headerHeight: 400,
miniHeaderHeight: 200,
animationInterval: 20,
scrollviewStartY: 0,
},
methods: {
onLoad() {
let info = wx.getSystemInfoSync()
this.data.windowHeight = info.windowHeight
this.setData({
scrollHeight: info.windowHeight - this.data.headerHeight - this.data.toolbarHeight
})
},
handleToolbarTouchStart(event) {
this.data.isLayouting = true
let type = event.currentTarget.dataset.type
if (type == 'toolbar') {
} else {
this.data.scrollviewStartY = event.touches[0].clientY
}
},
handleToolbarTouchEnd(event) {
this.data.isLayouting = false
let top = this.data.windowHeight - this.data.scrollHeight - this.data.miniHeaderHeight - this.data.toolbarHeight
if (top > (this.data.headerHeight - this.data.miniHeaderHeight) / 2) {
this.tween(this.data.windowHeight - this.data.scrollHeight, this.data.headerHeight + this.data.toolbarHeight, 200)
} else {
this.tween(this.data.windowHeight - this.data.scrollHeight, this.data.miniHeaderHeight + this.data.toolbarHeight, 200)
}
},
handleToolbarTouchMove(event) {
if (this.data.isLayouting) {
let type = event.currentTarget.dataset.type
if (type=='toolbar') {
this.updateLayout(event.touches[0].clientY + this.data.toolbarHeight / 2)
} else {
if (this.data.scrollAble) {
return
} else {
this.updateScrollViewLayout(event.touches[0].clientY)
}
}
}
},
handleRefesh() {
let that = this
setTimeout(() => {
that.setData({
refreshing: false
})
}, 3000);
},
updateLayout(top: number) {
if (top < this.data.miniHeaderHeight + this.data.toolbarHeight) {
top = this.data.miniHeaderHeight + this.data.toolbarHeight
} else if (top > this.data.headerHeight + this.data.toolbarHeight) {
top = this.data.headerHeight + this.data.toolbarHeight
}
let opacity = (top - (this.data.miniHeaderHeight + this.data.toolbarHeight)) / (this.data.miniHeaderHeight + this.data.toolbarHeight)
let isReachTop = opacity == 0 ? true : false
this.setData({
scrollHeight: this.data.windowHeight - top,
headerOpacity: opacity,
showMiniHeader: isReachTop,
scrollAble: isReachTop
})
},
updateScrollViewLayout(offsetY: number) {
let delta = offsetY - this.data.scrollviewStartY
if (delta > 0) {
return
}
delta = -delta
if (delta > this.data.headerHeight - this.data.miniHeaderHeight) {
delta = this.data.headerHeight - this.data.miniHeaderHeight
}
let opacity = 1 - (delta) / (this.data.headerHeight - this.data.miniHeaderHeight)
let isReachTop = opacity == 0 ? true : false
this.setData({
scrollHeight: this.data.windowHeight - this.data.headerHeight - this.data.toolbarHeight + delta,
headerOpacity: opacity,
showMiniHeader: isReachTop,
scrollAble: isReachTop
})
},
tween(from: number, to: number, duration: number) {
let interval = this.data.animationInterval
let count = duration / interval
let delta = (to-from) / count
this.tweenUpdate(count, delta, from)
},
tweenUpdate(count: number, delta: number, from: number) {
let interval = this.data.animationInterval
let that = this
setTimeout(() => {
that.updateLayout(from + delta)
if (count >= 0) {
that.tweenUpdate(count-1, delta, from + delta)
}
}, interval);
}
},
})
less
/**index.less**/
.header {
height: 400px;
background-color: red;
}
.scrollarea {
position: fixed;
left: 0;
right: 0;
bottom: 0;
background-color: blue;
}
.toolbar {
height: 100px;
position: fixed;
left: 0;
right: 0;
background-color: green;
}
.mini-header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 200px;
background-color: cyan;
}
.item {
width: 670rpx;
height: 200rpx;
background-color: yellow;
margin: 40rpx;
}