Поделиться через


Сводка по главе 26. Пользовательские макеты

Примечание.

Эта книга была опубликована весной 2016 года и с тех пор не обновлялась. Многое в этой книге остается ценным, но некоторые материалы устарели, а некоторые разделы перестали быть полностью верными или полными.

Xamarin.Forms включает несколько классов, производных от Layout<View>:

  • StackLayout,
  • Grid,
  • AbsoluteLayout и
  • RelativeLayout.

В этой главе описано, как создавать собственные классы, производные от Layout<View>.

Общие сведения о макете.

Не существует централизованной системы для обработки макета Xamarin.Forms. Каждый элемент самостоятельно определяет свой размер и способ отображения в определенной области.

Родительские и дочерние объекты

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

Размер и размещение

Построение макета начинается с верхнего уровня визуального дерева, где располагается узел страницы, и распространяется на все его ветви. Самым важным открытым методом в макете является Layout, определяемый в VisualElement. Каждый элемент, являющийся родительским для других элементов, вызывает Layout для каждого дочернего элемента, чтобы предоставить размер и расположение относительно себя в формате значения Rectangle. Эти вызовы Layout распространяются по всему визуальному дереву.

Вызов Layout требуется для того, чтобы элемент отображался на экране. Он устанавливает следующие свойства только для чтения. Они должны соответствовать Rectangle, который был передан в этот метод:

  • Bounds типа Rectangle
  • X типа double
  • Y типа double
  • Width типа double
  • Height типа double

Перед вызовом Layout Height и Width у вас есть макетные значения –1.

Вызов Layout также вызывает следующие защищенные методы:

Наконец, срабатывает следующее событие:

Метод OnSizeAllocated переопределяется классами Page и Layout. Это единственные два класса Xamarin.Forms, которые могут иметь дочерние элементы. Переопределенный метод вызывает следующее:

Затем LayoutChildren вызывает Layout для каждого из дочерних элементов этого элемента. Если хотя бы один дочерний элемент имеет новое значение Bounds, возникает следующее событие:

Запросы ограничений и размера

Чтобы элемент LayoutChildren правильно вызывал Layout для всех дочерних элементов, он должен знать предпочтительный или желаемый размер этих дочерних элементов. Поэтому перед вызовами Layout для каждого из дочерних элементов обычно выполняются вызовы

После публикации книги метод GetSizeRequest был объявлен нерекомендуемым и заменен на

Метод Measure поддерживает свойство Margin и содержит аргумент типа MeasureFlag, который имеет два элемента:

Для многих элементов 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 при любых изменениях, которое влияют на положение и размеры его дочерних элементов.

Некоторые правила для программирования макетов

  1. Свойства, определенные для производных от Layout<T>, должны поддерживаться привязываемыми свойствами, а обработчики изменений свойств должны вызывать InvalidateLayout.

  2. Производный от Layout<T> элемент, который определяет присоединенные привязываемые свойства, должен переопределять OnAdded, чтобы добавлять обработчик изменения свойств для своих дочерних элементов, и OnRemoved, чтобы удалять этот обработчик. Этот обработчик должен проверять наличие изменений в этих присоединенных привязываемых свойствах и реагировать на них вызовом InvalidateLayout.

  3. Производный Layout<T> , реализующий кэш дочерних размеров, должен переопределить InvalidateLayout и OnChildMeasureInvalidated очистить кэш при вызове этих методов.

Макет с поддержкой свойств

Класс WrapLayout из Xamarin.FormsBook.Toolkit предполагает, что все его дочерние элементы имеют одинаковый размер и переносит дочерние элементы из одной строки (или столбца) в следующую. Он определяет свойство Orientation как StackLayout, свойства ColumnSpacing и RowSpacing как Grid, а также кэширует размеры дочерних элементов.

Пример PhotoWrap размещает WrapLayout в ScrollView для отображения фотографий из коллекции.

Неограниченные измерения не допускаются!

Класс UniformGridLayout из библиотеки Xamarin.FormsBook.Toolkit предназначен для отображения всех своих дочерних элементов внутри себя. Поэтому он не поддерживает неограниченные измерения и в соответствующих случаях вызывает исключение.

Пример PhotoGrid демонстрирует применение UniformGridLayout.

Три снимка экрана с элементом PhotoGrid

Перекрывающиеся дочерние элементы

Производный от 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.