1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/CarGuo-GSYFlutterBook

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Flutter-TWHP.md 15 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 10.03.2025 00:06 5767d61

В мобильной разработке часто требуется создание макетов с сочетанием текста и изображений, как показано ниже.

Иллюстрация

На самом деле, для реализации такого типа макетов в Flutter существует удобный способ: WidgetSpan.

Как показано ниже, используя Text.rich вместе с TextSpan и WidgetSpan, можно быстро создать макет с сочетанием текста и изображений, а также заметно, что WidgetSpan поддерживает не только изображения, но и любые другие виджеты, такие как Card, InkWell и так далее.

Text.rich(TextSpan(
      children: <InlineSpan>[
        TextSpan(text: 'Flutter является'),
        WidgetSpan(
            child: SizedBox(
          width: 120,
          height: 50,
          child: Card(
              color: Colors.blue,
              child: Center(child: Text('Привет, мир!'))),
        )),
        WidgetSpan(
            child: SizedBox(
          width: size > 0 ? size : 0,
          height: size > 0 ? size : 0,
          child: new Image.asset(
            "static/gsy_cat.png",
            fit: BoxFit.cover,
          ),
        )),
        TextSpan(text: 'лучшим!'),
      ],
    ))

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

А почему WidgetSpan может так легко объединять текст и виджеты? Это связано с внутренней реализацией Text.

Реализация

Обычно используемый нами виджет Text представляет собой упаковку RichText, а его реализация состоит из трёх частей: MultiChildRenderObjectWidget, MultiChildRenderObjectElement и RenderParagraph.Схема

Как известно, каждый виджет в Flutter обычно состоит из трёх компонентов: Widget, Element и RenderObject. В случае RichText это тоже верно:

  • RenderParagraph отвечает за рисование и размещение текста;
  • RichText, наследуясь от MultiChildRenderObjectWidget, использует MultiChildRenderObjectElement для управления вставками и управлением дочерними элементами внутри WidgetSpan.

Как же WidgetSpan внедряется в отрисовку текста?

В предыдущих примерах мы передавали TextSpan в RichText, а затем объединяли нужные нам данные через children. Давайте рассмотрим принцип работы этого механизма начиная с RichText.

image

Как видно из приведённого выше кода, входной метод RichText — это _extractChildren, который рекурсивно выбирает все WidgetSpan из children переданного TextSpan с помощью метода visitChildren, а затем передает их родителю MultiChildRenderObjectWidget.

Почему это необходимо? В статье "Шестнадцать, подробное описание создания пользовательских компонентов" было объяснено, что children в MultiChildRenderObjectWidget в конечном итоге будут переданы через MultiChildRenderObjectElement как мост, чтобы они могли быть вставлены в список управляемых и отображаемых детей, что делает удобным управление и доступ к ним в RenderObject.Кроме того, известно, что text в RichText является объектом типа InlineSpan, где TextSpan является подклассом InlineSpan, а WidgetSpan также является реализацией InlineSpan. Отношения между этими типами показаны на следующем рисунке:image

Для серии InlineSpan важно обратить внимание на два метода: visitChildren и build, которые имеют свои реализации в подклассах TextSpan и WidgetSpan.

void build(ui.ParagraphBuilder builder, {double textScaleFactor = 1.0, List<PlaceholderDimensions> dimensions});

bool visitChildren(InlineSpanVisitor visitor);

image

Далее рассмотрим RenderParagraph. Как видно из приведённого выше кода, text (InlineSpan) из RichText продолжает передаваться в RenderParagraph. RenderParagraph наследует RenderBox и использует миксины ContainerRenderObjectMixin и RenderBoxContainerDefaultsMixin.

Эти миксины были рассмотрены в статье "Шестнадцать, подробное описание создания пользовательских компонентов". Здесь важно понять, что благодаря этим миксинам RenderParagraph получает возможность использовать список детей, переданных через WidgetSpan в MultiChildRenderObjectElement, а также выполнять вычисления размеров и других операций.

После того как text в RenderParagraph будет помещено в TextPainter, все PlaceholderSpans будут отфильтрованы методом _extractPlaceholderSpans.

TextPainter主要用于实现文本的绘制,在此暂不深入分析;而由_extractPlaceholderSpans筛选出的所有PlaceholderSpans实际上是WidgetSpan

WidgetSpan通过继承PlaceholderSpans实现了InlineSpan接口,当前唯一实现该接口的类即为WidgetSpan

