iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 16
1
Mobile Development

iOS Developer Learning Flutter系列 第 16

iOS Developer Learning Flutter. Lesson15 特製滾動

今天是Scroll篇最後一天
講講滾動綜藝大集合的神器

一樣先上圖

Today Preview

概念

本節內容引用自Flutter實戰一書 6.1 可滚动组件简介6.5 CustomScrollView

Sliver

通常可滚动组件的子组件可能会非常多、占用的总高度也会非常大;如果要一次性将子组件全部构建出将会非常昂贵!
为此,Flutter中提出一个Sliver(中文为“薄片”的意思)概念,如果一个可滚动组件支持Sliver模型,那么该滚动可以将子组件分成好多个“薄片”(Sliver),只有当Sliver出现在视口中时才会去构建它,这种模型也称为“基于Sliver的延迟构建模型”。
可滚动组件中有很多都支持基于Sliver的延迟构建模型,如ListView、GridView,但是也有不支持该模型的,如SingleChildScrollView

也就是說ScrollView有分兩種:不會 lazy的
會lazy的cell在Flutter裡的世界就叫做Sliver(不是Silver喔XDDD)
我們之前提到的children跟builder,只有builder裡的Widget才算Sliver

CustomScrollView的子元件

实际上Sliver版的可滚动组件和非Sliver版的可滚动组件最大的区别就是前者不包含滚动模型(自身不能再滚动),而后者包含滚动模型
也正因如此,CustomScrollView才可以将多个Sliver"粘"在一起,这些Sliver共用CustomScrollView的Scrollable。
SliverPadding、SliverAppBar等是和可滚动组件无关的,它们主要是为了结合CustomScrollView一起使用

  1. 如果要將多個ScrollView合起來我們就會用CustomScrollView
  2. CustomScrollView裡面一定要放Sliver
    所以像之前提到的sticky_header在CustomScrollView裡就要用flutter_sticky_header
  3. Sliver本身就不能scroll了
  4. 總結:☘️☘️☘️
    CustomScrollView就像CompositionalLayout
    其中的Sliver就像NSCollectionLayoutGroup

ViewPort

在很多布局系统中都有ViewPort的概念,在Flutter中,术语ViewPort(视口),如无特别说明,则是指一个Widget的实际显示区域。例如,一个ListView的显示区域高度是800像素,虽然其列表项总高度可能远远超过800像素,但是其ViewPort仍然是800像素。

感覺應該就是Frame☘️☘️☘️

實作

  1. 以前常見的關鍵字 child/children 都改 sliver/slivers 囉
  2. SliverList 或 SliverGrid 不使用 builder方法了
    多了一個delegate 屬性
    使用SliverChildBuilderDelegate來回傳builder 跟 childCount
  3. SliverList多了一種SliverFixedExtentList
    如果要指定cell的高度一定要用FixedExtent版
    反之, SliverList就不能指定高度
  4. 以前有提過RefreshIndicator,iOS版的叫做CupertinoSliverRefreshControl
    但因為iOS的是顯示在ScrollView裡面,不像Android是浮在上面
    所以必須使用Sliver版
    (原本提供的箭頭有點醜...可以指定builder把它換掉)
  5. 因為把SliverAppBar也包進來了
    所以不使用Scaffold
    雖然不使用Scaffold了
    但還是需要用Material Widget當作這頁面的root widget
    讓畫面風格都維持一致(但好像沒有Cupertino Widget這東東)
  6. SliverGrid 還是使用 childAspectRatio 去決定cell的高度
    如果想要自行指定高度,可以透過MediaQuery去算
    詳情請見這篇,今天就不實作了
  7. 如果想要讓ScrollView滾動到特定位置
    似乎沒有指定index的方法(倒是有看到一些套件)
    目前有看到的做法是:
    1. 自己算要滾動的距離
    2. 用key標記widget, 然後Scrollable.ensureVisible
class LessonPageCustomScrollView extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    final space = 10.0;
    final tintColor = Colors.pinkAccent.withOpacity(0.5);
    final horizontalKey = new GlobalKey();

    void scrollToTop() {
      Scrollable.ensureVisible(horizontalKey.currentContext);
    }

    Widget _createCell(String title) {
      return Card(
        color: tintColor,
        child: InkWell(
          child: Container(
            alignment: Alignment.center,
            padding: EdgeInsets.all(space),
            child: Text(title, textAlign: TextAlign.center,
              style: TextStyle(
                color: Colors.white,
                fontSize: 24,
                fontWeight: FontWeight.w500
              )
            )
          ),
          onTap: title.startsWith("horizontal") ? scrollToTop : null,
        )
      );
    }

    Widget buildNoArrowRefreshIndicator(
        BuildContext context,
        RefreshIndicatorMode refreshState,
        double pulledExtent,
        double refreshTriggerPullDistance,
        double refreshIndicatorExtent,
        ) {
      const Curve opacityCurve = Interval(0.4, 0.8, curve: Curves.easeInOut);
      return Align(
        alignment: Alignment.bottomCenter,
        child: Padding(
          padding: const EdgeInsets.only(bottom: 16.0),
          child: refreshState == RefreshIndicatorMode.drag
              ? Opacity(
            opacity: opacityCurve.transform(
                min(pulledExtent / refreshTriggerPullDistance, 1.0)
            ),
            child: Icon(
              CupertinoIcons.car_detailed,
              color: CupertinoDynamicColor.resolve(CupertinoColors.inactiveGray, context),
              size: 36.0,
            ),
          )
              : Opacity(
            opacity: opacityCurve.transform(
                min(pulledExtent / refreshIndicatorExtent, 1.0)
            ),
            child: const CupertinoActivityIndicator(radius: 14.0),
          ),
        ),
      );
    }

    return Material(
      child: CustomScrollView(
        slivers: <Widget>[

          //頂
          SliverAppBar(
            backgroundColor: tintColor,
            pinned: false,
            expandedHeight: 200,
            flexibleSpace: FlexibleSpaceBar(
              title: Text("第十三堂課"),
              background: Image.asset("resource/images/fantasy_unicorn.jpg",
                fit: BoxFit.cover
              ),
            ),
          ),

          //拉
          CupertinoSliverRefreshControl(
            builder: buildNoArrowRefreshIndicator,
            onRefresh: () async {
              print("拉爽的");
            },
          ),

          //上
          SliverPadding(
            padding: EdgeInsets.only(top: space, left: space, right: space),
            sliver: SliverGrid(
              delegate: SliverChildBuilderDelegate((ctx, idx){
                return _createCell("Grid\n$idx");
              }, childCount: 10),
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2,
                mainAxisSpacing: space,
                crossAxisSpacing: space,
                childAspectRatio: 1
              )
            ),
          ),

          //中
          SliverFixedExtentList(
            key: horizontalKey,
            itemExtent: 100,
            delegate: SliverChildBuilderDelegate ((ctx, idx) {
              return ListView.builder(
                scrollDirection: Axis.horizontal,
                itemCount: 10,
                itemBuilder: (ctx, idx) {
                  return _createCell("horizontal\n$idx");
              });
            }, childCount: 1 )
          ),

          //下
          SliverList(
            delegate: SliverChildBuilderDelegate ((ctx, idx) {
              return _createCell("list $idx");
            }, childCount: 10 )
          )
        ],
      ),
    );
  }
}

參考連結


下集預告:底部導航與頁籤


上一篇
iOS Developer Learning Flutter. Lesson14 網格
下一篇
iOS Developer Learning Flutter. Lesson16 底部導航與頁籤
系列文
iOS Developer Learning Flutter30

尚未有邦友留言

立即登入留言