Flutter 嵌套滾動 CustomScrollView 示例

CustomScrollView

來源: CustomScrollView 是可以使用Sliver來自定義滾動模型(效果)的組件。舉個例子,假設有一個頁面,頂部需要一個GridView,底部需要一個ListView,而要求整個頁面的滑動效果是統一的,即它們看起來是一個整體。如果使用GridView+ListView來實現的話,就不能保證一致的滑動效果,而CustomScrollView 就可以實現這個效果。

在這裏插入圖片描述
實現源碼請查看原文:CustomScrollView

簡易版代碼如下:

class CustomScrollViewTestRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Material(
      child: CustomScrollView(
        slivers: <Widget>[
          SliverAppBar(
             //AppBar,包含一個導航欄
          ),
          SliverPadding(
            padding: const EdgeInsets.all(8.0),
            sliver: new SliverGrid(
            	 //Grid 內容
            ),
          ),
          new SliverFixedExtentList(
          		 // ListView 內容
          ),
        ],
      ),
    );
  }
}

添加頭部

可以使用 SliverPersistentHeader 來實現,也可以直接在ListView頂部添加一個 Head來實現。

效果如下:
在這裏插入圖片描述
源碼實現也很簡單,這裏直接在CustomScrollView裏面嵌套兩個SliverFixedExtentList,一個顯示Head 頭部,一個顯示List 列表
在這裏插入圖片描述

參考:Flutter:Slivers大家族,讓滑動視圖的組合變得很簡單!
使用 SliverPersistentHeader 來實現需要自定義繼承SliverPersistentHeaderDelegate

class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate{
  _SliverAppBarDelegate({
    @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 => math.max(maxHeight, minHeight);

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

  @override
  bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
    return maxHeight != oldDelegate.maxHeight ||
        minHeight != oldDelegate.minHeight ||
        child != oldDelegate.child;
  }
}

使用的時候直接添加到sliver裏面即可

SliverPersistentHeader(
    pinned: false,
    floating: false,
    delegate: _SliverAppBarDelegate(
      minHeight: 60.0,
      maxHeight: 250.0,
      child: Container(
        color: Colors.blue,
        child: Center(
          child: Text('header',style: TextStyle(color: Colors.white),),
        )
      ),
    ),
  ),

視差滑動

視差滾動是指讓多層背景以不同的速度移動,在形成立體滾動效果的同時,還能保證良好的視覺體驗。

效果圖如下:
在這裏插入圖片描述
要實現該效果主要用到了SliverAppBar組件。

做過Android 開發的都知道CollapsingToolbarLayout控件,它可以實現頁面頭部展開、合併的視差效果。在Flutter中是通過SliverAppBar組件實現類似的效果。

直接查看SliverAppBar組件支持的字段吧:

  const SliverAppBar({
    Key key,
    this.leading,// 左側的widget
    this.automaticallyImplyLeading = true,
    this.title,//標題
    this.actions,//標題右側的操作
    this.flexibleSpace,// 背景widget,如 FlexibleSpaceBar 可設置標題,背景圖片,標題邊距等
    this.bottom, // 底部區
    this.elevation,//陰影
    this.forceElevated = false,//是否顯示陰影
    this.backgroundColor,//背景顏色
    this.brightness,//狀態欄主題
    this.iconTheme,// 圖標主題
    this.actionsIconTheme,//action圖標主題
    this.textTheme,//文字主題
    this.primary = true,//是否顯示在狀態欄的下面,false就會佔領狀態欄的高度
    this.centerTitle,//標題居中顯示
    this.titleSpacing = NavigationToolbar.kMiddleSpacing,//標題橫向間距
    this.expandedHeight,//合併的高度,默認是狀態欄的高度加AppBar的高度
    this.floating = false,//滑動時是否懸浮
    this.pinned = false,// 滑動時標題欄是否固定
    this.snap = false,// 滑動時標題欄跟隨移動並固定在頂部, pinned 和 floating 效果的組合
    this.stretch = false,// 標題跟隨滑動時拉伸,收縮
    this.stretchTriggerOffset = 100.0,// 標題跟隨滑動時拉伸,收縮的偏移量
    this.onStretchTrigger,// 跟隨滑動時拉伸,收縮的回調
    this.shape,// 陰影形狀,elevation 大於0 纔會顯示
  })

在字段的後面都寫明瞭相應的介紹,只需要在使用的時候設置相關的參數即可實現效果。

監聽滑動

ScrollController

使用列表提供的controller字段,並調用監聽方法監聽滑動距離

_controller.addListener((){
      print('_controller offset : ${_controller.offset}');
    });
NotificationListener
  • 使用ScrollNotification控件去監聽滾動列表。
 @override
  Widget build(BuildContext context) {
    return Material(
      child: NotificationListener<ScrollNotification>(
          onNotification: (scrollNotification) {
            //註冊通知回調
            if (scrollNotification is ScrollStartNotification) {
              //滾動開始
              print('Scroll Start: ${scrollNotification.metrics.pixels}');
            }else if (scrollNotification is ScrollUpdateNotification) {
              //滾動位置更新
              print('Scroll Update: ${scrollNotification.metrics.pixels}');
            } else if (scrollNotification is ScrollEndNotification) {
              //滾動結束
              print('Scroll End: ${scrollNotification.metrics.pixels}');
            }
            return false;
          },
        child: CustomScrollView(
          controller: _controller,
          // 滑動列表 widget
        ),
      )
    );
  }
回到頂部功能

在新聞列表,或者列表數據很多的時候,我們往後翻好幾頁之後,突然想回到列表的頂部,這時候該如何實現呢?

在這裏插入圖片描述

  • 首先,在initState方法裏,初始化 ScrollController
  • 隨後,在視圖構建方法 build 中,我們將 ScrollController對象與 滾動列表 controll關聯,並在SliverAppBar添加一個按鈕用於點擊後調用_controller.animateTo 方法返回列表頂部。
  • 最後,在 dispose方法中,我們對 ScrollController 進行了資源釋放。

實現源碼如下:

class _CustomScrollViewPageState extends State<CustomScrollViewPage> {
  //滑動控制器
  ScrollController _controller;

  @override
  void initState() {
    //初始化控制器
    _controller = ScrollController();
    super.initState();
  }
  @override
  void dispose() {
    //銷燬控制器
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Material(
      child: CustomScrollView(
        controller: _controller,
        slivers: <Widget>[
          //AppBar,包含一個導航欄
          SliverAppBar(
            pinned: true,
            expandedHeight: 250.0,
            actions: <Widget>[
              RaisedButton(
                child: Text('返回頂部'),
                onPressed: (){
                  _controller.animateTo(.0, duration: Duration(milliseconds: 200), curve: Curves.ease);
                },
              )
            ],
            flexibleSpace:  FlexibleSpaceBar(
              title: const Text('CustomScrollView'),
              background: Image.network(
                "https://ssyerv1.oss-cn-hangzhou.aliyuncs.com/picture/389e31d03d36465d8acd9939784df6f0.jpg!sswm", fit: BoxFit.cover,),
            ),
          ),
            //List
            new SliverFixedExtentList(
              itemExtent: 50.0,
              delegate: new SliverChildBuilderDelegate(
                      (BuildContext context, int index) {
                    //創建列表項
                    return new Container(
                      alignment: Alignment.center,
                      color: Colors.lightBlue[100 * (index % 9)],
                      child: new Text('list item $index'),
                    );
                  },
                  childCount: 50 //50個列表項
              ),
            ),
          ],
        ),
    );
  }
}

完~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章