Flutter實戰手勢番外篇-滑動抽屜效果實現

前言

這是第二篇番外了,第一篇是關於動畫翻轉效果實現而此次要說的是運用手勢操做實現相似於Drawer抽屜拖拽功能。Drawer是默認所有隱藏在屏幕外,在屏幕邊緣作手勢操做使其滑出或者經過點擊實現滑出。而我但願實現默認帶偏移量和多方向側拉效果功能,在默認狀況下可以展現側邊組件部份內容經過拖拽展現組件總體內容,可能描述上很差理解就直接上圖吧😂。git

沒圖怎麼行

實現效果如上圖所示,當初需求是但願側拉組件能夠拖拽邊緣實現滑出。

PS:爲何會有這樣的需求,我的認爲默認狀況下可以展現出組件部份內容可讓用戶更直觀地知道側邊可操做,感官上交互性更強😆。(例如豆瓣電影底部影評列表上拉展現)以上純屬我的對於交互設計理解╮( ̄▽ ̄)╭github

實現方案

實現拖拽功能確定離不開手勢操做和位移這兩點,而後結合這兩點組合操做實現抽屜拖拽滑出的功能組件。segmentfault

  • 拖拽組件在指定偏移量範圍內實現組件實時位移
  • 手勢擡起時根據組件位移具體作出不一樣動畫效果
  • 當拖拽距離小於最小偏移量則縮回組件
  • 當拖拽距離大於最小偏移量則展開組件

本次實現方案中使用到的組件包含:Transform 、RawGestureDetector 、 AnimationController 、 Animation。Transform、AnimationController、Animatio在以前實戰篇中都有介紹過而RawGestureDetector是第一次據說。RawGestureDetector也是實現手勢監聽組件和GestureDetector類似但比起GestureDetector使用上更爲複雜。markdown

簡要介紹一下二者不一樣點:post

  • GestureDetector無狀態、RawGestureDetector有狀態
  • GestureDetector經過設置各類手勢回調監聽手勢、RawGestureDetector則是設置gestures經過GestureRecognizerFactory實現手勢監聽
  • GestureDetector是高級組件,它的build方法就是一個RawGestureDetector組件。

因此在方案中徹底可使用GestureDetector代替RawGestureDetector更簡單,純粹是由於沒有用過RawGestureDetector因此嘗試一下😄學習

代碼

手勢部分

Transform.translate(
      offset: Offset(offsetX, offsetY),
      child: RawGestureDetector(
        gestures: {
          DrawerPanGestureRecognizer:
              GestureRecognizerFactoryWithHandlers<DrawerPanGestureRecognizer>(
            () => DrawerPanGestureRecognizer(),
            (DrawerPanGestureRecognizer instance) {
              instance
                ..onUpdate = (DragUpdateDetails details) {
                    // 省略處理邏輯,獲取實時手勢座標點根據拖拽方向更新Transform的offsetX或offsetY
                  }
                }
                ..onEnd = (DragEndDetails details) {
                    // 省略處理邏輯 手勢擡起操做根據位移距離判斷組件展開或是縮回動畫
                  }
                };
            },
          ),
        },
        child: Container(
          width: width,
          child: widget.child,
        ),
      ),
    );
複製代碼

動畫部分

動畫分爲展開動畫和縮回動畫。經過手勢位移量大小決定執行對應動畫效果,一樣是經過監聽動畫執行過程當中animation.value數值變化改變offsetX或offsetY的大小實現組件移動最終位移到最終點。動畫

/// 復原動畫
  _setCallBackAnimation() {
    double offset;
    switch (widget.direction) { //根據方向設置起始偏移值
      case DragDirection.top:
      case DragDirection.bottom:
        offset = offsetY;
        break;
      case DragDirection.left:
      case DragDirection.right:
        offset = offsetX;
        break;
    }
    print("dragdemo _setCallBackAnimation begin: $offset, end: $originOffset");
    _animation = Tween<double>(begin: offset, end: originOffset).animate(
        CurvedAnimation(
            parent: _callbackAnimationController, curve: Curves.easeOut))
      ..addListener(() {
        print("dragdemo _setCallBackAnimation ${_animation.value}");
        setState(() {
          switch (widget.direction) {
            case DragDirection.top:
            case DragDirection.bottom:
              offsetY = _animation.value;
              break;
            case DragDirection.left:
            case DragDirection.right:
              offsetX = _animation.value;
              break;
          }
        });
      });
  }

  /// 展開動畫
  _setToMaxAnimation() {
    double offset;
    switch (widget.direction) {//根據方向設置起始偏移值
      case DragDirection.top:
      case DragDirection.bottom:
        offset = offsetY;
        break;
      case DragDirection.left:
      case DragDirection.right:
        offset = offsetX;
        break;
    }
    print("dragdemo _setToMaxAnimation begin: $offset, end: $maxOffset");
    _animation = Tween<double>(begin: offset, end: maxOffset).animate(
        CurvedAnimation(
            parent: _toMaxAnimationController, curve: Curves.easeOutQuart))
      ..addListener(() {
        print("dragdemo _setToMaxAnimation ${_animation.value}");
        setState(() {
          switch (widget.direction) {
            case DragDirection.top:
            case DragDirection.bottom:
              offsetY = _animation.value;
              break;
            case DragDirection.left:
            case DragDirection.right:
              offsetX = _animation.value;
              break;
          }
        });
      });
  }
複製代碼

初始化部分

GestureDragDrawer(
      {this.child, //拖拽組件要展現的子內容
      this.childSize = 0, //子內容的大小
      this.originOffset = 0, //預設偏移量
      this.parentWidth = 0, //父級組件的寬度 當拖拽組件在bottom和right時須要用到
      this.parentHeight = 0,//父級組件的高度
      this.direction = DragDirection.left});
      .....
_initValue() {
    width = widget.childSize.abs();
    minOffset = -width / 2;
    midOffset = -width / 3;
    maxOffset = 0;
    /// 底部和右邊的偏移量須要特殊計算初始值(右邊和底部的值偏移量 = 父組件的寬或高 - 初始預設偏移量)
    switch (widget.direction) {
      case DragDirection.bottom:
        originOffset = widget.parentHeight - widget.originOffset;
        maxOffset = widget.parentHeight - width;
        midOffset = maxOffset + width / 3;
        minOffset = maxOffset + width / 2;
        break;
      case DragDirection.right:
        originOffset = widget.parentWidth - widget.originOffset;
        maxOffset = widget.parentWidth - width;
        midOffset = maxOffset + width / 3;
        minOffset = maxOffset + width / 2;
        break;
      default:
        originOffset = -width + widget.originOffset;
        break;
    }
  }
複製代碼

🚀完整代碼看這裏🚀ui

最後

最終實現效果以下(demo若是存在問題能夠告訴我哈~這樣我才能進步)this

考慮之後會寫更多自定義組件或Demo的可能性,因此Flutter番外篇組合成一個項目庫方便本身學習和總結。我也將第一篇番外Flip翻轉效果完整代碼添加到該項目中了但願小夥伴多多點贊支持一下。spa

番外篇的項目地址

最後的最後歡迎你們下載我本身開發的應用時鐘軟件,若是手頭有閒置Android手機能夠下載安裝平常使用。

參考資料

相關文章
相關標籤/搜索