Элементы управления прокруткой Flutter -> Мониторинг и контроль прокрутки (ScrollController)

Flutter

В предыдущей статье об элементах управления прокруткой мы упоминалиcontrollerатрибут, он получаетScrollControllerобъект.ScrollControllerОсновная роль заключается в управлении положением прокрутки и прослушивании событий прокрутки.

Эта глава начинается сListViewНапример, показатьScrollControllerконкретное использование

ScrollController

ScrollControllerУправляйте положением прокрутки и слушайте события прокрутки.

Пример исходного кода

Конструктор выглядит следующим образом:

ScrollController({
  double initialScrollOffset = 0.0, //初始滚动位置
  this.keepScrollOffset = true,//是否保存滚动位置
  ...
})

объяснение атрибута

Значение конкретных атрибутов было указано выше, вот введениеScrollControllerЧасто используемые свойства и методы

offset

Это свойство представляет текущую позицию прокрутки прокручиваемого компонента.

прыгать, анимировать

jumpTo(double offset),animateTo(double offset,...): Оба эти метода используются для прыжка в указанную позицию, разница в том, что последний выполняет анимацию при прыжке, а первый нет.

монитор прокрутки

ScrollControllerКосвенно унаследовано отListenable, мы можем согласноScrollControllerЧтобы прослушать события прокрутки,

Например:

controller.addListener(()=>print(controller.offset))

Этот код печатает текущую позицию прокрутки

Пример кода:

мы создаемListView, при изменении позиции прокрутки мы сначала распечатываем текущую позицию прокрутки, а затем оцениваем, превышает ли текущая позиция 1000 пикселей.Если она превышает, в правом нижнем углу экрана будет отображаться кнопка «Вернуться к началу».ListViewВернитесь в исходное положение, если не более 1000 пикселей, скройте кнопку «наверх».

import 'package:flutter/material.dart';

class CategoryPage extends StatefulWidget {
  @override
  _CategoryPageState createState() => _CategoryPageState();
}

class _CategoryPageState extends State<CategoryPage> {
  ScrollController _controller = new ScrollController();
  bool showToTopBtn = false; //是否显示“返回到顶部”按钮

  @override
  void initState() {
    //监听滚动事件,打印滚动位置
    _controller.addListener(() {
      print(_controller.offset); //打印滚动位置
      if (_controller.offset < 1000 && showToTopBtn) {
        setState(() {
          showToTopBtn = false;
        });
      } else if (_controller.offset >= 1000 && showToTopBtn == false) {
        setState(() {
          showToTopBtn = true;
        });
      }
    });
  }

  @override
  void dispose() {
    //为了避免内存泄露,需要调用_controller.dispose
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("滚动控制")),
      body: Scrollbar(
        child: ListView.builder(
            itemCount: 100,
            itemExtent: 50.0, //列表项高度固定时,显式指定高度是一个好习惯(性能消耗小)
            controller: _controller,
            itemBuilder: (context, index) {
              return ListTile(
                title: Text("$index"),
              );
            }),
      ),
      floatingActionButton: !showToTopBtn
          ? null
          : FloatingActionButton(
              child: Icon(Icons.arrow_upward),
              onPressed: () {
                //返回到顶部时执行动画
                _controller.animateTo(.0,
                    duration: Duration(milliseconds: 200), curve: Curves.ease);
              }),
    );
  }
}

Так как высота элемента списка составляет 50 пикселей, при пролистывании 20-го элемента списка в правом нижнем углу будет отображаться кнопка «Вернуться к началу».Нажав эту кнопку, ListView будет выполнять анимацию прокрутки в процессе возвращается наверх, а время анимации составляет 200 миллисекунд. , кривая анимацииCurves.ease

текущий результат:

восстановление положения прокрутки

PageStorageЯвляется ли компонент, используемый для сохранения данных, связанных со страницей (маршрутизацией), он не влияет на внешний вид пользовательского интерфейса поддерева.

Каждый раз, когда прокрутка заканчивается, прокручиваемый компонент меняет положение прокрутки.offsetхранить вPageStorage, восстановить при воссоздании прокручиваемого компонента.

еслиScrollController.keepScrollOffsetдляfalse, позиция прокрутки не будет сохранена и будет использоваться при воссоздании прокручиваемого компонентаScrollController.initialScrollOffset;
когдаScrollController.keepScrollOffsetдляtrue, прокручиваемый компонент находится впервый разПосле создания прокрутите доinitialScrollOffset, так как позиция прокрутки еще не сохранена. При следующем прокрутке позиция прокрутки сохраняется и восстанавливается, иinitialScrollOffsetбудет игнорироваться.

Когда маршрут содержит несколько прокручиваемых компонентов, если вы обнаружите, что положение прокрутки не может быть правильно восстановлено после выполнения некоторых переходов или операций переключения, вы можете явно указатьPageStorageKeyотслеживать положение различных прокручиваемых компонентов по отдельности,
Такие как:

ListView(key: PageStorageKey(1), ... );
...
ListView(key: PageStorageKey(2), ... );

разныеPageStorageKey, требует другого значения, чтобы положение прокрутки можно было сохранить для различных прокручиваемых компонентов.

Примечание. Когда маршрут содержит несколько прокручиваемых компонентов, если вы хотите отслеживать их положения прокрутки отдельно, вам не нужно указывать их отдельно.PageStorageKey. Это связано с тем, что Scrollable сам по себе является StatefulWidget, и его состояние также сохраняет текущую позицию прокрутки, пока сам прокручиваемый компонент не удаляется из дерева.detachЕсли его отбросить, то его состояние не будет уничтожено (удалено), и позиция прокрутки не будет потеряна. Состояние теряется только при изменении структуры виджета, что приводит к разрушению или перестроению состояния прокручиваемого компонента, в этом случае его нужно указать явно.PageStorageKey,пройти черезPageStorageдля хранения положения прокрутки типичным сценарием является использованиеTabBarViewПри переключении вкладки состояние прокручиваемого компонента на странице вкладки будет уничтожено.В это время, если вы хотите восстановить положение прокрутки, вам нужно указатьPageStorageKey.

