上一章内容:关于我如何艰难地在公司业务中实现侧滑置顶删除功能(一)
原代码不再贴出。
新的工程需求
上次算是做出来一个像样的侧滑置顶删除功能了,在微信小程序调试中以及安卓机上调试都没有问题,但在iOS测试机上运行时却出现了问题:
不知道是因为这个APP在iOS端上的一个整体功能还是微信小程序原生的触发,在右滑取消菜单的时候会触发一个返回到上级页面的操作……着实是让人不解。理论上,iOS原生的返回手势只有在手指接近屏幕左侧且右滑的时候才会触发。事实上,这个APP的另一个模块也有类似的侧滑删除功能,且右滑该条消息取消菜单时不会触发返回。于是mentor带我去请教了负责这块的iOS开发大哥,他表示:可以尝试在右滑取消时接管手势,但是微信小程序如何实现他也不知道。
初步想法:阻止手势穿透
微信小程序的事件有着冒泡机制,而手势则是整个窗口的一些事件(具体事件为tap, touchstart, touchmove, touchend, touchcancel等)。在wxml代码中进行事件捕捉时,需要设定组件的属性catchtap/bindtap, catchtouchstart/bindtouchstart等,这些都是用来捕捉微信小程序原生事件的属性。catch和bind的区别则是,catch会阻止事件冒泡,而bind不会。也就是说,只要设置catchtap,tap事件就会被局限在这个组件上,在它之后可能会收到事件的父组件、子组件都无法收到这个事件了。所以,如果对消息条组件设置catchtap,会不会就能够阻止页面返回呢?
尝试:将bind换成catch
index.wxml1 2 3
| <view wx:for="{{dataList}}" data-index="{{index}}" class="item {{item.isTouchMove ? 'touch-move-active' : ''}}" catchtouchstart="touchStart" catchtouchmove="touchMove"> <!-- 内容 --> </view>
|
可以是可以阻止滚动,但结果带来了其他的麻烦,因为catch也阻止了上下滑动触发页面滚动的手势,所以如果有很多内容,手势会被限制在某条组件内,页面就动不了了。
尝试:使bind和catch的事件根据isTouchMove进行变化
index.wxml1 2 3 4
| <view wx:for="{{dataList}}" data-index="{{index}}" class="item {{item.isTouchMove ? 'touch-move-active' : ''}}" catchtouchstart="{{item.isTouchMove ? 'touchstart' : ''}}" catchtouchmove="{{item.isTouchMove ? 'touchmove' : ''}}" bindtouchstart="{{item.isTouchMove ? '' : 'touchstart'}}" bindtouchmove="{{item.isTouchMove ? '' : 'touchmove'}}"> <!-- 内容 --> </view>
|
但是没有用,微信小程序并不能同时使用bind和catch两种属性,只会取其一。之后想想,这样的思路也不对,传空的事件名并不会使得事件解绑,而是引发了一个空的函数。
尝试:建立两个一模一样但分别是bind和catch的组件,然后根据isTouchMove的值选择显示哪一个
index.wxml1 2 3 4 5 6 7 8 9 10
| <block wx:for="{{dataList}}" data-index="{{index}}"> <!-- 一个是没有被打开菜单的(支持上下滑动和返回) --> <view wx:if="{{!item.isTouchMove}}" class="item {{item.isTouchMove ? 'touch-move-active' : ''}}" bindtouchstart="{{item.isTouchMove ? '' : 'touchstart'}}" bindtouchmove="{{item.isTouchMove ? '' : 'touchmove'}}"> <!-- 内容 --> </view> <!-- 被打开菜单的(阻止上下滑动和返回) --> <view wx:else class="item {{item.isTouchMove ? 'touch-move-active' : ''}}" catchtouchstart="{{item.isTouchMove ? 'touchstart' : ''}}" catchtouchmove="{{item.isTouchMove ? 'touchmove' : ''}}"> <!-- 内容 --> </view> </block>
|
效果是可行,但是会牺牲滑出和收起菜单的动画效果,因为通过css实现的动画效果只能在同一个组件中进行。
最终方案:建立两个一模一样但分别是bind和catch的组件,在每次动画结束后悄悄替换
绞尽脑汁之后,突然灵机一动想到了这样一个比较邪教的方法。
思路:设置一个hidden属性(用来标记被划开菜单的条目,即需要设成catch的条目的index),在每次动画开始时进行setTimeOut设定延时,等待动画完毕后渲染hidden,根据hidden的值悄悄将所有条目替换成catch或bind的组件。
注意:css中,动画设定是0.4s,延时应当适当小于这个值。
index.wxml1 2 3 4 5 6 7 8 9 10
| <block wx:for="{{dataList}}" data-index="{{index}}"> <!-- 一个是没有被打开菜单的(支持上下滑动和返回) --> <view wx:if="{{index!==hidden}}" class="item {{item.isTouchMove ? 'touch-move-active' : ''}}" bindtouchstart="{{item.isTouchMove ? '' : 'touchstart'}}" bindtouchmove="{{item.isTouchMove ? '' : 'touchmove'}}"> <!-- 内容 --> </view> <!-- 被打开菜单的(阻止上下滑动和返回) --> <view wx:else class="item {{item.isTouchMove ? 'touch-move-active' : ''}}" catchtouchstart="{{item.isTouchMove ? 'touchstart' : ''}}" catchtouchmove="{{item.isTouchMove ? 'touchmove' : ''}}"> <!-- 内容 --> </view> </block>
|
index.js1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| data: { dataList: [], startX: 0, startY: 0, hidden: null, },
touchStart(e) { let dataList = this.data.dataList; dataList.forEach(item => { if (item.isTouchMove) { item.isTouchMove = !item.isTouchMove; } }); this.setData({ dataList: dataList, });
this.data.hidden = null;
this.data.startX = e.touches[0].clientX; this.data.startY = e.touches[0].clientY; }, touchMove(e) { let moveX = e.changedTouches[0].clientX, moveY = e.changedTouches[0].clientY, curIndex = e.currentTarget.dataset.index, dataList = this.data.dataList, angle = this.angle( { X: this.data.startX, Y: this.data.startY }, { X: moveX, Y: moveY } ); const that = this; dataList.forEach((item, index) => { if (curIndex === index && angle < 30 && moveX < this.data.startX) { item.isTouchMove = true; that.data.hidden = index; } else { item.isTouchMove = false; } });
this.setData({ dataList: dataList, });
setTimeOut({ that.setData({ hidden: that.data.hidden }) }, 300); },
|
经检验,如果动画设在0.3s,对于快速滑动也能比较好地适应。既保证了动画的流畅性,又保证了接管手势,不触发意外返回。
总结
至此,这个功能总算是顺利完成。虽然性能问题可能有待商榷(因为要进行大量的if else),但是目前来说是没有想出更好的解决方法。我感觉这个功能对我来说还是比较有锻炼性的,如果另辟蹊径也能达到目的的话,不妨大胆尝试。如有更好的想法,欢迎在评论区留言。