原生的应用经常会有页面嵌套列表,滚动列表能够改变列表大小,然后还能支持列表内下拉刷新等功能。看了很多的小程序好像都没有这个功能,难道这个算是原生独享的吗,难道是由于手势冲突无法实现吗,冷静的思考了一下,又看了看小程序的手势文档(文档地址),感觉我又行了。
实现效果如下:
页面区域及支持手势
- 红色的是列表未展开时内容展示,无手势支持
- 绿色部分是控制部分,支持上拉下拉手势,对应展开列表及收起列表
- 蓝色列表部分,支持上拉下拉手势,对应展开列表,上拉下拉刷新等功能
- 浅蓝色部分是展开列表后的小界面内容展示,无手势支持
原理实现
主要是根据事件系统的事件来自行处理页面应当如何响应,原理其实同原生的差不多。
主要涉及 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.windowHeightthis.setData({scrollHeight: info.windowHeight - this.data.headerHeight - this.data.toolbarHeight})},handleToolbarTouchStart(event) {this.data.isLayouting = truelet type = event.currentTarget.dataset.typeif (type == 'toolbar') {} else {this.data.scrollviewStartY = event.touches[0].clientY }},handleToolbarTouchEnd(event) {this.data.isLayouting = falselet top = this.data.windowHeight - this.data.scrollHeight - this.data.miniHeaderHeight - this.data.toolbarHeightif (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.typeif (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 = thissetTimeout(() => {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 : falsethis.setData({scrollHeight: this.data.windowHeight - top,headerOpacity: opacity,showMiniHeader: isReachTop,scrollAble: isReachTop})},updateScrollViewLayout(offsetY: number) {let delta = offsetY - this.data.scrollviewStartYif (delta > 0) {return}delta = -deltaif (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 : falsethis.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.animationIntervallet count = duration / intervallet delta = (to-from) / countthis.tweenUpdate(count, delta, from)},tweenUpdate(count: number, delta: number, from: number) {let interval = this.data.animationIntervallet that = thissetTimeout(() => {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;
}