ScrollPosition

ScrollPositionиспользуется для сохранения положения прокрутки прокручиваемого компонента.

ОдинScrollControllerОбъекты могут использоваться несколькими прокручиваемыми компонентами одновременно,ScrollControllerсоздаст по одному для каждого прокручиваемого компонентаScrollPositionобъекты, этиScrollPositionСохранить какScrollControllerизpositionsв свойствах.

ScrollPositionэто объект, который фактически сохраняет информацию о позиции скольжения,offsetПросто свойство удобства:
double get offset => position.pixels;
Как видно из вышеизложенногоoffectизpositionиз.

ОдинScrollControllerХотя это может соответствовать нескольким прокручиваемым компонентам, существуют некоторые операции, такие как чтение положения прокрутки.offset, вам нужно один к одному!
Но мы все еще можем прочитать положение прокрутки с помощью других методов в случае «один ко многим»,
Например, предположим,ScrollControllerИспользуется двумя прокручиваемыми компонентами одновременно, тогда мы можем читать их позиции прокрутки отдельно следующими способами:

...
controller.positions.elementAt(0).pixels
controller.positions.elementAt(1).pixels
...

мы можем пройтиcontroller.positions.lengthЧтобы убедитьсяcontrollerИспользуется несколькими прокручиваемыми компонентами.

ScrollPositionЕсть два распространенных метода:animateTo()а такжеjumpTo(), они являются реальным способом управления положением прокрутки прыжка,ScrollControllerЭти два метода с одинаковыми именами в конечном итоге будут вызываться внутриScrollPositionиз.

должны знать о том,ScrollControllerизanimateTo()а такжеjumpTo()Внутренние вызовы всехScrollPositionизanimateTo()а такжеjumpTo(), добиться всего иScrollControllerСвязанные прокручиваемые компоненты прокручиваются до указанной позиции.

Пример слушателя прокрутки

Далее мониторимListViewЗатем в уведомлении о прокрутке отображается текущий процент выполнения прокрутки:

import 'package:flutter/material.dart';

class ScrollNotificationTestRoute extends StatefulWidget {
  @override
  _ScrollNotificationTestRouteState createState() =>
      new _ScrollNotificationTestRouteState();
}

class _ScrollNotificationTestRouteState
    extends State<ScrollNotificationTestRoute> {
  String _progress = "0%"; //保存进度百分比

  @override
  Widget build(BuildContext context) {
    return Scrollbar( //进度条
      // 监听滚动通知
      child: NotificationListener<ScrollNotification>(
        onNotification: (ScrollNotification notification) {
          double progress = notification.metrics.pixels /
              notification.metrics.maxScrollExtent;
          //重新构建
          setState(() {
            _progress = "${(progress * 100).toInt()}%";
          });
          print("BottomEdge: ${notification.metrics.extentAfter == 0}");
          //return true; //放开此行注释后,进度条将失效
        },
        child: Stack(
          alignment: Alignment.center,
          children: <Widget>[
            ListView.builder(
                itemCount: 100,
                itemExtent: 50.0,
                itemBuilder: (context, index) {
                  return ListTile(title: Text("$index"));
                }
            ),
            CircleAvatar(  //显示进度百分比
              radius: 30.0,
              child: Text(_progress),
              backgroundColor: Colors.black54,
            )
          ],
        ),
      ),
    );
  }
}

текущий результат:

Дочерние виджеты в дереве виджетов Flutter могут взаимодействовать с родительскими (включая предков) виджетами, отправляя уведомления. Родительский компонент может пройтиNotificationListenerКомпоненты для прослушивания уведомлений об их собственном внимании, этот метод связи похож на всплывающую подсказку событий браузера в веб-разработке.

Прокручиваемые компоненты отправляются при прокруткеScrollNotificationтип уведомления,ScrollBarОн делает это, прослушивая уведомления о прокрутке. пройти черезNotificationListenerСлушайте события прокрутки и пройтиScrollControllerЕсть два основных отличия:

  • пройти черезNotificationListenerВы можете слушать где угодно, от прокручиваемого компонента до корня дерева виджетов. а такжеScrollControllerОн может быть связан только с определенным прокручиваемым компонентом.

  • Информация, полученная после получения события прокрутки, отличается;NotificationListenerПри получении события прокрутки уведомление будет содержать текущую позицию прокрутки иViewPortнекоторую информацию, покаScrollControllerМожно получить только текущую позицию прокрутки.

Когда получено событие прокрутки, тип параметраScrollNotification, который включает в себяmetricsсвойство, его типScrollMetrics, который содержит текущийViewPortи положение прокрутки и другую информацию:

  • pixels: Текущая позиция прокрутки.
  • maxScrollExtent: Максимальная прокручиваемая длина.
  • extentBefore: длина слайда из верхней части окна просмотра; в этом примере она эквивалентна длине списка над верхней частью слайда из экрана.
  • extentInside: внутренняя длина ViewPort; в данном примере это длина области списка на экране.
  • extentAfter: Длина части списка, которая не перемещается в ViewPort; длина части экрана, которая не отображается в нижней части списка в этом примере.
  • atEdge: следует ли скользить к границам прокручиваемого компонента (эквивалентно верхней или нижней части списка в этом примере).

P_P