Флаттер Слайвер, мисс Водопад, которую вы хотите

Flutter

предисловие

Я смотрел сегодня Flutter Interact, и там была девушка, которая переводила весь процесс (синхронный перевод, такой мощный), поэтому я закончил эту статью, слушая.

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

Добро пожаловать во флаттер-конфетыflutter-candiesГруппа QQ: 181398081.

Зная, что вас волнует только юная леди, я поставлю визуализацию на первое место.

принцип

Когда я раньше занимался UWP, я также сам делал макет водопада. Это похоже на навязчивую идею После входа в яму Флаттера я также хочу реализовать схему потока водопада. Кратко поговорим о том, что такое водопадный поток и как он работает.

Схема водопада характеризуется равной шириной и неравной высотой. Для того, чтобы минимизировать зазор в последнем ряду, начиная со второго ряда, элемент нужно расположить под самым коротким элементом в первом ряду и так далее, как показано на рисунке ниже. 4 меньше 0, 5 меньше 3, 6 меньше 1, 7 меньше 2, 8 меньше 4...

основной код

Зная принцип, давайте реализуем его в виде кода вместе.

  • Так как нам нужно знать ближайшие элементы к верхней части окна просмотра и ближайшие элементы к нижней части окна просмотра. Таким образом, вы можете узнать, под каким элементом находится новый элемент при прокрутке назад, или узнать, над каким элементом размещается новый элемент при прокрутке вперед.

я разработалCrossAxisItemsдля хранения начальных и конечных элементов.

  • При добавлении нового элемента в обратном порядке код выглядит следующим образом

1. Добавляйте LeadItems до тех пор, пока они не будут равны crossAxisCount.

2. Найдите текущий самый короткий элемент и установите его layoutoffset

3. Сохраните индексы этого столбца

  void insert({
    @required RenderBox child,
    @required ChildTrailingLayoutOffset childTrailingLayoutOffset,
    @required PaintExtentOf paintExtentOf,
  }) {
    final WaterfallFlowParentData data = child.parentData;
    final LastChildLayoutType lastChildLayoutType =
        delegate.getLastChildLayoutType(data.index);
    
    ///处理最后一个特殊化布局
    switch (lastChildLayoutType) {
      case LastChildLayoutType.fullCrossAxisExtend:
      case LastChildLayoutType.foot:
        //横轴绘制offset
        data.crossAxisOffset = 0.0;
        //横轴index
        data.crossAxisIndex = 0;
        //该child的大小
        final size = paintExtentOf(child);
        
        if (lastChildLayoutType == LastChildLayoutType.fullCrossAxisExtend ||
            maxChildTrailingLayoutOffset + size >
                constraints.remainingPaintExtent) {
          data.layoutOffset = maxChildTrailingLayoutOffset;
        } else {
          //如果全部children没有绘制viewport的大
          data.layoutOffset = constraints.remainingPaintExtent - size;
        }
        data.trailingLayoutOffset = childTrailingLayoutOffset(child);
        return;
      case LastChildLayoutType.none:
        break;
    }

    if (!leadingItems.contains(data)) {
      //补充满leadingItems
      if (leadingItems.length != crossAxisCount) {
        data.crossAxisIndex ??= leadingItems.length;

        data.crossAxisOffset =
            delegate.getCrossAxisOffset(constraints, data.crossAxisIndex);

        if (data.index < crossAxisCount) {
          data.layoutOffset = 0.0;
          data.indexs.clear();
        }

        trailingItems.add(data);
        leadingItems.add(data);
      } else {
        if (data.crossAxisIndex != null) {
          var item = trailingItems.firstWhere(
              (x) =>
                  x.index > data.index &&
                  x.crossAxisIndex == data.crossAxisIndex,
              orElse: () => null);

          ///out of viewport
          if (item != null) {
            data.trailingLayoutOffset = childTrailingLayoutOffset(child);
            return;
          }
        }
        //找到最矮的那个
        var min = trailingItems.reduce((curr, next) =>
            ((curr.trailingLayoutOffset < next.trailingLayoutOffset) ||
                    (curr.trailingLayoutOffset == next.trailingLayoutOffset &&
                        curr.crossAxisIndex < next.crossAxisIndex)
                ? curr
                : next));

        data.layoutOffset = min.trailingLayoutOffset + delegate.mainAxisSpacing;
        data.crossAxisIndex = min.crossAxisIndex;
        data.crossAxisOffset =
            delegate.getCrossAxisOffset(constraints, data.crossAxisIndex);

        trailingItems.forEach((f) => f.indexs.remove(min.index));
        min.indexs.add(min.index);
        data.indexs = min.indexs;
        trailingItems.remove(min);
        trailingItems.add(data);
      }
    }

    data.trailingLayoutOffset = childTrailingLayoutOffset(child);
  }
  • При добавлении нового элемента вперед код выглядит следующим образом

