Flutter | 思路解析 WPopupMenu 仿微信聊天長按彈出菜單

在上一篇文章中發佈了 WPopupMenu 的第一個版本,而且也遺留了兩個問題:git

  1. 彈出框下面的三角
  2. 在最頂端的時候應向下彈出

那在此次發文以前也是解決了上述兩個問題和完善了一些邏輯問題:github

  1. 若是 child 的長度大於 menu 的長度,那麼則把 menu 放在中間
  2. 若是 child 的長度小於 menu 的長度,三角形的位置在 child 的中間

很少說,上圖:canvas

WPopupMenu 實現思路解析

首先,仍是按照正常業務邏輯,先提需求:微信

  1. 在當前頁面彈出
  2. 樣子要和微信同樣
  3. 自動肯定彈出位置(上 或者 下)
  4. 三角形自動判斷是 正三角 仍是 倒三角

需求差很少了,就該來實現了。ide

在當前頁面彈出

首先迎面來的就是第一個難題,如何在當前頁面彈出?this

這就涉及到我前面所講的幾篇文章:spa

Flutter | 超實用簡單菜單彈出框 PopupMenuButton3d

Flutter 源碼系列:DropdownButton 源碼淺析code

這幾個控件的源碼裏都有一個類:PopupRoute,該類我也講過:cdn

PopupRoute 是一個浮在當前頁面上的 Route.

看到沒,這就是閱讀源碼的益處!

既然是一個 Route,那麼也能夠經過他來返回值,一箭雙鵰。

瞭解瞭如何在當前頁面彈出頁面,那就能夠自定義樣式了。

樣子要和微信同樣

樣式也很簡單,大概也能看的出來:

  1. 第一頁是一個 ListView + 箭頭,若是不滿一頁則不顯示箭頭
  2. 除了第一頁都是 左箭頭 + ListView + 右箭頭
  3. 第二頁之後 若是後續再沒有數據,則右箭頭灰色且不可點擊

三角形咱們先不說,整個 menu 實際上是有一個背景的,就是一個圓角矩形,使用 ClipRRect 就能實現。

剩下的就是 ListView 和箭頭的組合,我使用了 Row 來組合這些組件,由於箭頭和 ListView 的 item 寬度不同,而且若是都使用 ListView,那麼下標的計算也很煩人。

因此我這裏直接放棄了這種麻煩的方法,選擇了一個相對簡單的方法。

大體邏輯以下:

自動肯定彈出位置(上 或者 下)

若是你看過最開始說的那些控件的源碼,那麼這個問題對於你來講應該不是問題,由於...

那些控件的源碼裏給了一個解決方案。直接複製代碼,稍微改一改就能用:

// 使用該控件
CustomSingleChildLayout(
  // 這裏計算偏移量
  delegate: _PopupMenuRouteLayout(),
  child: SizedBox()
)
 // ---------------------------------------------

// 使用 SingleChildLayoutDelegate 並複寫 getPositionForChild 方法來計算座標
class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
  @override
  Offset getPositionForChild(Size size, Size childSize) {
     // ...
    return Offset(x, y);
  }
}

複製代碼

這裏只是肯定了一個偏移量,那對於彈出位置是在 child 上面仍是下面,我是用 y 來判斷的:

若是 「 y < menu.height * 2 」,那麼則把它放到 child 下面。

三角形自動判斷是 正三角 仍是 倒三角

這裏的三角形是用 CustomPainter 來畫的,這樣能夠本身隨便定義屬性,簡單又方便。

簡單邏輯以下:

大概的代碼以下:

void paint(Canvas canvas, Size size) {
  var path = Path();

  // 若是 menu 的長度 大於 child 的長度
  if (size.width > this.size.width) {
    // 靠右
    if (position.left + this.size.width / 2 > position.right) {
      path.moveTo(size.width - this.size.width + this.size.width / 2, isInverted ? 0 : size.height);
      path.lineTo(
        size.width - this.size.width + this.size.width / 2 - radius / 2, isInverted ? size.height : 0);
      path.lineTo(
        size.width - this.size.width + this.size.width / 2 + radius / 2, isInverted ? size.height : 0);
    }else{
      // 靠左
      path.moveTo(this.size.width / 2, isInverted ? 0 : size.height);
      path.lineTo(
        this.size.width / 2 - radius / 2, isInverted ? size.height : 0);
      path.lineTo(
        this.size.width / 2 + radius / 2, isInverted ? size.height : 0);
    }
  } else {
    path.moveTo(size.width / 2, isInverted ? 0 : size.height);
    path.lineTo(
      size.width / 2 - radius / 2, isInverted ? size.height : 0);
    path.lineTo(
      size.width / 2 + radius / 2, isInverted ? size.height : 0);
  }

  path.close();

  canvas.drawPath(
    path,
    _paint,
  );
}
複製代碼

總結

一個完整版的 WPopupMenu 就完成了,這裏只是簡單的說了一下邏輯,

但實際寫起來並無那麼簡單。

若是以爲項目還能夠,請點個 star,萬分感謝!

完整代碼已經傳至GitHub:github.com/wanglu1209/…

我也建立了一個微信羣,有興趣的能夠掃碼加羣,若是羣滿,能夠添加我我的微信:17610912320,並註明來意。

img
相關文章
相關標籤/搜索