<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dima</title>
    <description>The latest articles on DEV Community by Dima (@followthemoney1).</description>
    <link>https://dev.to/followthemoney1</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F158275%2Fc4e5a8c2-5b99-4863-895b-5ca16b6be4a4.jpeg</url>
      <title>DEV Community: Dima</title>
      <link>https://dev.to/followthemoney1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/followthemoney1"/>
    <language>en</language>
    <item>
      <title>Как мы делали Custom Category Picker на Flutter</title>
      <dc:creator>Dima</dc:creator>
      <pubDate>Fri, 20 Nov 2020 10:57:32 +0000</pubDate>
      <link>https://dev.to/followthemoney1/custom-category-picker-flutter-53gk</link>
      <guid>https://dev.to/followthemoney1/custom-category-picker-flutter-53gk</guid>
      <description>&lt;p&gt;Сидишь ты такой, никого не трогаешь, работаешь себе спокойно, и потом тебе прилетает дизайн на очередной проект. Начинаешь смотреть, разбираться.вроде все хорошо и прикольно, только вот мы не задумывались что бывают элементы дизайна которые и в правду могут быть тяжелыми в реализации.&lt;br&gt;
Смысл приложения был очень прост, это всего лишь новостник, только вот в нем была одна часть, которая сразу выглядела очень сложно, это категории.&lt;/p&gt;

&lt;p&gt;Какой был дизайн:&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uHwhGEO1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ruox9iz7plr102oivmik.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uHwhGEO1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ruox9iz7plr102oivmik.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
И что получилось:&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EVproCnI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8d7nqxrj3anm3mvyz5hk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EVproCnI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8d7nqxrj3anm3mvyz5hk.gif" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;А теперь все сначала. Изначально эта задача не выглядела сложно, так как мы все знаем что существует GridView или же такие библиотеки как &lt;a href="https://pub.dartlang.org/packages/flutter_staggered_grid_view"&gt;https://pub.dartlang.org/packages/flutter_staggered_grid_view&lt;/a&gt;. После того как мы протестировали и поняли что анимация в этих случаях работать не будет, так как view обновляется и даже с использованием всяких Hero и других анимаций, у нас не получается нормально привязать элемент для и связать с анимацией плавного перехода, мы начали себе ломать голову в плоть до того чтобы использовать какие-то Wrap, Flexible, FlowLayout..виджеты.&lt;/p&gt;

&lt;p&gt;В конечном счёте я пришел к тому что пора думать самому как реализовать эту систему.&lt;br&gt;
Сначала мы выбрали виджет родителя на котором все должно было происходить, и это был Stack. Так как у нас много виджетов, то нам нужно понимать изначальное состояние каждого из них и конечно же их количество.&lt;/p&gt;
&lt;h1&gt;
  
  
  Первое решение, почему стоит думать сначала об виджете
&lt;/h1&gt;