1. Найдите, к какому столбцу принадлежит новый элемент с помощью индексов.

2. Добавить в начало старого элемента

  void insertLeading({
    @required RenderBox child,
    @required PaintExtentOf paintExtentOf,
  }) {
    final WaterfallFlowParentData data = child.parentData;
    if (!leadingItems.contains(data)) {
      var pre = leadingItems.firstWhere((x) => x.indexs.contains(data.index),
          orElse: () => null);

      if (pre == null || pre.index < data.index) return;

      data.trailingLayoutOffset = pre.layoutOffset - delegate.mainAxisSpacing;
      data.crossAxisIndex = pre.crossAxisIndex;
      data.crossAxisOffset =
          delegate.getCrossAxisOffset(constraints, data.crossAxisIndex);

      leadingItems.remove(pre);
      leadingItems.add(data);
      trailingItems.remove(pre);
      trailingItems.add(data);
      data.indexs = pre.indexs;

      data.layoutOffset = data.trailingLayoutOffset - paintExtentOf(child);
    }
  }
  • Рассчитайте ближайший к верхней части области просмотра, вы должны убедиться, что все ведущие элементы находятся внутри области просмотра.

Аналогичен предыдущему анализу исходного кода Listview, но здесь мы должны убедиться, что наибольшее значение LeadingLayoutOffset меньше, чем scrollOffset, чтобы все LeadItems находились в области просмотра.

    if (crossAxisItems.maxLeadingLayoutOffset > scrollOffset) {
      RenderBox child = firstChild;
      //move to max index of leading
      final int maxLeadingIndex = crossAxisItems.maxLeadingIndex;
      while (child != null && maxLeadingIndex > indexOf(child)) {
        child = childAfter(child);
      }
      //fill leadings from max index of leading to min index of leading
      while (child != null && crossAxisItems.minLeadingIndex < indexOf(child)) {
        crossAxisItems.insertLeading(
            child: child, paintExtentOf: paintExtentOf);
        child = childBefore(child);
      }
      //collectGarbage(maxLeadingIndex - index, 0);

      while (crossAxisItems.maxLeadingLayoutOffset > scrollOffset) {
        // We have to add children before the earliestUsefulChild.
        earliestUsefulChild =
            insertAndLayoutLeadingChild(childConstraints, parentUsesSize: true);

        if (earliestUsefulChild == null) {
          if (scrollOffset == 0.0) {
            // insertAndLayoutLeadingChild only lays out the children before
            // firstChild. In this case, nothing has been laid out. We have
            // to lay out firstChild manually.
            firstChild.layout(childConstraints, parentUsesSize: true);

            earliestUsefulChild = firstChild;
            leadingChildWithLayout = earliestUsefulChild;
            trailingChildWithLayout ??= earliestUsefulChild;
            crossAxisItems.reset();
            crossAxisItems.insert(
              child: earliestUsefulChild,
              childTrailingLayoutOffset: childTrailingLayoutOffset,
              paintExtentOf: paintExtentOf,
            );
            break;
          } else {
            // We ran out of children before reaching the scroll offset.
            // We must inform our parent that this sliver cannot fulfill
            // its contract and that we need a scroll offset correction.
            geometry = SliverGeometry(
              scrollOffsetCorrection: -scrollOffset,
            );
            return;
          }
        }

        crossAxisItems.insertLeading(
            child: earliestUsefulChild, paintExtentOf: paintExtentOf);

        final WaterfallFlowParentData data = earliestUsefulChild.parentData;

        // firstChildScrollOffset may contain double precision error
        if (data.layoutOffset < -precisionErrorTolerance) {
          // The first child doesn't fit within the viewport (underflow) and
          // there may be additional children above it. Find the real first child
          // and then correct the scroll position so that there's room for all and
          // so that the trailing edge of the original firstChild appears where it
          // was before the scroll offset correction.
          // do this work incrementally, instead of all at once,
          // i.e. find a way to avoid visiting ALL of the children whose offset
          // is < 0 before returning for the scroll correction.
          double correction = 0.0;
          while (earliestUsefulChild != null) {
            assert(firstChild == earliestUsefulChild);
            correction += paintExtentOf(firstChild);
            earliestUsefulChild = insertAndLayoutLeadingChild(childConstraints,
                parentUsesSize: true);
            crossAxisItems.insertLeading(
                child: earliestUsefulChild, paintExtentOf: paintExtentOf);
          }
          geometry = SliverGeometry(
            scrollOffsetCorrection: correction - data.layoutOffset,
          );
          return;
        }

        assert(earliestUsefulChild == firstChild);
        leadingChildWithLayout = earliestUsefulChild;
        trailingChildWithLayout ??= earliestUsefulChild;
      }
    }
  • Вычисление достигает нижней части области просмотра, вы должны убедиться, что самые короткие trailingItems превышают нижнюю часть области просмотра.
    // Now find the first child that ends after our end.
    if (child != null) {
      while (crossAxisItems.minChildTrailingLayoutOffset <
              targetEndScrollOffset ||
              //make sure leading children are painted. 
          crossAxisItems.leadingItems.length < _gridDelegate.crossAxisCount
          || crossAxisItems.leadingItems.length  > childCount
          ) {
        if (!advance()) {
          reachedEnd = true;
          break;
        }
      }
    }

