Создавайте интересные эффекты прокрутки во Flutter — серия Sliver

Flutter
Создавайте интересные эффекты прокрутки во Flutter — серия Sliver

1. Введение

FlutterКак одна из самых популярных технологий в настоящее время, она уже привлекла внимание большого количества энтузиастов технологий, и даже некоторых闲鱼,美团,腾讯И другие крупные компании были введены в эксплуатацию. Хотя его экология еще не полностью созрела,GoogleБлаго, скорость его развития уже достаточно поразительна, можно предвидеть, что в будущемFlutterСпрос на разработчиков также будет расти.

Будь то первые пользователи технологий или возможные вакансии в будущем, прошло уже 9102 года.Как фронтенд-разработчику, кажется, нет причин не попробовать это. Именно с таким менталитетом автор и начал изучатьFlutter, и построилсклад, весь последующий код будет размещаться на нем, добро пожаловать звездочка, учитесь вместе. Это серия статей о Flutter, которую я написал:

В предыдущей статье мы узнали, как использоватьListViewиGridViewЭти два компонента типа прокрутки. Сегодня давайте изучим еще один компонент прокруткиCustomScrollViewи его использование сSliverКомпоненты серии. Освойте их, и вы сможете создавать интересные эффекты прокрутки~

2. Необходимые знания

Прежде чем перейти к сегодняшней теме, давайте кратко разберемся с двумя сегодняшними главными героями.CustomScrollViewиSliver:CustomScrollViewдаFlutterПредоставленные компоненты, которые можно использовать для настройки эффекта прокрутки, можно использовать как клей для объединения несколькихSliverсклеены.

Что это обозначает? Возьмите каштан (также можно нажатьздесьСмотретьyoutubeвидео выше):

если естьListиGrid, хотя они кажутся единым целым, но поскольку их эффекты прокрутки являются отдельными, нет гарантии согласованности эффектов прокрутки.

при использованииCustomScrollViewкомпоненты в виде прокручиваемых контейнеров,SliverListиSliverGridсоответственно заменитьListиGridв видеCustomScrollView, эффект прокрутки затем контролируетсяCustomScrollViewЕдиный контроль, вот и все.

вSliverListиSliverGridэто то, что мы упоминали ранееSliverДва члена сериала, среди прочего,SliverЕсть еще несколько часто используемых семейств:

  • SliverAppBar: создает панель приложения для дизайна материалов, которую можно поместить в CustomScrollView.
  • SliverPersistentHeader: Создает полосу, размер которой меняется при прокрутке до начала области просмотра.
  • SliverFillRemaining: создает полосу, которая заполняет оставшееся пространство в окне просмотра.
  • SliverToBoxAdapter: создает полосу, содержащую виджет с одним полем.
  • SliverPadding: Создает полосу, которая применяет отступы с каждой стороны другой полоски.

Примечание: из-заCustomeScrollViewподкомпоненты могут быть толькоSliverсерии, так что если вы хотите впихнуть нормальный компонент вCustomScrollView, то обязательно используйте этот компонент сSliverToBoxAdapterпакет.

3. Разминка: SliverList/SliverGrid

Кажется немного скучным говорить о таком количестве концепций раньше, поэтому давайте начнем с самого простого примера и посмотрим, как его использовать.CustomScrollViewиSliverList/SliverGrid.

фактическиCustomScrollViewИспользование простое, оно имеетsliversсобственность, представляет собойWidgetМассив, просто поместите в него все подкомпоненты, и некоторые другие свойства, связанные с прокруткой, в основном такие же, как то, что мы узнали раньше.ListViewпочти.

CustomScrollView(
  slivers: <Widget>[
    renderSliverA(),
    renderSliverB(),
    renderSliverC(),
  ],
)

посмотри сноваSliverList, он имеет только одинdelegateсвойства, вы можете использоватьSliverChildListDelegateилиSliverChildBuilderDelegateЭти два класса реализуют. Первый будет отображать все дочерние компоненты одновременно, а второй будет отображать появляющиеся в данный момент элементы в соответствии с окном просмотра, и его эффект может быть таким же, какListViewиListView.buildДва конструктора аналогичны.

SliverList(
  delegate: SliverChildListDelegate(
    <Widget>[
      renderA(),
      renderB(),
      renderC(),
    ]
  )
)

SliverList(
  delegate: SliverChildBuilderDelegate(
    (context, index) => renderItem(context, index),
    childCount: 10,
  )
)

Из приведенного выше примера мы находим, чтоSliverListкак использовать иListViewпочти так же, иSliverGridТо же самое верно, поэтому я не буду вдаваться в подробности. Давайте рассмотрим пример сетки из двух столбцов:

SliverGrid.count(
  crossAxisCount: 2,
  children: <Widget>[
    renderA(),
    renderB(),
    renderC(),
    renderD()
  ]
)

Далее, давайте объединим три пункта выше с практическим примером.

Код (см. полную версиюздесь):

final List<Color> colorList = [
  Colors.red,
  Colors.orange,
  Colors.green,
  Colors.purple,
  Colors.blue,
  Colors.yellow,
  Colors.pink,
  Colors.teal,
  Colors.deepPurpleAccent
];

// Text组件需要用SliverToBoxAdapter包裹,才能作为CustomScrollView的子组件
Widget renderTitle(String title) {
  return SliverToBoxAdapter(
    child: Padding(
      padding: EdgeInsets.symmetric(vertical: 16),
      child: Text(
        title,
        textAlign: TextAlign.center,
        style: TextStyle(fontSize: 20),
      ),
    ),
  );
}

CustomScrollView(
  slivers: <Widget>[
    renderTitle('SliverGrid'),
    SliverGrid.count(
      crossAxisCount: 3,
      children: colorList.map((color) => Container(color: color)).toList(),
    ),
    renderTitle('SliverList'),
    SliverFixedExtentList(		// SliverList的语法糖,用于每个item固定高度的List
      delegate: SliverChildBuilderDelegate(
        (context, index) => Container(color: colorList[index]),
        childCount: colorList.length,
      ),
      itemExtent: 100,
    ),
  ],
)

визуализация:

Еще одна вещь, которую следует отметить в приведенном выше примере, это то, что мы поместили компонент заголовка вSliverToBoxAdapterвнутри, потому чтоCustomScrollViewпринимать толькоSliverряд компонентов.

4. Яркий SliverAppBar

AppBarобычно используется для создания заголовка страницыBarкомпоненты, вCustomScrollViewчто соответствуетSliverAppBarкомпоненты. Что в нем такого волшебного? По мере прокрутки страницы заголовокBarБудет эффект перехода коллапса. Давайте сначала посмотрим на эффект:

плавающий эффект моментальный эффект закрепленный эффект
float
snap
pinned

Через предварительный просмотр выше, вы должны быть очень любопытныSliverAppBarКак добиться эффекта перехода в ~ Не волнуйтесь, давайте посмотрим, как его использовать:

SliverAppBar(
  floating: true,
  snap: true,
  pinned: true,
  expandedHeight: 250,
  flexibleSpace: FlexibleSpaceBar(
    title: Text(this.title),
    background: Image.network(
      'http://img1.mukewang.com/5c18cf540001ac8206000338.jpg',
      fit: BoxFit.cover,
    ),
  ),
)

SliverAppBarНаиболее важные свойства перечислены в примере выше. в:

  • expandedHeight: в развернутом состоянииappBarВысота , то есть место, занимаемое картинкой на рисунке;
  • flexibleSpace: компоненты с переменным размером пространства,Flutterпредоставляет нам готовыйFlexibleSpaceBarКомпонент, сделай это для насtitleпереходный эффект.

