flutter sliver 多種滾動組合開發指南

發表於2024-02-27

flutter sliver 多種滾動組合開發指南

影片

https://youtu.be/4mho1kZ_YQU

https://www.bilibili.com/video/BV1WW4y1d7ZC/

前言

有不少同學工作中遇到需要把幾個不同滾動行為元件(頂部 appBar、內容固定塊、tabBar 切換、tabBarView檢視、自適應高度、橫向滾動)黏貼成一個元件。

這時候就需要 sliver 出場了,本文將會寫一個多種滾動的組合。

業務場景分析

下面是淘寶、小紅書的常見情況。

原文 https://ducafecat.com/blog/flutter-sliver-scroll

知識點 sliver

Sliver 是 Flutter 中用於構建可滾動檢視的基本構建塊之一。Sliver 是可滾動區域中的一小部分,具有固定的大小和位置,可以根據需要動態載入和解除安裝。Sliver 通常用於建立高效能、高度靈活的可滾動檢視,例如列表、網格、瀑布流等。

在 Flutter 中,有許多不同型別的 Sliver 元件,每個元件都有特定的作用和用途。下面是一些常見的 Sliver 元件:

  • SliverAppBar:一個帶有滾動效果的應用欄,可以在向上滾動時隱藏,並在向下滾動時顯示。
  • SliverList:將子元件放置在一個垂直列表中,可以根據需要動態載入和解除安裝列表項。
  • SliverGrid:將子元件放置在一個網格中,可以根據需要動態載入和解除安裝網格項。
  • SliverPadding:為子元件提供填充,以使它們與其他 Sliver 元件的大小和位置保持一致。
  • SliverToBoxAdapter:將一個普通的元件包裝成一個 Sliver 元件,以便將其放置在 CustomScrollView 中。

參考

步驟

第一步:Sliver 橫向滾動

