Сводка по главе 26. Пользовательские макеты
Примечание.
Эта книга была опубликована весной 2016 года и с тех пор не обновлялась. Многое в этой книге остается ценным, но некоторые материалы устарели, а некоторые разделы перестали быть полностью верными или полными.
Xamarin.Forms включает несколько классов, производных от Layout<View>
:
StackLayout
,Grid
,AbsoluteLayout
иRelativeLayout
.
В этой главе описано, как создавать собственные классы, производные от Layout<View>
.
Общие сведения о макете.
Не существует централизованной системы для обработки макета Xamarin.Forms. Каждый элемент самостоятельно определяет свой размер и способ отображения в определенной области.
Родительские и дочерние объекты
Каждый элемент, имеющий дочерние элементы, отвечает за размещение этих дочерних элементов внутри себя. Именно родительский элемент в конечном итоге определяет размер дочернего элемента, исходя из доступного пространства и желаемого размера дочернего элемента.
Размер и размещение
Построение макета начинается с верхнего уровня визуального дерева, где располагается узел страницы, и распространяется на все его ветви. Самым важным открытым методом в макете является Layout
, определяемый в VisualElement
. Каждый элемент, являющийся родительским для других элементов, вызывает Layout
для каждого дочернего элемента, чтобы предоставить размер и расположение относительно себя в формате значения Rectangle
. Эти вызовы Layout
распространяются по всему визуальному дереву.
Вызов Layout
требуется для того, чтобы элемент отображался на экране. Он устанавливает следующие свойства только для чтения. Они должны соответствовать Rectangle
, который был передан в этот метод:
Перед вызовом Layout
Height
и Width
у вас есть макетные значения –1.
Вызов Layout
также вызывает следующие защищенные методы:
SizeAllocated
, который вызываетOnSizeAllocated
, который может быть переопределен.
Наконец, срабатывает следующее событие:
Метод OnSizeAllocated
переопределяется классами Page
и Layout
. Это единственные два класса Xamarin.Forms, которые могут иметь дочерние элементы. Переопределенный метод вызывает следующее:
UpdateChildrenLayout
для производных отPage
иUpdateChildrenLayout
для производных отLayout
, которые вызываютLayoutChildren
для производных отPage
иLayoutChildren
для производных отLayout
.
Затем LayoutChildren
вызывает Layout
для каждого из дочерних элементов этого элемента. Если хотя бы один дочерний элемент имеет новое значение Bounds
, возникает следующее событие:
LayoutChanged
для производных отPage
иLayoutChanged
для производных отLayout
.
Запросы ограничений и размера
Чтобы элемент LayoutChildren
правильно вызывал Layout
для всех дочерних элементов, он должен знать предпочтительный или желаемый размер этих дочерних элементов. Поэтому перед вызовами Layout
для каждого из дочерних элементов обычно выполняются вызовы
После публикации книги метод GetSizeRequest
был объявлен нерекомендуемым и заменен на
Метод Measure
поддерживает свойство Margin
и содержит аргумент типа MeasureFlag
, который имеет два элемента:
IncludeMargins
None
без учета полей
Для многих элементов GetSizeRequest
или Measure
получает собственный размер элемента от его отрисовщика. Оба метода имеют параметры для ограничения ширины и высоты. Например, Label
на основе ограничения ширины определяет, как переносить несколько строк текста.
GetSizeRequest
и Measure
возвращают значение типа SizeRequest
, которое имеет два свойства:
Очень часто эти два значения совпадают, а значение Minimum
обычно можно игнорировать.
Также VisualElement
определяет защищенный метод, аналогичный GetSizeRequest
, который вызывается из GetSizeRequest
:
OnSizeRequest
возвращает значениеSizeRequest
.
Этот метод теперь считается устаревшим и заменен этим методом:
Каждый класс, производный от Layout
или Layout<T>
, должен переопределять OnSizeRequest
или OnMeasure
. Именно здесь класс макета определяет свой собственный размер, который обычно основывается на размерах его дочерних элементов, полученных путем вызова GetSizeRequest
или Measure
для дочерних элементов. До и после вызова OnSizeRequest
или OnMeasure
GetSizeRequest
или Measure
вносит корректировки с учетом следующих свойств:
WidthRequest
с типомdouble
влияет на свойствоRequest
объектаSizeRequest
;HeightRequest
с типомdouble
влияет на свойствоRequest
объектаSizeRequest
.MinimumWidthRequest
с типомdouble
влияет на свойствоMinimum
объектаSizeRequest
.MinimumHeightRequest
с типомdouble
влияет на свойствоMinimum
объектаSizeRequest
.
Бесконечные ограничения
Аргументы ограничения, передаваемые в GetSizeRequest
(или Measure
) и OnSizeRequest
(или OnMeasure
), могут быть бесконечными значениями (т. е. значения Double.PositiveInfinity
). Но значения SizeRequest
, возвращаемые этими методами, не могут содержать бесконечные измерения.
Бесконечные ограничения означают, что запрошенный размер должен отражать естественный размер элемента. Вертикальный StackLayout
вызывает GetSizeRequest
(или Measure
) для своих дочерних элементов, указывая бесконечное ограничение высоты. Горизонтальный макет стека вызывает GetSizeRequest
(или Measure
) для своих дочерних элементов, указывая бесконечное ограничение ширины. Объект AbsoluteLayout
вызывает GetSizeRequest
(или Measure
) для своих дочерних элементов, указывая бесконечные ограничения ширины и высоты.
Внутренний механизм процесса
Пример ExploreChildSize отображает сведения о запросе ограничений и размера для простого макета.
Производный от представления макета<>
Пользовательский класс макета является производным от класса Layout<View>
. Он выполняет две задачи:
- Переопределяет
OnMeasure
для вызоваMeasure
для всех дочерних элементов макета. Возвращает запрошенный размера для самого макета. - Переопределяет
LayoutChildren
для вызоваLayout
для всех дочерних элементов макета.
Цикл for
или foreach
в этих переопределениях должен пропустить все дочерние элементы, у которых свойство IsVisible
имеет значение false
.
Вызов OnMeasure
не гарантируется. OnMeasure
не будет вызываться, если родительский элемент макета самостоятельно управляет размером макета (например, если макет заполняет всю страницу). По этой причине LayoutChildren
не может полагаться на размеры дочерних элементов, полученные во время вызова OnMeasure
. Очень часто приходится прямо из LayoutChildren
вызывать Measure
для дочерних элементов макета или создавать некоторую логику кэширования размеров (которая будет обсуждаться далее).
Простой пример
Пример VerticalStackDemo содержит упрощенный класс VerticalStack
и демонстрацию его использования.
Упрощение вертикального и горизонтального размещения
Одна из задач VerticalStack
выполняется при переопределении LayoutChildren
. Этот метод использует свойство HorizontalOptions
дочернего элемента, чтобы определить положение этого дочернего элемента в слоте в пределах VerticalStack
. Вместо него вы можете вызвать статический метод Layout.LayoutChildIntoBoundingRect
. Этот метод вызывает Measure
для дочернего элемента и применяет его свойства HorizontalOptions
и VerticalOptions
для размещения этого дочернего элемента в пределах указанного прямоугольника.
Недействительность
Зачастую изменение свойства элемента влияет на отображение этого элемента в макете. Макет в этом случае нужно объявить недействительным, чтобы активировать новый расчет макета.
VisualElement
определяет защищенный метод InvalidateMeasure
, который обычно вызывается обработчиком изменения любого привязываемого свойства, изменение которого влияет на размер элемента. Метод InvalidateMeasure
вызывает событие MeasureInvalidated
.
Класс Layout
определяет аналогичный защищенный метод с именем InvalidateLayout
, который должен вызываться производным от Layout
при любых изменениях, которое влияют на положение и размеры его дочерних элементов.
Некоторые правила для программирования макетов
Свойства, определенные для производных от
Layout<T>
, должны поддерживаться привязываемыми свойствами, а обработчики изменений свойств должны вызыватьInvalidateLayout
.Производный от
Layout<T>
элемент, который определяет присоединенные привязываемые свойства, должен переопределятьOnAdded
, чтобы добавлять обработчик изменения свойств для своих дочерних элементов, иOnRemoved
, чтобы удалять этот обработчик. Этот обработчик должен проверять наличие изменений в этих присоединенных привязываемых свойствах и реагировать на них вызовомInvalidateLayout
.Производный
Layout<T>
, реализующий кэш дочерних размеров, должен переопределитьInvalidateLayout
иOnChildMeasureInvalidated
очистить кэш при вызове этих методов.
Макет с поддержкой свойств
Класс WrapLayout
из Xamarin.FormsBook.Toolkit предполагает, что все его дочерние элементы имеют одинаковый размер и переносит дочерние элементы из одной строки (или столбца) в следующую. Он определяет свойство Orientation
как StackLayout
, свойства ColumnSpacing
и RowSpacing
как Grid
, а также кэширует размеры дочерних элементов.
Пример PhotoWrap размещает WrapLayout
в ScrollView
для отображения фотографий из коллекции.
Неограниченные измерения не допускаются!
Класс UniformGridLayout
из библиотеки Xamarin.FormsBook.Toolkit предназначен для отображения всех своих дочерних элементов внутри себя. Поэтому он не поддерживает неограниченные измерения и в соответствующих случаях вызывает исключение.
Пример PhotoGrid демонстрирует применение UniformGridLayout
.
Перекрывающиеся дочерние элементы
Производный от Layout<T>
элемент допускает перекрытие для дочерних элементов. Но при этом дочерние элементы отображаются том порядке, в котором они включены в коллекцию Children
, а не в порядке вызова методов Layout
.
Класс Layout
определяет два метода, которые позволяют перемещать дочерний объект в пределах коллекции:
LowerChild
для переноса дочернего элемента в начало коллекции;RaiseChild
для переноса дочернего элемента в конец коллекции.
Если применяется перекрытие дочерних элементов, элементы в конце коллекции визуально отображаются поверх элементов в начале коллекции.
Класс OverlapLayout
из библиотеки Xamarin.FormsBook.Toolkit определяет присоединенное свойство для указания порядка отрисовки, позволяя отображать один из дочерних элементов поверх остальных. Пример StudentCardFile демонстрирует такой сценарий.
Дополнительные присоединенные привязываемые свойства
Класс CartesianLayout
из библиотеки Xamarin.FormsBook.Toolkit определяет присоединенные привязываемые свойства для указания двух значений Point
и значения толщины, а также размещает элементы BoxView
в виде линий.
Пример UnitCube использует эту возможность для отрисовки трехмерного куба.
Макет и LayoutTo
Производный от Layout<T>
элемент может вызывать LayoutTo
вместо Layout
для анимации макета. Например, класс AnimatedCartesianLayout
может так делать, и это представлено в примере AnimatedUnitCube.