Кроме того,floating/snap/pinnedЭти три свойства могут быть указаныSliverAppBarПрезентация контента после того, как он соскользнет с экрана.

  • float: при смахивании вниз, даже если текущийCustomScrollViewне наверху,SliverAppBarтакже появятся вниз вместе;
  • snap: Когда палец отпущен,SliverAppBarбудет регулироваться в соответствии с текущим положением, всегда сохраняя展开или收起статус;
  • pinned: отличается отfloatэффект, когдаSliverAppBarКогда содержимое соскальзывает с экрана, компонент свернутого состояния, закрепленный вверху, всегда будет отображаться.

нужно знать, это:snapЭффект должен бытьfloatзаtrueвступит в силу. Кроме того, вы можете использовать комбинацию из трех.

5. Разновидности SliverPersistentHeader

В предыдущем разделе мы виделиSliverAppBarЕго магия в том, что он основан наSliverPersistentHeaderосуществленный. пройти черезSliverPersistentHeader, мы также можем достичьstickyэффект потолка.

SliverPersistentHeaderНаиболее важным атрибутом являетсяSliverPersistentHeaderDelegate, для этого нам нужно реализовать класс, который наследуется отSliverPersistentHeaderDelegate.

class StickyTabBarDelegate extends SliverPersistentHeaderDelegate {

  @override
  double get minExtent => null;

  @override
  double get maxExtent => null;

  @override
  bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => null;
  
  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) => null;
}

можно увидеть,SliverPersistentHeaderDelegateКласс реализации должен реализовать свои 4 метода. в:

  • minExtent: высота компонента в убранном состоянии;
  • maxExtent: высота компонента в развернутом состоянии;
  • shouldRebuild: что-то типаreactсерединаshouldComponentUpdate;
  • build: Создайте визуализированный контент.

Далее мы реализуемTabBarэффект потолка.

Код (см. полную версиюздесь):

CustomScrollView(
  slivers: <Widget>[
    SliverAppBar(
      // ...
    ),
    SliverPersistentHeader(	// 可以吸顶的TabBar
      pinned: true,
      delegate: StickyTabBarDelegate(
        child: TabBar(
          labelColor: Colors.black,
          controller: this.tabController,
          tabs: <Widget>[
            Tab(text: 'Home'),
            Tab(text: 'Profile'),
          ],
        ),
      ),
    ),
    SliverFillRemaining(		// 剩余补充内容TabBarView
      child: TabBarView(
        controller: this.tabController,
        children: <Widget>[
          Center(child: Text('Content of Home')),
          Center(child: Text('Content of Profile')),
        ],
      ),
    ),
  ],
)

class StickyTabBarDelegate extends SliverPersistentHeaderDelegate {
  final TabBar child;

  StickyTabBarDelegate({@required this.child});

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

  @override
  double get maxExtent => this.child.preferredSize.height;

  @override
  double get minExtent => this.child.preferredSize.height;

  @override
  bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
    return true;
  }
}

визуализация:

Из приведенного выше рисунка видно, что при следующемtabПосле того, как содержимое соскользнет с экрана,tabBarВместо того, чтобы соскользнуть, он прилип к вершине. видимыйSliverPersistentHeaderдействительно удовлетворить нашиstickyЭффект.

ноSliverPersistentHeaderМагия — это гораздо больше~ В конце концов, мы можем настроить некоторые эффекты перехода головы через него.SliverAppBarЭто также достигается через него. Например, эффект перехода головы на странице сведений о фильме ниже довольно распространен в обычных приложениях.

Так как же можно добиться этого эффекта? Дело в томbuildв методеshrinkOffsetСвойство, представляющее смещение прокрутки текущего заголовка. На его основе мы можем рассчитать текущую закрытую головку背景颜色а также иконки и текст字体颜色, чтобы вы могли получить эффект перехода в соответствии с текущей позицией~

Код (см. полную версиюздесь):