lib/page.dart

  Widget _mainView() {
    return CustomScrollView(
      slivers: [
        // 橫向滾動
        SliverToBoxAdapter(
          child: SizedBox(
            height: 100,
            child: PageView(
              children: [
                Container(
                  color: Colors.yellow,
                  child: const Center(child: Text('橫向滾動')),
                ),
                Container(color: Colors.green),
                Container(color: Colors.blue),
              ],
            ),
          ),
        ),
        ...
SliverToBoxAdapter 進行包裝才能 slivers 使用。
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sliver Scroll')),
      body: _mainView(),
    );
  }

第二步:固定高度的 tabView

    return CustomScrollView(
      slivers: [
      ...
        // 固定高度內容
        SliverToBoxAdapter(
          child: Container(
            height: 200,
            color: Colors.greenAccent,
            child: const Center(child: Text('固定高度內容')),
          ),
        ),
        // tabView 內容
        SliverToBoxAdapter(
          child: DefaultTabController(
            length: 3,
            child: Column(
              children: [
                const TabBar(
                  tabs: [
                    Tab(text: 'Tab 1'),
                    Tab(text: 'Tab 2'),
                    Tab(text: 'Tab 3'),
                  ],
                ),
                SizedBox(
                  height: 200,
                  child: TabBarView(
                    children: [
                      Container(color: Colors.yellow),
                      Container(color: Colors.green),
                      Container(color: Colors.blue),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
外層巢狀 DefaultTabController ,才能讓 TabBar、TabBarView 順利工作。

第三步:自適應高度的 tabView

實現 SliverPersistentHeaderDelegate 抽象類


class _SliverDelegate extends SliverPersistentHeaderDelegate {
  _SliverDelegate({
    required this.minHeight,
    required this.maxHeight,
    required this.child,
  });

  final double minHeight; //最小高度
  final double maxHeight; //最大高度
  final Widget child;

  @override
  double get minExtent => minHeight;

  @override
  double get maxExtent => max(maxHeight, minHeight);

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    return SizedBox.expand(child: child);
  }

  @override //是否需要重建
  bool shouldRebuild(_SliverDelegate oldDelegate) {
    return maxHeight != oldDelegate.maxHeight ||
        minHeight != oldDelegate.minHeight ||
        child != oldDelegate.child;
  }
}

編寫固定頭部 sliver 元件

  Widget _buildPersistentHeader(Widget child,
          {double? minHeight, double? maxHeight}) =>
      SliverPersistentHeader(
          pinned: true,
          delegate: _SliverDelegate(
            minHeight: minHeight ?? 40.0,
            maxHeight: maxHeight ?? 40.0,
            child: child,
          ));

定義 TabController

class _MyPageViewState extends State<MyPageView> with TickerProviderStateMixin {
    ...
混入 TickerProviderStateMixin
  late TabController _tabController;
  @override
  void initState() {
    _tabController = TabController(length: 3, vsync: this);
    super.initState();
  }
  @override
  void dispose() {
    _tabController.dispose(); // 釋放記憶體
    super.dispose();
  }

加入 slivers

  Widget _mainView() {
    return CustomScrollView(
      slivers: [
        ...
        
                // TabBar 固定
        _buildPersistentHeader(TabBar(
          controller: _tabController,
          tabs: const [
            Tab(text: 'Tab 1'),
            Tab(text: 'Tab 2'),
            Tab(text: 'Tab 3'),
          ],
        )),

使用 SliverFillRemaining 來撐開剩餘空間

        // TabBarView 自適應高度
        SliverFillRemaining(
          child: TabBarView(
            controller: _tabController,
            children: [
              // 第一個選項卡的內容
              ListView.builder(
                itemCount: 20,
                itemBuilder: (BuildContext context, int index) {
                  return ListTile(title: Text('Item $index'));
                },
              ),
              // 第二個選項卡的內容
              ListView.builder(
                itemCount: 10,
                itemBuilder: (BuildContext context, int index) {
                  return ListTile(title: Text('Item $index'));
                },
              ),
              // 第三個選項卡的內容
              ListView.builder(
                itemCount: 5,
                itemBuilder: (BuildContext context, int index) {
                  return ListTile(title: Text('Item $index'));
                },
              ),
            ],
          ),
        ),
SliverFillRemaining 是一個可以填充剩餘空間的 sliver 元件,它可以將子元件放置在檢視區域的剩餘空間中,並自動調整子元件的大小以填充整個空間。通常情況下,SliverFillRemaining 用於在滾動檢視中放置一個佔滿整個檢視區域的元件,例如底部欄或頁尾。

第四步:子 tabBar

還可以加入子 tabBar 組成父子選項切換

        // 子 TabBar 固定
        _buildPersistentHeader(TabBar(
          controller: _tabController,
          tabs: const [
            Tab(text: 'subTab 1'),
            Tab(text: 'subTab 2'),
            Tab(text: 'subTab 3'),
          ],
        )),

父子 tabBar 中間再加一個固定塊,檢視滾動效果

        // 固定高度內容
        SliverToBoxAdapter(
          child: Container(
            height: 100,
            color: Colors.greenAccent,
            child: const Center(child: Text('固定高度內容')),
          ),
        ),

最後:底部再加入 SliverList

我們在底部再加一個 list 模組,看看效果。

  Widget _mainView() {
    return CustomScrollView(
      slivers: [
        ...
        
        // 固定高度內容
        SliverToBoxAdapter(
          child: Container(
            height: 200,
            color: Colors.greenAccent,
            child: const Center(child: Text('固定高度內容')),
          ),
        ),

        // 列表 100 行
        SliverList(
          delegate: SliverChildBuilderDelegate(
            (BuildContext context, int index) {
              return ListTile(title: Text('Item $index'));
            },
            childCount: 100,
          ),
        ),

程式碼

https://github.com/ducafecat/flutter_develop_tips/tree/main/flutter_application_sliver_scroll

小結

使用 Sliver 元件後我們確實把幾種滾動給黏貼上了,但是不難發現過於複雜的滾動,使用者體驗方面還是要考慮的。

不能只為了堆積功能。

感謝閱讀本文

如果我有什麼錯?請在評論中讓我知道。我很樂意改進。


© 貓哥
ducafecat.com

end

相關文章