Подробный обзор виджета ListView от Flutter

Прокрутка. В наши дни это все, что люди делают - пролистывают свои телефоны. Как оказалось, это важная возможность для мобильных приложений. Давайте разберемся, как выполнять прокрутку во Flutter с помощью виджета ListView.

Как видно из иерархии классов ниже, виджет ListView состоит из множества элементов. Мы начнем с основ и перейдем к более сложным функциям.

Мне нравятся скриншоты. Щелкните для Gists.

Как всегда, я предпочитаю использовать скриншоты, а не суть, чтобы показать концепции, а не просто показывать код в своих статьях. Откровенно говоря, я считаю, что с ними легче работать. Однако вы можете щелкнуть или коснуться этих снимков экрана, чтобы увидеть код, который они представляют, в сущности или в Github. По иронии судьбы, эту статью о мобильной разработке лучше читать на компьютере, чем на телефоне. Кроме того, мы программируем на наших компьютерах, а не на телефонах. Теперь.

Никаких движущихся изображений, никаких социальных сетей

В этой статье будут файлы gif, демонстрирующие аспекты рассматриваемой темы. Однако сказано, что просмотр таких файлов gif невозможен при чтении этой статьи на таких платформах, как Instagram, Facebook и т. Д. Они могут выглядеть как статические изображения или просто пустые поля-заполнители. Помните об этом и, возможно, прочтите эту статью на medium.com.

Давай начнем.

Начнем с основного списка. Это конечный список. И, как и все, что использует виджет ListView, это список, состоящий из других виджетов. Они называются дочерними виджетами. Этот список можно найти в примере из Поваренной книги Flutter с подходящей маркировкой Базовый список. И человек! Это когда-нибудь! Всего три виджета ListTile.

Это только дети

В этом очень простом примере вызывается только именованный параметр children, который предоставляет список виджетов. Все остальные параметры, относящиеся к тому, как пользователь может прокручивать этих дочерних элементов, и что происходит, когда дочерний элемент прокручивается вне поля зрения, оставлены в покое - поэтому для некоторых установлены значения по умолчанию. Ниже приведен снимок экрана класса ListView и его базового конструктора.

Для этого класса доступно еще несколько конструкторов. Они называются именованными конструкторами и предлагают разработчику дополнительные функции и возможности. Мы скоро до них доберемся.

От ребенка к родителю

Как вы видите на скриншоте выше, большинство параметров передаются родительскому классу BoxScrollView и так далее. Многие вернулись к классу ScrollView.

Да, многие из этих параметров играют важную роль в глубине иерархии классов. Он находится в функции build () класса ScrollView, где создается прокручиваемый список виджетов. Однако в этой статье мы не будем углубляться в иерархию классов. ListView - очень популярный виджет для разработчиков, поэтому мы будем придерживаться класса ListView, а не его родительских классов. Это для следующих статей, которые когда-нибудь появятся.

По горизонтали

Немного по сравнению с первым примером, но рядом есть еще один пример Cookbook, в котором также используется второй параметр. Он называется Пример создания горизонтального списка, и в этом примере вместо вертикальной прокрутки вы можете прокручивать по горизонтали. Этот дополнительный именованный параметр называется scrollDirection. Вместо значения по умолчанию Axis.vertical объекту ListView в этом примере присваивается значение Axis.horizontal.

Горизонтально с ограничениями

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

Список в обратном порядке

Если установлено значение Axis.vertical, элементы перечисляются сверху вниз. Однако, если для другого именованного параметра, reverse, установлено значение true, эти элементы будут перечислены снизу вверх. Давайте посмотрим на это на нашем первом примере.

Если для именованного параметра scrollDirection задано значение Axis.horizontal, то направление чтения будет слева направо - элементы будут перечислены слева направо. Опять же, если для именованного параметра reverse задано значение true, а не значение по умолчанию, false, направление чтения будет справа налево.

Только те, кого видели