class SliverCustomHeaderDelegate extends SliverPersistentHeaderDelegate {
  final double collapsedHeight;
  final double expandedHeight;
  final double paddingTop;
  final String coverImgUrl;
  final String title;

  SliverCustomHeaderDelegate({
    this.collapsedHeight,
    this.expandedHeight,
    this.paddingTop,
    this.coverImgUrl,
    this.title,
  });

  @override
  double get minExtent => this.collapsedHeight + this.paddingTop;

  @override
  double get maxExtent => this.expandedHeight;

  @override
  bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
    return true;
  }

  Color makeStickyHeaderBgColor(shrinkOffset) {
    final int alpha = (shrinkOffset / (this.maxExtent - this.minExtent) * 255).clamp(0, 255).toInt();
    return Color.fromARGB(alpha, 255, 255, 255);
  }

  Color makeStickyHeaderTextColor(shrinkOffset, isIcon) {
    if(shrinkOffset <= 50) {
      return isIcon ? Colors.white : Colors.transparent;
    } else {
      final int alpha = (shrinkOffset / (this.maxExtent - this.minExtent) * 255).clamp(0, 255).toInt();
      return Color.fromARGB(alpha, 0, 0, 0);
    }
  }

  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    return Container(
      height: this.maxExtent,
      width: MediaQuery.of(context).size.width,
      child: Stack(
        fit: StackFit.expand,
        children: <Widget>[
          // 背景图
          Container(child: Image.network(this.coverImgUrl, fit: BoxFit.cover)),
          // 收起头部
          Positioned(
            left: 0,
            right: 0,
            top: 0,
            child: Container(
              color: this.makeStickyHeaderBgColor(shrinkOffset),	// 背景颜色
              child: SafeArea(
                bottom: false,
                child: Container(
                  height: this.collapsedHeight,
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: <Widget>[
                      IconButton(
                        icon: Icon(
                          Icons.arrow_back_ios,
                          color: this.makeStickyHeaderTextColor(shrinkOffset, true),	// 返回图标颜色
                        ),
                        onPressed: () => Navigator.pop(context),
                      ),
                      Text(
                        this.title,
                        style: TextStyle(
                          fontSize: 20,
                          fontWeight: FontWeight.w500,
                          color: this.makeStickyHeaderTextColor(shrinkOffset, false),	// 标题颜色
                        ),
                      ),
                      IconButton(
                        icon: Icon(
                          Icons.share,
                          color: this.makeStickyHeaderTextColor(shrinkOffset, true),	// 分享图标颜色
                        ),
                        onPressed: () {},
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Вышеприведенный код длинный, но в основном строитwidgetкод. Поэтому мы ориентируемся наmakeStickyHeaderTextColorиmakeStickyHeaderBgColorВот и все. Оба метода основаны на текущемshrinkOffsetvalue вычисляет значение цвета во время перехода. Кроме того, здесь следует отметить, что голова находится вiPhoneXИ над дизайном головы челки вы можете использоватьSafeAreaКомпоненты решают проблему.

6. Резюме

Эта статья впервые знакомитCustomScrollViewиSliverПонятие о компонентах ряда и их взаимосвязях, сопровождаемоеSliverListиSliverGridКомбинированный пример иллюстрирует его использование. Затем чаще используетсяSliverAppBarкомпоненты, объясняя ихfloat/snap/pinnedсоответствующие эффекты. Наконец-то объяснилSliverPersistentHeaderКак использовать компонент и использовать практические примеры, чтобы проиллюстрировать использование его пользовательского эффекта перехода. Я надеюсь, что благодаря введению этой статьи вы сможете использоватьCustomScrollViewиSliverКомпоненты серии создают более интересные эффекты прокрутки~

Весь код в этой статье размещен по адресуздесь, вы также можете подписаться на меняBlog, добро пожаловать, чтобы обменяться и учиться вместе~