&lt;p&gt;Изначально, первым решением было, построить матрицу и привязать каждый элемент по крайней верхней\левой точке, и вышло что-то вроде этого:&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--a3eUg7zI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/a20liakms1n0fh2kyqi4.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--a3eUg7zI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/a20liakms1n0fh2kyqi4.gif" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Так как мы работали изначально с массивом данных нужно было построить наши виджеты и заполнить матрицу начальными значениями:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;suggestionMatrix = Map.from(suggestionMatrix.map((key, value) {
      final endIndex = (rowCount * (key + 1));
      return MapEntry(
          key,
          widget.items
              .getRange(
                  rowCount * key,
                  endIndex &amp;lt; widget.items.length
                      ? endIndex
                      : widget.items.length)
              .toList()
              .asMap()
              .entries
              .map((element) {
            final val = element.value;
            final i = element.key;
            //data to use
            return SuggestionItem(
              data: val,
              width: startSize,
              height: startSize,
              currentWeight: 1,
              x: (i) * startSize,
              y: (key) * startSize,
            );
          }).toList());
    }));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;После добавить в наш список и отрисовать их:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; List&amp;lt;Widget&amp;gt; childerCards() {
    List&amp;lt;Widget&amp;gt; cardsMatrixWidgets = [];

    suggestionMatrix.entries.forEach((columns) {
      int iColumn = columns.key;
      List&amp;lt;SuggestionItem&amp;gt; rowsList = columns.value;
      rowsList.asMap().entries.forEach((rows) {
        ///get all widgets
        cardsMatrixWidgets.add(AnimatedPositioned.fromRect(
          duration: Duration(milliseconds: widget.stackAnimatedDuration),
          child: item(rows.value),
          rect: currentRow.rect,
        ));
      });
    });

    return cardsMatrixWidgets;
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;А при клике на какой-то элемент соответственно обновлять остальные, если наш текущий элемент пересекается с другим, то подвинуть остальные:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rowsList.asMap().entries.forEach((rows) {
        //нам нужно определить пересекается ли текущий элемент с элементом который слева или же сверху, если пересекается,
        //тогда текущий элемент мы двигаем вниз или справо в зависимости
        //mark: left 
        if (currentRow.iRow != 0) {
          calcOverflowLeft(rowsList.elementAt(currentRow.iRow - 1), currentRow);
        }
        //mark: top
        if (currentRow.iColumn != 0) {
          calcOverflowTop(
              suggestionMatrix[currentRow.iColumn - 1]
                  .elementAt(currentRow.iRow),
              currentRow);
        }

        //mark: left top first
        if (currentRow.iRow != 0 &amp;amp;&amp;amp;
            currentRow.iRow + 1 &amp;lt; rowsList.length &amp;amp;&amp;amp;
            currentRow.iColumn &amp;gt;= 1) {
          caclLeftTopElementOverflow(
              suggestionMatrix[iColumn - 1].elementAt(currentRow.iRow),
              rowsList.elementAt(currentRow.iRow + 1));
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;и так же:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;caclLeftTopElementOverflow(
      SuggestionCategory prev, SuggestionCategory current) {
    final currentYStartPosition = current.y;
    final prevYEndPosition = prev.y + prev.height;

    final currentXStartPosition = current.x;
    final prevXEndPosition = prev.x + prev.width;
    if (prevYEndPosition &amp;gt;= currentYStartPosition &amp;amp;&amp;amp; prev.isExpanded) {
      current.y += (prev.y + prev.height) - current.y;
    }
  }

  void calcOverflowLeft(SuggestionCategory prev, SuggestionCategory current) {
    final currentStartPosition = current.x;
    final prevEndPosition = prev.x + prev.width;
    if (prevEndPosition &amp;gt; currentStartPosition) {
      current.x += prevEndPosition - currentStartPosition;
    } else if (prevEndPosition &amp;lt; currentStartPosition) {
      current.x -= currentStartPosition - prevEndPosition;
    } else if (prevEndPosition != currentStartPosition) {
      print(
          "prevEndPosition = $prevEndPosition currentStartPosition=$currentStartPosition");
    }
  }

  void calcOverflowTop(SuggestionCategory prev, SuggestionCategory current) {
    final currentStartPosition = current.y;
    final prevEndPosition = prev.y + prev.height;
    if (prevEndPosition &amp;gt; currentStartPosition) {
      current.y += prevEndPosition - currentStartPosition;
    } else if (prevEndPosition &amp;lt; currentStartPosition) {
      current.y -= currentStartPosition - prevEndPosition;
    } else if (prevEndPosition != currentStartPosition) {
      print(
          "prevEndPosition = $prevEndPosition currentStartPosition=$currentStartPosition");
    }
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Спустя много времени рисований на доске и поиска решений задачи
&lt;/h1&gt;

&lt;p&gt;Основная наша проблема была в том что мы пытались просчитать куда и как должны двигаться наши элементы, а после уже их построить. Но только мы начали связывать каждый элемент друг с другом, все стало на свои места.&lt;br&gt;
Когда мы начали сравнивать пересечение квадратов, мы получили нужный нам результат:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; bool calcOverflowClosestElement(
      {@required List&amp;lt;SuggestionItem&amp;gt; line,
      @required SuggestionItem current,
      bool check = false}) {
    for (SuggestionItem element in line) {
      if (current.rect.intersect(element.rect).height &amp;gt; 0 &amp;amp;&amp;amp;
          current.rect.intersect(element.rect).width &amp;gt; 0) {
        if (current.rect.intersect(element.rect).height &amp;gt; 0) {
          if (!check) {
            current.y += element.rect.intersect(current.rect).height;
          }
        }
      }
    }
    return false;
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;В догонку с этим когда сделали проверку на элемент сверху, чтобы каждый элемент был привязан еще и к своему родителю, так как матрица может раздвигаться и элементы которые сверху могли стать ниже или выше, мы получаем что-то вроде этого:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; _update({final currentRow, final rowsList}) {
    if (currentRow.iRow &amp;gt; 0) {
      calcOverflowLeft(rowsList.elementAt(currentRow.iRow - 1), currentRow);
    }
    setState(() {
      if (currentRow.iColumn &amp;gt; 0) {
        calcOverflowTop(
            suggestionMatrix[currentRow.iColumn - 1].elementAt(currentRow.iRow),
            currentRow);

        calcOverflowClosestElement(
            line: suggestionMatrix[currentRow.iColumn - 1],
            current: currentRow);
      }
    });
  }

  bool calcOverflowClosestElement(
      {@required List&amp;lt;SuggestionItem&amp;gt; line,
      @required SuggestionItem current,
      bool check = false}) {
    for (SuggestionItem element in line) {
      if (current.rect.intersect(element.rect).height &amp;gt; 0 &amp;amp;&amp;amp;
          current.rect.intersect(element.rect).width &amp;gt; 0) {
        if (current.rect.intersect(element.rect).height &amp;gt; 0) {
          if (!check) {
            current.y += element.rect.intersect(current.rect).height;
          }
        }
      }
    }
    return false;
  }

  void calcOverflowLeft(SuggestionItem prev, SuggestionItem current,
      {bool withGravity}) {
    if (prev.right &amp;gt; current.left) {
      current.x += prev.right - current.left;
    } else if (prev.right &amp;lt; current.left) {
      current.x -= current.left - prev.right;
    }
  }

  void calcOverflowTop(SuggestionItem prev, SuggestionItem current) {
    if (current.x == prev.x)
      current.y += prev.rect.intersect(current.rect).height;
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Оригинальный пост: &lt;a href="https://medium.com/@followthemoney1/%D0%BA%D0%B0%D0%BA-%D0%BC%D1%8B-%D0%B4%D0%B5%D0%BB%D0%B0%D0%BB%D0%B8-custom-category-picker-%D0%BD%D0%B0-flutter-d078b9697606"&gt;https://medium.com/@followthemoney1/как-мы-делали-custom-category-picker-на-flutter-d078b9697606&lt;/a&gt;&lt;br&gt;
Спасибо за внимание!!!&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>library</category>
      <category>libraries</category>
      <category>dart</category>
    </item>
  </channel>
</rss>
