Компонент контейнера прокрутки Flutter — ListView

Flutter
Компонент контейнера прокрутки Flutter — ListView

1. Введение

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

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

  1. Компонент нуждается вlistВ переменной хранится источник данных текущего списка;
  2. Компонент нуждается вboolТипisLoadingфлаг, указывающий, является ли текущийLoadingусловие;
  3. Должна быть возможность определить, прокручивается ли текущий список вниз, и для этого требуется помощь, о которой мы упоминали ранее.controllerатрибут (ScrollControllerПоложение прокрутки текущего списка и максимальную область прокрутки списка можно получить, а результат можно получить путем сравнения);
  4. Приступая к загрузке данных, вам необходимо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, добро пожаловать, чтобы обменяться и учиться вместе ~