小程序新的手势系统开发可拖动的悬浮按钮

811 阅读2分钟

最近踩坑微信小程序skyline模式,新版手势系统,怎么说呢,还是很好用的,相比于之前在视图层通过wxs去拦截手势操作的垃圾API,手势系统可简单多了,做了一个下面的悬浮按钮的例子

  • 支持随意拖拽
  • 点击弹出子菜单
  • 触发拖拽前回收子菜单

源码关注小程序 miaoui 获取

选择手势系统

有下列几种手势系统对应不同需求

  • double-tap-gesture-handler 双击
  • force-press-gesture-handler 强击
  • horizontal-drag-gesture-handler 水平方向
  • long-press-gesture-handler 长按
  • pan-gesture-handler 任意方向
  • scale-gesture-handler 多指操作
  • tap-gesture-handler 点击
  • vertical-drag-gesture-handler 垂直方向

没太搞懂tap-gesture-handler用来干嘛。基于这个悬浮按钮的需求,这里使用pan-gesture-handler手势就够了,tap响应事件的话view本身就有。

实现悬浮按钮

悬浮按钮的实现原理很简单,元素跟踪手势滑动轨迹的x, y值就好了。

第一步:设计元素并绑定shared变量

wxml
使用手势标签框定需要拖动的元素

<pan-gesture-handler worklet:ongesture="hanldePan">
  <view class="box center" catch:tap="ballTap">
    <view wx:if="{{menuData.length}}" class="menu-box">
      <view wx:for="{{menuData}}" wx:key="index" class="ball-menu-item item-{{index}}" bind:tap="{{item.tap}}">
        <view wx:if="{{item.icon}}" class="ball-item {{item.icon}}"></view>
        <text wx:if="{{item.title}}">{{item.title}}</text>
      </view>
    </view>
    <image src="/images/logo.png" mode="widthFix" style="width: 100%;"/>
  </view>
</pan-gesture-handler>

绑定元素与shared变量
因为手势系统是基于skyline模式开发的,这里需要使用worklet的绑定拖动元素,使得它运行在UI线程中。如果不了解worklet, shared共享变量就需要补一补课了,去官网熟悉一下。

// 将shared变量与元素绑定
this.applyAnimatedStyle('.box', () => {
  'worklet'
  return {
    transform: `translate(${this.x.value}px, ${this.y.value}px)`
  }
})

第二步,动态设置shared(x, y)变量
通过worklet:ongesture的响应方法捕获手势轨迹,将轨迹数据赋值给x, y就完成了,是不是很简单

hanldePan(e) {
  'worklet'
  const dx = e.deltaX
  const dy = e.deltaY
  this.x.value += dx
  this.y.value += y
  const ballWidth = 66
  const padding = 40
  if (e.state === 1) {  // 捕获手势起始动作,收回子菜单
    wx.worklet.runOnJS(this.showMyMenu.bind(this))(null, false) 
  }
  if (e.state === 3) { // 手势结束
    const absX = Math.abs(this.x.value)
    //吸附靠边
    if (absX < (this.halfWinWidth - (ballWidth / 2) - (padding / 2))) { 
      // 靠右
      this.x.value = timing(0, {duration: 400})
    } else {
      // 靠左
      this.x.value = timing(-(windowWidth - ballWidth - 40), {duration: 400}) 
    }
  } 
}

timing(0, {duration: 400}) timing是worklet中内置的位移动画算法
'worklet'声明,手势系统响应方法需要声明为worklet才能正确执行

射出子菜单

UI上让子菜单以扇形展开。菜单项比较简单,传入数组数据就好了。UI这里涉及到圆,三角函数啥的,哈哈哈,这些都忘光了,还好去百度一下很快捡回来了。根据旋转的角度,通过 sin 和 cos 计算得到x, y的位移然后补上动画就行了,此处使用了wAnimate动画库,可以去小程序搜一下它的示例小程序

for (let ii=0; ii<datas.length; ii++) {
  wAnimate(`${animateMenuPrefix}${ii}`, (ani)=>{
    const angle = 45 * ii
    const restAngle = 90 - angle
    let toX = dd
    let toY = 0
    if (angle !== 0) {
      toY = dd * Math.sin(angle*(Math.PI / 180))  
      const tmp = (toY*toY + dd*dd) - 2*dd*toY*Math.cos(restAngle*(Math.PI / 180))
      toX = Math.sqrt(tmp)
    }
    ani.css({x: -(toX), y: -(toY), opacity: 1}).animate()
  })
}

结尾

新搞的公众号,偏小程序的,欢迎关注: 悟空不是程序猿