Конструктор, который до сих пор использовался для создания экземпляра объекта ListView, подходит для коротких списков с простыми виджетами в качестве дочерних. Это потому, что все виджеты, составляющие список, создаются тут же. Однако обычно более эффективно создавать дочерние элементы на лету, конструируя только те, которые фактически будут видны пользователю, и уничтожая уже показанные. Это делается с помощью конструктора ListView, L istView.builder.

С этого момента мы будем использовать этот конструктор строитель. Мы воспользуемся еще одним примером из Поваренной книги, Работа с длинными списками, и посмотрим, сможем ли мы лучше продемонстрировать оставшиеся параметры. Единственное реальное отличие от базового конструктора и параметров этого конструктора состоит в том, что именованный параметр children типа List ‹Widget› теперь заменен параметром с именем itemBuilder. , класс типа IndexedWidgetBuilder.

Строитель длинных списков

Это тоже простой пример. Тем не менее, он создает десять тысяч объектов String, начинающихся со слова «item», а затем добавляется текущий счетчик в конце. Это длинный список. По общему признанию, не так уж и много для оригинального конструктора ListView, но это только потому, что он состоит из очень-очень простых виджетов. Однако это потребовало бы ненужного использования памяти. И если список виджетов был более сложным или этот список был не менее чем бесконечно длинным, конструктор «строитель» - действительно ваш лучший выбор. Это экономит ресурсы. Строим только то, что будет видно; уничтожая то, что больше не видно, но повторно использует то, что может. Они называют это «ленивым построением».

Это делегировано

И в базовом конструкторе, и в конструкторе builder дочерние виджеты передаются другим классам, которым делегировано возвращение Sliver прокручиваемой области, которая содержит один из дочерних виджетов. Ниже представлена ​​суть этих классов - соответствующие им функции build (). Слева класс, который использует конструктор строитель, SliverChildBuilderDelegate, а справа базовая версия ListView использует класс SliverChildListDelegate.

Прокрутка виджетов

Каждая функция build () возвращает виджет. Версия «Builder» немного длиннее, поскольку ей приходится бороться с подпрограммой «Builder», в то время как «базовая» версия предоставляет конечный список уже созданных «дочерних» виджетов. Как и «строительная» версия, «базовая» версия также имеет параметр индекса. Однако «базовая» версия просто переходит к списку предоставленных дочерних виджетов и выбирает один по этому индексу. В то время как «строительная» версия передает индекс в подпрограмму построителя.

Обратите внимание, есть третий дополнительный конструктор ListView, который также использует класс, SliverChildBuilderDelegate. Он называется ListView.separated, и он также создает прокручиваемый список виджетов, но каждый из них разделен указанными разделителями. Таким образом, все три конструктора создают полосу виджетов (Slivers), которые составляют прокручиваемую область ListView. объект.

Три типа щепок

По умолчанию каждый такой виджет заключен или «завернут» в три других класса. Эти три других класса напрямую влияют на фактическую прокрутку вложенных виджетов и их представление пользователю при прокрутке.

Как вы видите ниже, как в «конструкторе», так и в «базовом» конструкторе ListView, по умолчанию все три класса имеют связанные логические свойства, для которых установлено значение true. Давайте быстро познакомим вас с каждым из этих свойств в порядке их реализации.

Перерисовывать при прокрутке

Первое - это свойство addRepaintBoundaries. По умолчанию для повышения производительности каждый элемент ленты не перекрашивается при прокрутке. В самом элементе ничего не изменилось при прокрутке, поэтому нет необходимости перерисовывать его на экране при перемещении. Принимая исходный дочерний виджет, создается новый дочерний виджет типа класса RepaintBoundary. Однако предполагается, что в некоторых случаях, если список состоит из простых дочерних виджетов - например, одного сплошного цвета или коротких фрагментов текста, может быть еще более эффективным установить addRepaintBoundaries в значение false и разрешить перекрашивание дочерних виджетов по мере их прокрутки. Однако в большинстве случаев в этом нет необходимости.

Сделайте вашу прокрутку голосом

Следующее свойство, addSemanticIndexes, предоставляет средство для лингвистических программ, таких как TalkBack или Voiceover, для объявления пользователям с ослабленным зрением сообщений во время прокрутки элементов. Например, позволяя таким программам сообщать пользователю, сколько элементов в настоящее время видно. По умолчанию для этой способности установлено значение true.

Оставайся в живых, когда вне поля зрения

Последнее свойство, addAutomaticKeepAlives, также имеет значение true по умолчанию. Вместо того, чтобы быть кандидатом на сборку мусора, когда элемент прокручивается за пределы экрана и теперь находится вне поля зрения пользователя, они просто сохраняются в памяти. Предполагается, что при прокрутке взад и вперед такие предметы снова появятся в поле зрения, что сделает их восстановление излишне дорогостоящим. В противном случае, если для свойства установлено значение false, они могут быть уничтожены (удалены из памяти) при прокрутке за пределы экрана.

Строитель строит

Итак, в нашем маленьком примере с длинным списком, когда свойство builder встречается (слева внизу) в функции build () SliverChildBuilderDelegate , вызывается анонимная функция, назначенная названному параметру itemBuilder (справа внизу). Затем он возвращает объект Text, назначенный внутри объекта ListTile. Это, в зависимости от значения этих трех логических свойств, далее оборачивается одно за другим в новый виджет, который, наконец, считается частью прокручиваемого списка.

Обратите внимание, что анонимная функция, назначенная указанному параметру, itemBuilder, может содержать любое количество классов, функций и виджетов любой степени сложности, как показывают некоторые дальнейшие примеры, приведенные ниже. Он может даже содержать другой ListView.builder, как показано в последнем примере. Опять же, чтобы сэкономить на производительности, он не создаст все возможные фрагменты, а только те, которые стали видны при прокрутке пользователя.

Управление прокруткой

Теперь продолжим рассмотрение остальных параметров, доступных вам при использовании класса ListView. Следующим после параметра reverse является параметр controller. Это касается класса ScrollController. Что именно так: управляет прокруткой.

С его помощью вы можете указать, чтобы прокручиваемый список начинался с определенной позиции, а не с начала, как по умолчанию. Например, он может начинаться с того места, где вы остановились, когда последний раз прокручивали приложение.

С его помощью вы можете включать «слушателей» в свой прокручиваемый список. Тогда вы будете «знать», когда вы прокручиваете, когда вы перестали прокручивать и где вы перестали прокручивать.

Теперь я нагло украл несколько примеров у джентльмена-медиума. Мне некуда было придумать свое. Он написал замечательную статью на тему виджета ListView и его ScrollController, и я почувствовал, что это отличные примеры, демонстрирующие лишь некоторые из функций, доступных вам при использовании ScrollController в ваших собственных ListViews. Я, конечно, уведомлю его о своем усмотрении.

В этом есть суть

Я составил составной пример ListViewScrollController.dart, включающий все три функции, показанные выше. Опять же, всего лишь несколько возможностей, доступных вам с помощью ScrollController с вашим ListView.

Ты где?

Мы рассмотрим каждый пример слева направо. В первом вы видите, что при прокрутке вверх списка заголовок приложения изменится на Достигнуто наверху. Для этого в ListView.builder должен быть предоставлен объект ScrollController и назначен слушатель. в примере функции initState () создается экземпляр ScrollController, которому назначается такой слушатель.

У этого слушателя есть средства определить, где в любой момент времени мы находимся в прокручиваемом списке, используя свойство ScrollController, смещение. Смещение - это двойное значение, указывающее накопительное расстояние в логических пикселях от начала прокручиваемого списка. Начало списка имеет смещение 0,0. После создания экземпляр объекта ScrollController принимает именованный параметр ListView, controller.

Логические пиксели имеют примерно одинаковый визуальный размер на всех устройствах, в то время как физические пиксели - это размер фактических аппаратных пикселей на устройстве. Логические пиксели должны быть независимыми от устройства и разрешения.

Смещение с самого начала

В большинстве случаев ListView начинается со смещения 0,0. Другими словами, начните с начала прокручиваемого списка. Конечно, с объектом ScrollController можно начинать где угодно. Его конструктор приспособит это к именованному параметру initialScrollOffset. Обратите внимание, что по умолчанию используется 0,0.

Достичь вершины

А теперь вернемся к нашему примеру. Глядя на процедуру Listener, определенную и представленную ListView функцией addListener (), мы видим, что ScrollController знает начало прокручиваемого списка, используя значение из con. position.minScrollExtent. Как вы уже догадались - его значение будет 0,0. Итак, если текущее смещение (con.offset) равно 0,0, вы достигли начала списка. Таким образом, название изменено соответствующим образом.

Автоматизировать прокрутку

В следующем примере используется автоматическая прокрутка с использованием объекта ScrollController. В нашем примере используется небольшая автоматизация.

Как вы видите на скриншоте выше, мы определяем функцию под названием rowButtons (), которая возвращает виджет, содержащий кнопки «вверх» и «вниз», представленные во втором примере. Также в коде есть функция ScrollController, animatTo (), которая отвечает за анимацию. Первый параметр в функции указывает смещение для «перехода к» от текущей позиции смещения в прокручиваемом списке. В этом примере мы просто перемещаемся на «ширину» перечисленного дочернего виджета itemSize. Вы можете увидеть, как это делается, «прибавив» или «вычтя» указанный размер.

Параметр curve описывает "как" движется анимация: ускоряется ли она при движении или замедляется при движении. В этом примере анимация выполняется с постоянной скоростью с параметром Curves.linear. Это параметр duration, который определяет фактическую скорость, поскольку он устанавливает время, необходимое для завершения анимации.

Обратите внимание, что функция jumpTo (), закомментированная на снимке экрана, перемещает прокручиваемый список со вспышкой и без анимации. Не так аккуратно.

Как видно на снимке экрана выше, функция rowButtons () размещается над прокручиваемым списком, созданным конструктором ListView.builder. В конструктор вводится новый параметр itemExtent, который определяет «ширину» каждого «дочернего» виджета, указанного в прокручиваемом списке. Он описывается как «размер ребенка по главной оси». Другими словами, ширина по вертикали или горизонтали зависит от того, установлен ли параметр ListView, scrollDirection, Axis.vertical, или Axis.horizontal .

В документации II сказано, что для повышения производительности всегда указывать свойство itemExtent при создании ListViews, поскольку это избавляет Flutter от необходимости каждый раз вычислять ширину.

Избавиться от контроллера

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

Начальный

Следующий параметр называется primary. Он используется, чтобы указать, является ли это основным представлением прокрутки, связанным с родительским PrimaryScrollController. Это означает, что не должно быть параметра ScrollController, если для primary установлено значение true.

Когда это правда, представление прокрутки можно прокручивать, даже если в нем недостаточно содержимого для фактической прокрутки. В противном случае по умолчанию пользователь может прокручивать представление только в том случае, если в нем достаточно содержимого. В iOS, если установлено значение true, это также определяет представление прокрутки, которое будет прокручиваться вверх в ответ на «касание» в строке состояния.

Физика прокрутки

Следующий параметр, Physics, определяет, как будет вести себя прокручиваемый список, когда пользователь достигает начала или конца прокрутки, и как он ведет себя, когда пользователь «пойдем» и какое-то время продолжает прокрутку. . Он принимает объект класса типа ScrollPhysics.