waterfall_flowиспользовать

  • Добавить ссылку на библиотеку в pubspec.yaml

dependencies:
  waterfall_flow: any

  • библиотека импорта

  import 'package:waterfall_flow/waterfall_flow.dart';
  

как определить

Вы можете определить каскадный поток, установив параметр SliverWaterfallFlowDelegate.

параметр описывать дефолт
crossAxisCount Количество элементов одинаковой длины по горизонтальной оси необходимые
mainAxisSpacing расстояние между элементами главной оси 0.0
crossAxisSpacing Расстояние между элементами горизонтальной оси 0.0
collectGarbage Обратный вызов, когда элемент перерабатывается -
lastChildLayoutTypeBuilder Стиль макета последнего элемента (подробности см. далее) -
viewportBuilder Обратный вызов при изменении индексов элементов в видимой области -
closeToTrailing Можно ли сделать верстку близкой к трейлингу (подробности читайте далее) false
            WaterfallFlow.builder(
              //cacheExtent: 0.0,
              padding: EdgeInsets.all(5.0),
              gridDelegate: SliverWaterfallFlowDelegate(
                  crossAxisCount: 2,
                  crossAxisSpacing: 5.0,
                  mainAxisSpacing: 5.0,
                  /// follow max child trailing layout offset and layout with full cross axis extend
                  /// last child as loadmore item/no more item in [GridView] and [WaterfallFlow]
                  /// with full cross axis extend
                  //  LastChildLayoutType.fullCrossAxisExtend,

                  /// as foot at trailing and layout with full cross axis extend
                  /// show no more item at trailing when children are not full of viewport
                  /// if children is full of viewport, it's the same as fullCrossAxisExtend
                  //  LastChildLayoutType.foot,
                  lastChildLayoutTypeBuilder: (index) => index == _list.length
                      ? LastChildLayoutType.foot
                      : LastChildLayoutType.none,
                  ),

Завершить демо-версию «Мисс сестра»

Эпилог

Анализа исходного кода больше нет, если вы читали предыдущую статью, то должны понимать правду лучше. Эта статья является демонстрацией принципа водопадного потока, реализованного на Flutter.Нет эффекта, которого нельзя было бы достичь, а есть только неожиданные эффекты.Это тот опыт, который приносит мне Flutter.

Наконец, поместите какой-нибудь контент Flutter Interact. Я написал его во время прослушивания. Если есть какая-то ошибка, напомните мне, чтобы я его изменил.

  • Флаттер версии 1.12
  • Материальный дизайн и библиотека шрифтов
  • Приносит рабочий стол / Интернет (вы можете попробовать их сейчас, а не игрушки)
  • Разнообразные инструменты разработки для взрыва неба (одновременная отладка 7 видов устройств, интерфейс пользовательского интерфейса)
  • gskinnerКлассное взаимодействие + открытый исходный код
  • supernovaИзвестный инструмент проектирования в код
  • Adobe XDТрепещите программисты, вас заменит UI.
  • flutter_vignettesделовой удар
  • rive.appПоказывает милую игру в жареную курицу и использует паутину Flutter.

Добро пожаловатьFlutter Candies, чтобы вместе производить милые маленькие леденцы Flutter (flutter-candiesГруппа QQ: 181398081)

надевать в последнюю очередьFlutter CandiesВся семейная бочка, действительно ароматная.