圖文並茂演示小程式movable-view的可移動範圍

2022-07-22 21:00:45

前言

開發過小程式的同學可能對這兩個內建元件並不陌生,他們配合用來實現在頁面中可以拖拽滑動,其中:

  • movable-area表示元素可移動的區域,它決定元素移動的區域範圍
  • movable-view表示可移動的檢視容器,它決定了什麼元素可以移動

使用上要求movable-view必須是movable-area的直接子節點,否則不能移動。

這兩個元件對於比較常規的可拖拽行動產品需求可以輕鬆應對,但是針對一些稍微複雜的需求,可能需要對他們的用法原理要進一步掌握理解。

重新認識movable-area和movable-view

在微信小程式官網介紹movable-area時,有過這樣的一段提示:

  1. tip: 當 movable-view 小於 movable-area 時,movable-view的移動範圍是在 movable-area 內;
  2. tip: 當 movable-view 大於 movable-area 時,movable-view的移動範圍必須包含movable-area(x軸方向和 y 軸方向分開考慮)

上面兩個元件比較大小是基於各自的尺寸大小而言的,也就是對應矩形的區域面積而言。

其實官網上面對二者關係的說明不是太詳細,有很多情況需要區分開;本人在專案做了不同的嘗試,下面是總結的不同情況,有不對的地方還請大家斧正。

movable-area和movable-view的一方完全包含另一方

針對movable-areamovable-view其中一方的尺寸大小可以完全覆蓋另一方的尺寸大小時,其移動範圍表現比較好理解。

例如下圖為movable-view的尺寸完全覆蓋movable-area的區域時,movable-view的可以移動範圍演示圖:

movable-view不管怎麼移動都要完全包含住movable-area,也就是說movable-area不能超出movable-view的區域範圍;反之亦然。

那麼大家有沒有想過,若不滿足一方能完全包含另一方,也就是二者區域存在交叉時,movable-view的移動範圍是怎麼表現的呢?

movable-area與movable-view區域交叉

所謂區域交叉,是指一方不能完全覆蓋另一方時,二者區域有部分重疊;針對這種情況其表現是有差異,這時movable-view的移動範圍就要針對x軸方向和 y 軸方向分開考慮。

總結來說:

二者交叉時,不看movable-areamovable-view的區域誰大誰小,而是看movable-view寬高值最大的那個方向。

舉個例子:movable-view的width比其height大,因為其跟movable-area區域交叉,那麼兩個不同方向的最大移動範圍表現:

  • 水平方向:movable-view的width要完全包含movable-area的width
  • 垂直方向: movable-view的height要被movable-area的包含覆蓋

如下移動演示圖:

movable-area區域大小為0,而movable-view不為0

movable-view的區域大於0,而movable-area的面積為0的在移動過程會有怎樣的表現呢?

首先,看下movable-area區域為0的兩種形式:

  • movable-area元件的widthheight都為0
  • movable-area元件的widthheight其中只有一個為0

那麼在這兩種情況下,movable-view的移動範圍是什麼呢,思考幾秒鐘。

其實,針對movable-area的寬高都為0的情況,可以將上圖的黑色正方塊想象成一個尺寸為0的一個點,只不過在介面不會展示,但是其位置還在對應位置,那麼movable-view就是圍繞該不可見點的位置移動,不能超過這個範圍,如下圖所示,為了方便展示將該點位置用紅色點表示。

針對movable-area的width和height任一個為0的情況,與二者同時為0將其想象一個點的情況主要區別是,可以將movable-area想象其為一條不可見的直線,它也不會在介面展示,但是它決定了movable-view移動範圍,我們以width為0,height不為0的情況來說明movable-view的移動範圍,如下圖演示:

movable-area與movable-view區域大小同時為0

首先介紹本節前說明一下:

  • moable-view為0不代表不能移動,例如其子元素有尺寸,依然可以移動
  • 在二者區域都為0的情況下,頁面是不會展示對應元素的,下圖以演示目的會將其畫出來表示其在頁面的位置

