首頁 > 軟體

Flutter實現固定header底部滑動頁效果範例

2022-12-29 14:01:35

正文

實現的效果是這樣的:

剛開始的時候,是在dev上找了兩個輪子,簡單測了下,都不太滿意,滑動事件處理的比較粗糙,總有bug。就在想著,要不要拿原始碼改一版的時候,讓我無意間看到了這個貼文

裡面的想法,大開眼界,是通過 DraggableScrollableSheet 和 IgnorePointer 來完美實現上面的效果。

實現

這是 DraggableScrollableSheet 的程式碼,

DraggableScrollableSheet(
  maxChildSize: 0.8,
  minChildSize: 0.25, // 注意都是佔父元件的比例
  initialChildSize: 0.25,
  expand: true,
  builder: (BuildContext context, ScrollController controller) {
    return Stack(); // body列表和header欄都在stack內
  },
)

這是 body 列表和 header,這裡的 body 是個 list,

Stack(
  children: [
    Container(
      color: Colors.blue,
      child: Body( // ListView.separated
        controller: controller,
        paddingTop: headerHeight, // 防止壓蓋
      ),
    ),
    const IgnorePointer( // 這裡不接收事件,所以拖動 header 也能夠滑動頁面
      child: Header( // Container[Center[Text]]
        height: headerHeight,
      ),
    ),
  ],
)

但如果我們想在 header 內加點選事件呢?那在 Stack header 上層再加 widget 就好了。

程式碼就這點,我放在了 gitHub 上,感興趣的可以看下。

2022.8.23 補充:

這是在上面功能基礎上的一個小擴充套件,即當滑動距離超過一半則自動滾至頂部,反之回到底部,來看下效果:

思路也很簡單,首先我要知道當前捲動的距離或其佔比,DraggableScrollableController 提供了這個能力:

void _draggableScrollListener() {
  // [_currStale] 記錄下當前的佔比
  // [_controller.size] 即佔比, 範圍[minChildSize,maxChildSize]
  // [_controller.pixels] 即距離
  if (_currStale != _controller.size) {
    _currStale = _controller.size;
  }
  debugPrint('[listener] size: ${_controller.size}'
      ', pixels : ${_controller.pixels}');
}

其次要知道使用者何時停止了捲動,我們可以使用 NotificationListener 來監聽 DraggableScrollableSheet 的捲動狀態:

NotificationListener<ScrollNotification>(
  onNotification: (ScrollNotification notification) {
    ...
    return false;
  },
child: DraggableScrollableSheet(...),

之後在使用者停止捲動的時候,我們判斷當前距離,並根據結果讓 DraggableScrollableSheet 自動捲動到頂部或底部。

onNotification: (ScrollNotification notification) {
  if (_animation) { // 動畫中,不處理狀態
    return false;
  }
  if (notification is ScrollStartNotification) {
    debugPrint('start scroll');
  } else if (notification is ScrollEndNotification) {
    debugPrint('stop scroll');
    // 通過 [_controller.animateTo] 方法捲動
    _scrollAnimation();
  }
  return false;

在 _scrollAnimation 內就是捲動的方法了,這裡要注意的是,不能直接使用 await Feature,我測試在頻繁不同方向滑動時,可能會導致方法被掛起。在這直接 dedelayed(duration: xx) 即可:

Future<void> _scrollAnimation() async {
  if (_animation) {
    return;
  }
  _animation = true;
  //debugPrint('async start');
  final int duration;
  // `await`ing the returned Feature(of [animateTo]) may cause the method to hang
  // So, Start a timer to set [_animation].
  if (_currStale >= (_maxScale + _minScale) * 0.5) {
    duration =
        (_duration * ((_maxScale - _currStale) / (_maxScale - _minScale)))
            .toInt();
    if (duration == 0) {
      _animation = false;
      return;
    } else {
      // [duration] control speed, Avoid situations where it's equal to 0
      _animationTo(_maxScale, duration);
    }
  } else {
    duration =
        (_duration * ((_currStale - _minScale) / (_maxScale - _minScale)))
            .toInt();
    if (duration == 0) {
      _animation = false;
      return;
    } else {
      _animationTo(_minScale, duration);
    }
  }
  Future.delayed(
    Duration(milliseconds: duration),
  ).then((value) => {
        //debugPrint('async stop'),
        _animation = false,
      });
}

其中 _animationTo 是實際控制控制元件捲動的方法:

void _animationTo(double scale, int duration) {
  _controller.animateTo(
    scale,
    duration: Duration(milliseconds: duration),
    curve: Curves.ease,
  );
}

2022.9.24 補充:

那如果再提供一種通過點選按鈕來控制 DraggableScrollableSheet 收起和彈出的方法呢?

我們可以直接這樣,是不是很簡單:

Future<void> _scrollAnimation2() async {
  if (_animation) {
    return;
  }
  if (_currStale > (_maxScale + _minScale) * 0.5) {
    _animationTo(_minScale, _duration);
  } else {
    _animationTo(_maxScale, _duration);
  }
}

以上就是Flutter實現固定header底部滑動頁效果範例的詳細內容,更多關於Flutter固定header底部滑動頁的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com