В мобильной разработке часто требуется создание макетов с сочетанием текста и изображений, как показано ниже.
На самом деле, для реализации такого типа макетов в 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
.
Как видно из приведённого выше кода, входной метод RichText
— это _extractChildren
, который рекурсивно выбирает все WidgetSpan
из children
переданного TextSpan
с помощью метода visitChildren
, а затем передает их родителю MultiChildRenderObjectWidget
.
Почему это необходимо? В статье "Шестнадцать, подробное описание создания пользовательских компонентов" было объяснено, что
children
вMultiChildRenderObjectWidget
в конечном итоге будут переданы черезMultiChildRenderObjectElement
как мост, чтобы они могли быть вставлены в список управляемых и отображаемых детей, что делает удобным управление и доступ к ним вRenderObject
.Кроме того, известно, чтоtext
вRichText
является объектом типаInlineSpan
, гдеTextSpan
является подклассомInlineSpan
, аWidgetSpan
также является реализациейInlineSpan
. Отношения между этими типами показаны на следующем рисунке:
Для серии InlineSpan
важно обратить внимание на два метода: visitChildren
и build
, которые имеют свои реализации в подклассах TextSpan
и WidgetSpan
.
void build(ui.ParagraphBuilder builder, {double textScaleFactor = 1.0, List<PlaceholderDimensions> dimensions});
bool visitChildren(InlineSpanVisitor visitor);
Далее рассмотрим 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
завершен.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )