1. Введение
Flutter
Как одна из самых популярных технологий в настоящее время, она уже привлекла внимание большого количества энтузиастов технологий, и даже некоторых闲鱼
,美团
,腾讯
И другие крупные компании были введены в эксплуатацию. Хотя его экология еще не полностью созрела,Google
Благо, скорость его развития уже достаточно поразительна, можно предвидеть, что в будущемFlutter
Спрос на разработчиков также будет расти.
Будь то первопроходцы технологий или будущие тенденции, прошло 9102 года.Как фронтенд-разработчику, кажется, нет причин не попробовать это. Именно с таким менталитетом автор и начал изучатьFlutter
, и построилсклад, весь последующий код будет размещаться на нем, добро пожаловать звездочка, учитесь вместе. Это серия статей о Flutter, которую я написал:
- Создание красивых интерфейсов пользовательского интерфейса с помощью Flutter — основные компоненты
- Компонент контейнера прокрутки Flutter — ListView
- Компоновка сетки Flutter — статьи GridView
- Использование пользовательского значка во Flutter
в предыдущем постестатья, мы изучилиFlutter
Некоторые из наиболее часто используемых основных компонентов. Но в некоторых случаях, когда ширина или высота компонента выходит за пределы экрана,Flutter
часто даетoverflow
Предупреждение о том, что компонент выходит за пределы экрана. Чтобы решить эту проблему, сегодня мы изучим наиболее часто используемый滚动型组件
—ListView组件
.
2. Как использовать ListView
Из функционального сравнения,Flutter
серединаListView
компоненты иRN
серединаScrollView
/FlatList
Очень похожи, но немного отличаются в том, как они используются. Далее, следуйте за мной, чтобы увидетьListView
Каковы общие методы использования компонентов.
2.1 ListView()
Первый способ использовать его — напрямую вызвать его默认构造函数
для создания списка эффект эквивалентенRN
серединаScrollView
компоненты. Но есть проблема со списками, созданными таким образом:для тех长列表
или нужно较昂贵渲染开销
, даже если его еще нет на экране, он все равно будет называтьсяListView
Создан, это будет большой накладные расходы, а неправильное использование может вызвать проблемы с производительностью или даже зависания..
Но опять же, хотя у этого метода могут быть проблемы с производительностью, это зависит от различных сценариев его применения. Давайте взглянем на его конструктор (нечастые свойства опущены):
ListView({
Axis scrollDirection = Axis.vertical,
ScrollController controller,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
this.itemExtent,
double cacheExtent,
List<Widget> children = const <Widget>[],
})
-
scrollDirection
: Направление прокрутки списка, необязательные значенияAxis
изhorizontal
а такжеvertical
, вы можете видеть, что по умолчанию используется вертикальная прокрутка; -
controller
: Контроллер, связанный с прокруткой списка, например прослушивание событий прокрутки списка; -
physics
: физический эффект перетаскивания после прокрутки списка до края,Android
а такжеiOS
Эффект разный.Android
появится как рябь (соответствуетClampingScrollPhysics
),а такжеiOS
Существует эффект подпрыгивания на (соответствуетBouncingScrollPhysics
). Если вы хотите представить разные эффекты на разных платформах, вы можете использоватьAlwaysScrollableScrollPhysics
, он автоматически выберет собственный физический эффект в соответствии с различными платформами. Если вы хотите отключить эффект перетаскивания на краю, вы можете использоватьNeverScrollableScrollPhysics
; -
shrinkWrap
: 该属性将决定列表的长度是否仅包裹其内容的长度。 когдаListView
При встраивании в бесконечно длинный компонент-контейнерshrinkWrap
должно быть правдой, иначеFlutter
выдаст предупреждение; -
padding
: заполнение списка; -
itemExtent
: длина дочернего элемента. Это значение может быть указано, когда длина каждого элемента в списке фиксирована, что может помочь улучшить производительность списка (поскольку это может помочьListView
Вычислить положение каждого элемента перед фактическим рендерингом дочерних элементов); -
cacheExtent
: Предварительно визуализированная длина области,ListView
оставит один по обе стороны от видимой областиcacheExtent
длина региона как пререндеренного региона (дляListView.build
илиListView.separated
Список, созданный конструктором, дочерние элементы, которые не находятся в видимой области и пререндеренной области, не будут созданы или уничтожены); -
children
: Массив компонентов, содержащий дочерние элементы.
Вышеупомянутые свойства представляют многое, и они не так реальны, как реальный пример. мы можем использоватьListView
компонент для обертывания реализации в предыдущей статьебанковская карта,карточка для домашних животных,Круг друзейЭти три примера:
код (адрес файла)
class NormalList extends StatelessWidget {
const NormalList({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
CreditCard(data: creditCardData),
PetCard(data: petCardData),
FriendCircle(data: friendCircleData),
],
);
}
}
предварительный просмотр
можно увидеть,默认构造函数
Использование очень простое, поместите компонент дочернего элемента непосредственно вchildren
Массивы в порядке. Но потенциальные проблемы были объяснены ранее, поскольку长列表
Этот сценарий приложения все еще следует использоватьListView.build
Производительность конструктора будет лучше.
2.2 ListView.build()
ListView
Конструктор по умолчанию прост в использовании, но не подходит для длинных списков. Для этого рассмотримListView.build
Конструктор:
ListView.builder({
...
int itemCount,
@required IndexedWidgetBuilder itemBuilder,
})
Редко используется иListView
Конструктор по умолчанию повторяет некоторые параметры, в отличие от них мы можем найтиListView.builder
Два новых параметра:
-
itemCount
: количество элементов в списке; -
itemBuilder
: метод рендеринга дочерних элементов, позволяющий создавать пользовательские компоненты дочерних элементов (эквивалентноrn
серединаFlatList
компонентrenderItem
Атрибуты).
отличный отListView
Конструктор по умолчанию передаетсяchildren
Таким образом параметр указывает дочерние элементы,ListView.build
раскрывая единыйitemBuilder
Метод возвращает управление отрисовкой дочерних элементов обратно вызывающей стороне. Здесь мы используем пример общедоступной учетной записи WeChat, чтобы проиллюстрироватьListView.build
Как пользоваться (стиль-макет официальной карточки аккаунта можно посмотретьздесь, что также является закреплением и обзором основных компонентов):
код (адрес файла)
class SubscribeAccountList extends StatelessWidget {
const SubscribeAccountList({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
color: Color(0xFFEFEFEF),
child: ListView.builder(
itemCount: subscribeAccountList.length,
itemBuilder: (context, index) {
return SubscribeAccountCard(data: subscribeAccountList[index]);
},
),
);
}
}
предварительный просмотр
Как видно из приведенного выше кода,ListView.build
Два наиболее важных параметра для создания списка:itemCount
а такжеitemBuilder
. Например, для списка официальной учетной записи, поскольку макет каждой карточки сообщения официальной учетной записи является обычным, и количество этого списка может быть очень большим, поэтому используйтеListView.build
Это не может быть более подходящим для создания.
2.3 ListView.separated()
Мы можем использовать большинство требований класса спискаListView.build
Конструктор для решения проблемы, но некоторые элементы списка должны быть между分割线
, то мы можем использоватьFlutter
предоставлен другой конструкторListView.separated
для создания списка. Давайте посмотрим, чем отличается его конструктор:
ListView.separated({
...
@required IndexedWidgetBuilder separatorBuilder
})
в сравнении сListView.build
конструктор, вы можете видетьListView.separated
еще одинseparatorBuilder
Обязательный параметр. Как следует из названия, это метод обратного вызова, предоставляемый пользовательскому компоненту-разделителю вызывающей стороны. В качестве примера возьмем список друзей Alipay (стиль карты друга можно увидеть наздесь), Давайте посмотрим наListView.separated
Как использовать:
код (адрес файла)
class FriendList extends StatelessWidget {
const FriendList({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView.separated(
itemCount: friendListData.length,
itemBuilder: (context, index) {
return FriendCard(data: friendListData[index]);
},
separatorBuilder: (context, index) {
return Divider(
height: .5,
indent: 75,
color: Color(0xFFDDDDDD),
);
},
);
}
}
предварительный просмотр
Глядя на код, можно увидеть, что разница заключается в реализацииseparatorBuilder
Это функция, с помощью которой мы можем настроить разделительную линию между каждым дочерним элементом.
2.4 Резюме
Пока что мы научилисьListView
,ListView.build
а такжеListView.separated
Каждый из трех способов создания списка имеет свои применимые сценарии, поэтому вам все равно придется анализировать конкретные проблемы, когда вы сталкиваетесь с требованиями.
Но по факту,ListView
Также есть конструктор:ListView.custom
. а такжеListView.build
а такжеListView.separated
наконец черезListView.custom
осуществленный. Но в этой статье не ставится цель представить этот метод, потому что в целом для решения задачи достаточно трех упомянутых выше методов построения (мы изучим это позже, когда столкнемся с практическими задачами).
3. Расширенный метод ListView
Выше мы представилиListView
Основное использование , но в реальном продукте мы также сталкиваемся со списками下拉刷新
а также上拉加载
и так далее. Далее будем учитьсяFlutter
как такие взаимодействия должны быть реализованы.
3.1 Потяните вниз, чтобы обновить
быть вFlutter
На самом деле очень просто реализовать эффект обновления выпадающего списка, потому чтоFlutter
Упаковал один для насRefreshIndicator
компоненты также очень удобны в использовании. Взгляните на пример кода:
class PullDownRefreshList extends StatefulWidget {
const PullDownRefreshList({Key key}) : super(key: key);
@override
_PullDownRefreshListState createState() => _PullDownRefreshListState();
}
class _PullDownRefreshListState extends State<PullDownRefreshList> {
Future onRefresh() {
return Future.delayed(Duration(seconds: 1), () {
Toast.show('当前已是最新数据', context);
});
}
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: this.onRefresh,
child: ListView.separated(
itemCount: friendListData.length,
itemBuilder: (context, index) {
return FriendCard(data: friendListData[index]);
},
separatorBuilder: (context, index) {
return Divider(
height: .5,
indent: 75,
color: Color(0xFFDDDDDD),
);
},
),
);
}
}
Поскольку источник данных списка является переменным, на этот раз мы выбираем наследование отStatefulWidget
.
можно увидетьRefreshIndicator
Использование очень простое, пока наш оригинальныйListView
как его ребенок, и реализовать егоonRefresh
метод в порядке. а такжеonRefresh
На самом деле метод заключается в обновлении уведомленияRefreshIndicator
функция обратного вызова . В приведенном выше коде мы имитируем 1-секундное ожидание в качестве сетевого запроса, а затем выводим всплывающую подсказку «уже самые последние данные» (здесьToast
установленоtoast: ^0.1.3
эта сумка,Flutter
изначально не предоставляет).
Это имитирует пользовательский интерфейс списка Toutiao в качестве примера (стиль макета карточки новостей можно увидеть наздесь), давайте посмотрим на эффект:
Вы можете видеть, что все успешно выполнено, как и ожидалось, эффект все еще хороший, иRefreshIndicator
Он также очень прост в использовании. Однако из-заFlutter
упакованныйRefreshIndicator
Настраиваемость компонентов немного слаба и не совсем соответствует требованиям пользовательского стиля в большинстве приложений. Но посмотриRefreshIndicator
Исходного кода не так много.Изучив анимацию в будущем, я вернусь, чтобы изучить, как настроить собственный компонент обновления выпадающего списка.
3.2 Подтягивающая нагрузка
В дополнение к выпадающему обновлению, подтягивающая загрузка является еще одной часто встречающейся операцией со списком. Однако на этот разFlutter
Однако не существует готового компонента, который можно было бы вызвать напрямую, как pull-down Refresh, и взаимодействие загрузки pull-up нужно выполнять самим. Для этого кратко проанализируем:
- Компонент нуждается в
list
В переменной хранится источник данных текущего списка; - Компонент нуждается в
bool
ТипisLoading
флаг, указывающий, является ли текущийLoading
условие; - Должна быть возможность определить, прокручивается ли текущий список вниз, и для этого требуется помощь, о которой мы упоминали ранее.
controller
атрибут (ScrollController
Положение прокрутки текущего списка и максимальную область прокрутки списка можно получить, а результат можно получить путем сравнения); - Приступая к загрузке данных, вам необходимо
isLoading
установлен вtrue
; Когда данные загружены, новые данные необходимо объединить вlist
переменная и сбросьтеisLoading
установлен вfalse
.
Согласно приведенной выше идее, мы можем получить следующий код:
class PullUpLoadMoreList extends StatefulWidget {
const PullUpLoadMoreList({Key key}) : super(key: key);
@override
_PullUpLoadMoreListState createState() => _PullUpLoadMoreListState();
}
class _PullUpLoadMoreListState extends State<PullUpLoadMoreList> {
bool isLoading = false;
ScrollController scrollController = ScrollController();
List<NewsViewModel> list = List.from(newsList);
@override
void initState() {
super.initState();
// 给列表滚动添加监听
this.scrollController.addListener(() {
// 滑动到底部的关键判断
if (
!this.isLoading &&
this.scrollController.position.pixels >= this.scrollController.position.maxScrollExtent
) {
// 开始加载数据
setState(() {
this.isLoading = true;
this.loadMoreData();
});
}
});
}
@override
void dispose() {
// 组件销毁时,释放资源(一定不能忘,否则可能会引起内存泄露)
super.dispose();
this.scrollController.dispose();
}
Future loadMoreData() {
return Future.delayed(Duration(seconds: 1), () {
setState(() {
this.isLoading = false;
this.list.addAll(newsList);
});
});
}
Widget renderBottom() {
// TODO
}
@override
Widget build(BuildContext context) {
return ListView.separated(
controller: this.scrollController,
itemCount: this.list.length + 1,
separatorBuilder: (context, index) {
return Divider(height: .5, color: Color(0xFFDDDDDD));
},
itemBuilder: (context, index) {
if (index < this.list.length) {
return NewsCard(data: this.list[index]);
} else {
return this.renderBottom();
}
},
);
}
}
Следует отметить, что списокitemCount
значение становитсяlist.length + 1
, потому что мы визуализируем дополнительный底部组件
. Когда не загружается, мы можем отобразить上拉加载更多
информативный компонент; при загрузке данных мы снова можем отобразить努力加载中...
компонент-заполнитель.renderBottom
Реализация выглядит следующим образом:
Widget renderBottom() {
if(this.isLoading) {
return Container(
padding: EdgeInsets.symmetric(vertical: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'努力加载中...',
style: TextStyle(
fontSize: 15,
color: Color(0xFF333333),
),
),
Padding(padding: EdgeInsets.only(left: 10)),
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 3),
),
],
),
);
} else {
return Container(
padding: EdgeInsets.symmetric(vertical: 15),
alignment: Alignment.center,
child: Text(
'上拉加载更多',
style: TextStyle(
fontSize: 15,
color: Color(0xFF333333),
),
),
);
}
}
Наконец, давайте посмотрим на окончательный эффект реализации:
4. Резюме
Во-первых, в этой статье представлены часто используемыеListView
,ListView.build
а такжеListView.separated
Три метода построения для создания списка в сочетании с практическими примерами, иллюстрирующими различные сценарии его использования. Далее вводится компонент списка下拉刷新
а также上拉加载
Эти два наиболее часто используемых взаимодействия находятся вFlutter
как это должно быть реализовано.
Судя по 5 практическим примерам в тексте, вы, должно быть, были правы.Flutter
как использоватьListView
С предварительным пониманием, остальное - больше практиковаться (играть)~
Весь код в этой статье размещен по адресуздесь, вы также можете подписаться на меняBlog, добро пожаловать, чтобы обменяться и учиться вместе ~