В настоящее время существует четыре подкласса класса ScrollPhysics, предоставленных вашему виджету ListView. Три из них показаны выше. Первый эмулирует поведение, наблюдаемое в Apple iPhone, BouncingScrollPhysics. Второй, ClampingScrollPhysics, дает свечение индикации «перепрокрутки», обычно наблюдаемое на телефонах Android. Последний продемонстрированный метод называется NeverScrollableScrollPhysics, полностью отключающий возможность прокрутки. Не отображается AlwaysScrollableScrollPhysics. Он обеспечивает соответствующее поведение в зависимости от платформы: Android или iOS.

Свернуть свиток в термоусадочную пленку

Далее следует логический именованный параметр shrinkWrap. В документации по умолчанию то, что они называют «просмотром прокрутки» (т. Е. Я вызывал прокручиваемый список), будет занимать все доступное пространство, чтобы соответствовать размеру родительского элемента. Даже если список предметов требует меньше места. Вы можете видеть это в исходном примере «Базового списка», с которого мы начали. Чтобы показать его размер, я обернул «вид прокрутки» толстой красной рамкой, определяемой его родительским элементом, виджетом-контейнером.

Как указано в документации, «Если вид прокрутки не сжимается, то вид прокрутки расширяется до максимально допустимого размера в [scrollDirection]». - В этом примере это вертикальное направление. Если вы установите для свойства shrinkWrap значение true, как мы делали выше, прокручиваемый список (или представление с прокруткой) обернет свое содержимое и будет настолько большим, насколько позволяют его дочерние виджеты. Опять же, вы можете легко увидеть новый размер с толстой красной рамкой.

В документации также отмечается снижение производительности, если для this property установлено значение true: Сжатие упаковки содержимого представления прокрутки значительно дороже, чем расширение до максимально допустимого размера, потому что содержимое может расширяться и сжиматься во время прокрутки, что означает размер окна прокрутки необходимо пересчитывать при изменении положения прокрутки .

Заполнение дочерних элементов свитка

Flutter регулярно заполняет свои виджеты пустыми пространствами с помощью класса EdgeInsetsGeometry. Например, если не указано иное, всегда выполняется небольшое заполнение вокруг дочерних виджетов, перечисленных в ListView Widget.

На скриншоте выше у нас есть виджеты карточек, составляющие прокручиваемый список, чтобы лучше проиллюстрировать отступы. Слева направо двойное значение, передаваемое объекту EdgeInsetsGeometry, - от 0,0 до 16,0, а затем до 100,0.

Есть кеш

Следующий параметр, который мы рассмотрим, - это cacheExtent. Есть область непосредственно перед и сразу после «видимой» части прокручиваемой области или «области просмотра», как они описывают ее в документации, которая содержит содержимое, размещенное, хотя еще не видимое на экране. CacheExtent - это свойство типа double. Это «количество логических пикселей», которые нарисованы в этих двух кэшированных областях, но еще не видны на экране. Чем больше числовое значение, тем больше «закрашено» в кэш-памяти, которое скоро станет видимым, когда пользователь прокручивает страницу.

Это перетаскивание

Последний именованный параметр, dragStartBehavior, может иметь один из двух возможных перечислимых типов: DragStartBehavior.start и DragStartBehavior.down. По умолчанию установлено значение DragStartBehavior.start. Это означает, что поведение перетаскивания при прокрутке начнется при обнаружении жеста перетаскивания. Если установлено значение
DragStartBehavior.down, поведение при перетаскивании при прокрутке начнется при первом обнаружении события щелчка мыши. Говорят, что установка DragStartBehavior.start сделает анимацию перетаскивания более плавной. В то время как DragStartBehavior.down, как сказано, сделает "поведение перетаскивания при прокрутке" немного более реактивным.

А пока оставим все как есть. Сделай перерыв. Ты заслуживаешь это.

Ваше здоровье.

* Исходный код по состоянию на 27 марта 2019 г.
+ Исходный код по состоянию на 9 марта 2019 г.
^ Исходный код по состоянию на 1 марта 2019 г.
# Исходный код по состоянию на 21 февраля 2019 г.

→ Другие рассказы Грега Перри