movable-area或者movable-view區域為0的情況,有兩種情況:要麼元素的width和height都為0,或者二者不同時為0。

下面我們來介紹下movable-view在其width和height不同時為0情況下(同時為0不會有移動的元素)的移動範圍,該前提下要區分movable-area區域為0的情況。

  • movable-area的寬高同時為0,movable-view的width不為0,height為0的情況(height不為0 的情況類似)。
  • movable-area的寬高不同時為0

    • movable-areamovable-view的width都不為0,或者height都不為0,其表現如下圖演示:
    • movable-area的有width為0,height不為0,而movable-view的width不為0,height為0的情況移動範圍演示如下圖,相反的情況類似;

由上面的演示可以得知:

movable-areamovable-view同時為0的情況,跟二者區域不為0且存在交叉的情況下表現類似。

movable-view的子元素內容超過其尺寸

movable-areamovable-view元素必須設定widthheight,但是有時我們movable-view的子元素內容超過其設定的寬高,這時其表現如何呢?

先說結論:

拖拽滑動元素的移動範圍是由movable-areamovable-view元素決定的,與movable-view的子元素尺寸沒有關係。

也就是說,movable-areamovable-view的寬高一旦設定後,移動範圍就固定了,如下圖所演示。

movable-view決定可拖動元素

要實現元素可拖動,至少要滿足:

  • 可拖動元素必須通過movable-view設定
  • movable-view必須為movable-area的直接子元素

說明一下,可以在movable-area中設定多個movable-view表示設定多個可滑動的塊,例如這文章# 微信小程式基於movable-area實現DIY T恤/logo客製化實現的拖動。

實現一個卡片多段式拖動

例如有一個產品需求螢幕內一個卡片支援多段式滑動,例如下圖所示的三段式:

要求:頁面資料初始化後卡片移動到h2的為位置,使用者手動拖動到h2 ~ h1的中間位置靠上時,卡片移動到h1的位置,中間位置靠下的話還是移動到h2的位置,h1~h0之間的移動後卡片位置策略與h2 ~ h1一樣。

一個實現思路:可以借鑑上面討論的movable-areamovable-view區域都為0,但是二者存在交叉的情況,具體實現:

  • movable-area設定其區域尺寸為width為0,height為100vh
  • movable-view設定其區域尺寸width為100vw,height為0
  • movable-view的子元素內容即為卡片的展示內容

這樣,movable-view在垂直方向的移動範圍就是movable-area的高度範圍,相當於在垂直方向,movable-area的長度大於movable-view,所以後者的移動範圍不能超出前者。

wxml的結構如下所示:

<movable-area
   style="width: 0; height: 100vh;"
>
  <movable-view
    direction="vertical"
    y="{{offsetY}}"
    style="width: 100vw; height: 0;"
    bindchange="onChange"
    bind:touchend="onTouchEnd"
    bind:touchcancel="onTouchEnd"
  >
    <view class="movable-content">
       <view class="card">
           ...
       </view>
    </view>
  </movable-view>
</movable-area>

可以在movable-view的change事件中收集卡片滑動後的y方向的偏移值,在觸控事件的結束最後統一計算卡片的最終滑動偏移量值。

Page({
 // 下面的h0、h1、h2、100vh 分別表示需求要求設定的卡片多段式滑動範圍
 data: {
     offsetY: h2,
     segs: [{
         value: h0,
         mix_value: h0,
         max_value: (h0 + h1)/2
     }, {
         value: h1,
         mix_value: (h0 + h1)/2,
         max_value: (h1 + h2)/2
     }, {
         value: h2,
         mix_value: (h1 + h2)/2,
         max_value: 100vh
     }]
 },
 ...
 onChange(event) {
   if (event.detail.source) {
      this._offsetY = event.detail.y
   }
 },
 onTouchEnd() {
     const y = this._offsetY;
      const idx = this.segs.findIndex(item => {
        return (
          y >= item.min_value && y <= item.max_value
        );
      });
      if (idx !== -1) {
        this.setData({
            offsetY: this.segs[idx].value
        })
      }
 }
})