Отфильтрованные списки List<PlaceholderSpan> используются в методах расчета ширины и высоты компонента RenderParagraph, таких как computeMaxIntrinsicWidth. **Основные ключевые методы здесь — это _canComputeIntrinsics, _computeChildrenWidthWithMaxIntrinsics и _layoutText**, которые вместе решают вопросы размеров и размещения Span в RenderParagraph.

  • _canComputeIntrinsics: _canComputeIntrinsics проверяет, поддерживаются ли конфигурации baseline для PlaceholderSpan.

  • _computeChildrenWidthWithMaxIntrinsics: В методе _computeChildrenWidthWithMaxIntrinsics с помощью PlaceholderSpan создается объект PlaceholderDimensions, который используется для последующего определения размеров WidgetSpan.

Этот объект PlaceholderDimensions передается в TextPainter через метод setPlaceholderDimensions, таким образом при выполнении layout методом TextPainter, размеры WidgetSpan будут установлены.

  • _layoutText: Метод _layoutText вызывает _textPainter.layout, что приводит к выполнению _text.build, который активирует выполнение метода build для WidgetSpan внутри children.

Поэтому, как показано ниже, метод _textPainter.layout выполняет метод build для Span, устанавливает PlaceholderDimensions для WidgetSpan, а также использует метод _paragraph.getBoxesForPlaceholders() для получения информации о позициях (left, right) для рисования компонентов, которая основана на выполнении text.build.

_paragraph.getBoxesForPlaceholders() полученные данные TextBox, основаны на методе addPlaceholder, который мы рассмотрим далее в контексте Span.Эти данные будут установлены в TextParentData с помощью метода setParentData. О том, как работают ParentData и его подклассы, можно прочитать в "Шестнадцатой главе: подробное описание практического использования пользовательских компонентов"; здесь же достаточно сказать, что эти данные предоставляют информацию о положении offset для WidgetSpan.

Затем, как показано ниже, выполняется метод build класса WidgetSpan. Здесь используется счетчик placeholderCount, который начинается с нуля и увеличивается при каждом вызове метода addPlaceholder через _placeholderCount++. Таким образом, следующий WidgetSpan получает следующие PlaceholderDimensions для установки размера.

После выполнения метода addPlaceholder управление переходит к процессу внутри движка Flutter.

Наконец, метод paint класса RenderParagraph вызывает _textPainter.paint, передавая детализированную информацию о размерах и позициях для отрисовки потомков.

Не кажется ли вам это сложным? Обобщая, можно сказать, что:

  • Процесс создания виджетов включает использование счетчика placeholderCount.
  • При каждом вызове метода addPlaceholder значение счетчика увеличивается.
  • Каждый последующий WidgetSpan получает новые значения PlaceholderDimensions.
  • Управление после выполнения метода addPlaceholder переходит внутрь движка Flutter.
  • Метод paint класса RenderParagraph использует _textPainter.paint для отрисовки потомков с учетом информации о размерах и позициях.- В RichText передаются TextSpan, где используются WidgetSpan в качестве детей; WidgetSpan преобразуются в MultiChildRenderObjectElement и создают цепочку потомков;
  • Затем TextSpan попадают в RenderParagraph, где выделяются соответствующие PlaceholderSpan (то есть, WidgetSpan) и сохраняются размеры и другие данные в виде PlaceholderDimensions;
  • Далее, в TextPainter вызывается метод build класса InlineSpan, который передает ранее полученные PlaceholderDimensions в WidgetSpan;
  • Информация о контролах внутри WidgetSpan передается в Paragraph с помощью метода addPlaceholder;
  • Затем, используя информацию из addPlaceholder, метод _paragraph.getBoxesForPlaceholders() получает необходимую информацию о положении (offset) для отрисовки контролов;
  • Наконец, благодаря информации о размерах и позициях, контролы, вставленные в текст, отрисовываются в методе paint класса RenderParagraph.Управление вставками в RichText реализовано с помощью MultiChildRenderObjectWidget, что позволило воспользоваться существующими логиками управления компонентами. После этого положение вычисляется с помощью движка, что позволяет завершить отрисовку.

Таким образом, анализ основных принципов работы простого WidgetSpan завершен.

Рекомендованные ресурсы

image

Опубликовать ( 0 )

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

1
https://api.gitlife.ru/oschina-mirror/CarGuo-GSYFlutterBook.git
git@api.gitlife.ru:oschina-mirror/CarGuo-GSYFlutterBook.git
oschina-mirror
CarGuo-GSYFlutterBook
CarGuo-GSYFlutterBook
master