Создание пользовательского макета в :::no-loc(Xamarin.Forms):::Create a Custom Layout in :::no-loc(Xamarin.Forms):::

Загрузить образец загрузить примерDownload Sample Download the sample

:::no-loc(Xamarin.Forms)::: определяет пять классов макета — StackLayout, Абсолутелайаут, RelativeLayout, Grid и Флекслайаут, каждый из которых упорядочивает свои дочерние элементы другим способом. Однако иногда необходимо упорядочить содержимое страницы, используя макет, не предоставляемый :::no-loc(Xamarin.Forms)::: . В этой статье объясняется, как написать пользовательский класс макета и демонстрируется ориентированный на ориентацию класс Враплайаут, который упорядочивает свои дочерние элементы на странице по горизонтали, а затем переносит отображение последующих дочерних элементов в дополнительные строки.:::no-loc(Xamarin.Forms)::: defines five layout classes – StackLayout, AbsoluteLayout, RelativeLayout, Grid, and FlexLayout, and each arranges its children in a different way. However, sometimes it's necessary to organize page content using a layout not provided by :::no-loc(Xamarin.Forms):::. This article explains how to write a custom layout class, and demonstrates an orientation-sensitive WrapLayout class that arranges its children horizontally across the page, and then wraps the display of subsequent children to additional rows.

В :::no-loc(Xamarin.Forms)::: все классы макета являются производными от Layout<T> класса и ограничивают универсальный тип View и его производные типы.In :::no-loc(Xamarin.Forms):::, all layout classes derive from the Layout<T> class and constrain the generic type to View and its derived types. В свою очередь, Layout<T> класс является производным от Layout класса, который предоставляет механизм для позиционирования и изменения размеров дочерних элементов.In turn, the Layout<T> class derives from the Layout class, which provides the mechanism for positioning and sizing child elements.

Каждый визуальный элемент отвечает за определение собственного предпочтительного размера, который называется запрошенным размером.Every visual element is responsible for determining its own preferred size, which is known as the requested size. Page, Layout и Layout<View> производные типы отвечают за определение расположения и размера своих дочерних или дочерних элементов относительно самих.Page, Layout, and Layout<View> derived types are responsible for determining the location and size of their child, or children, relative to themselves. Таким образом, макет включает связь типа «родители-потомки», где родительский элемент определяет, какой размер дочернего элемента должен быть, но пытается разместить запрошенный размер дочернего элемента.Therefore, layout involves a parent-child relationship, where the parent determines what the size of its children should be, but will attempt to accommodate the requested size of the child.

:::no-loc(Xamarin.Forms):::Чтобы создать пользовательский макет, необходимо глубокое понимание циклов макета и недействительности.A thorough understanding of the :::no-loc(Xamarin.Forms)::: layout and invalidation cycles is required to create a custom layout. Сейчас будут обсуждаться эти циклы.These cycles will now be discussed.

LayoutLayout

Макет начинается в верхней части визуального дерева со страницей и проходит через все ветви визуального дерева, чтобы охватывать каждый визуальный элемент на странице.Layout begins at the top of the visual tree with a page, and it proceeds through all branches of the visual tree to encompass every visual element on a page. Элементы, являющиеся родительскими для других элементов, отвечают за изменение размера и размещение своих потомков относительно самих.Elements that are parents to other elements are responsible for sizing and positioning their children relative to themselves.

VisualElementКласс определяет [ Measure ] (xref: :::no-loc(Xamarin.Forms)::: . Висуалелемент. Measure (System. Double, System. Double, :::no-loc(Xamarin.Forms)::: . Меасурефлагс)) метод, который измеряет элемент для операций макета и [ Layout ] (xref: :::no-loc(Xamarin.Forms)::: . Висуалелемент. Layout ( :::no-loc(Xamarin.Forms)::: . Прямоугольник)), который указывает прямоугольную область, в которой будет отображаться элемент.The VisualElement class defines a Measure method that measures an element for layout operations, and a Layout method that specifies the rectangular area the element will be rendered within. При запуске приложения и отображении первой страницы цикл макета , состоящий из первых Measure вызовов, а затем Layout вызывает метод, начинает с Page объекта:When an application starts and the first page is displayed, a layout cycle consisting first of Measure calls, and then Layout calls, starts on the Page object:

  1. Во время цикла макета каждый родительский элемент отвечает за вызов Measure метода для его дочерних элементов.During the layout cycle, every parent element is responsible for calling the Measure method on its children.
  2. После измерения дочерних элементов каждый родительский элемент отвечает за вызов Layout метода для его дочерних элементов.After the children have been measured, every parent element is responsible for calling the Layout method on its children.

Этот цикл гарантирует, что каждый визуальный элемент на странице будет получать вызовы Measure к Layout методам и.This cycle ensures that every visual element on the page receives calls to the Measure and Layout methods. Этот процесс показан на следующей схеме:The process is shown in the following diagram:

::: No-Loc (Xamarin. Forms)::: цикл макета

Примечание

Обратите внимание, что циклы макета также могут возникать в подмножестве визуального дерева, если какое-либо изменение повлияет на макет.Note that layout cycles can also occur on a subset of the visual tree if something changes to affect the layout. Сюда входят элементы, добавляемые или удаляемые из коллекции, например в StackLayout , изменение IsVisible свойства элемента или изменение размера элемента.This includes items being added or removed from a collection such as in a StackLayout, a change in the IsVisible property of an element, or a change in the size of an element.

Каждый :::no-loc(Xamarin.Forms)::: класс, у которого есть Content свойство или, Children имеет переопределяемый LayoutChildren метод.Every :::no-loc(Xamarin.Forms)::: class that has a Content or a Children property has an overridable LayoutChildren method. Пользовательские классы макета, производные от, Layout<View> должны переопределять этот метод и гарантировать, что [ Measure ] (xref: :::no-loc(Xamarin.Forms)::: . Висуалелемент. Measure (System. Double, System. Double, :::no-loc(Xamarin.Forms)::: . Меасурефлагс)) и [ Layout ] (xref: :::no-loc(Xamarin.Forms)::: . Висуалелемент. Layout ( :::no-loc(Xamarin.Forms)::: . ). Методы вызываются для всех дочерних элементов элемента, чтобы обеспечить требуемый пользовательский макет.Custom layout classes that derive from Layout<View> must override this method and ensure that the Measure and Layout methods are called on all the element's children, to provide the desired custom layout.

Кроме того, каждый класс, производный от Layout или, Layout<View> должен переопределять OnMeasure метод, в котором класс макета определяет размер, который он должен сделать, выполнив вызовы [ Measure ] (xref: :::no-loc(Xamarin.Forms)::: . Висуалелемент. Measure (System. Double, System. Double, :::no-loc(Xamarin.Forms)::: . Меасурефлагс)) дочерними методами.In addition, every class that derives from Layout or Layout<View> must override the OnMeasure method, which is where a layout class determines the size that it needs to be by making calls to the Measure methods of its children.

Примечание

Элементы определяют их размер на основе ограничений , которые указывают, сколько места доступно для элемента в родительском элементе.Elements determine their size based on constraints , which indicate how much space is available for an element within the element's parent. Ограничения, передаваемые [ Measure ] (xref: :::no-loc(Xamarin.Forms)::: . Висуалелемент. Measure (System. Double, System. Double, :::no-loc(Xamarin.Forms)::: . Меасурефлагс)) и OnMeasure методы могут находиться в диапазоне от 0 до Double.PositiveInfinity .Constraints passed to the Measure and OnMeasure methods can range from 0 to Double.PositiveInfinity. Элемент ограничен или полностью ограничен при получении вызова его [ Measure ] (xref: :::no-loc(Xamarin.Forms)::: . Висуалелемент. Measure (System. Double, System. Double, :::no-loc(Xamarin.Forms)::: . Меасурефлагс)) метод с бесконечными аргументами — элемент ограничен определенным размером.An element is constrained , or fully constrained , when it receives a call to its Measure method with non-infinite arguments - the element is constrained to a particular size. Элемент не ограничен или частично ограничен , когда он получает вызов его Measure метода по крайней мере с одним аргументом, равным Double.PositiveInfinity – бесконечное ограничение можно рассматривать как указывающее на автоматическое изменение размера.An element is unconstrained , or partially constrained , when it receives a call to its Measure method with at least one argument equal to Double.PositiveInfinity – the infinite constraint can be thought of as indicating autosizing.

НедействительностьInvalidation

Недействительность — это процесс, с помощью которого изменение элемента на странице вызывает новый цикл макета.Invalidation is the process by which a change in an element on a page triggers a new layout cycle. Элементы считаются недопустимыми, если они больше не имеют нужного размера или расположения.Elements are considered invalid when they no longer have the correct size or position. Например, если FontSize свойство Button изменено, то Button считается недопустимым, так как он больше не будет иметь правильный размер.For example, if the FontSize property of a Button changes, the Button is said to be invalid because it will no longer have the correct size. Изменение размера элемента Button может привести к изменению макета с помощью оставшейся части страницы.Resizing the Button may then have a ripple effect of changes in layout through the rest of a page.

Элементы становятся недействительными при вызове InvalidateMeasure метода, как правило, когда изменяется свойство элемента, которое может привести к новому размеру элемента.Elements invalidate themselves by invoking the InvalidateMeasure method, generally when a property of the element changes that might result in a new size of the element. Этот метод вызывает MeasureInvalidated событие, которое родительские дескрипторы элемента активируют новый цикл макета.This method fires the MeasureInvalidated event, which the element's parent handles to trigger a new layout cycle.

LayoutКласс задает обработчик для MeasureInvalidated события для каждого дочернего элемента, добавленного к его Content свойству или Children коллекции, и отсоединяет обработчик при удалении дочернего элемента.The Layout class sets a handler for the MeasureInvalidated event on every child added to its Content property or Children collection, and detaches the handler when the child is removed. Таким образом, каждый элемент в визуальном дереве, имеющий дочерние элементы, уведомляется каждый раз, когда изменяется размер одного из его дочерних элементов.Therefore, every element in the visual tree that has children is alerted whenever one of its children changes size. На следующей схеме показано, как изменение размера элемента в визуальном дереве может привести к изменениям, которые привели к дереву:The following diagram illustrates how a change in the size of an element in the visual tree can cause changes that ripple up the tree:

Недействительность в визуальном дереве

Однако Layout класс пытается ограничить влияние изменения размера дочернего элемента на макет страницы.However, the Layout class attempts to restrict the impact of a change in a child's size on the layout of a page. Если размер макета ограничен, изменение размера дочернего элемента не влияет на содержимое, превышающее родительский макет в визуальном дереве.If the layout is size constrained, then a child size change does not affect anything higher than the parent layout in the visual tree. Однако обычно изменение размера макета влияет на то, как макет упорядочивает свои дочерние элементы.However, usually a change in the size of a layout affects how the layout arranges its children. Таким образом, любое изменение размера макета приведет к запуску цикла макета для макета, и макет будет принимать вызовы к его OnMeasure LayoutChildren методам и.Therefore, any change in a layout's size will start a layout cycle for the layout, and the layout will receive calls to its OnMeasure and LayoutChildren methods.

LayoutКласс также определяет InvalidateLayout метод, который имеет аналогичное назначение для InvalidateMeasure метода.The Layout class also defines an InvalidateLayout method that has a similar purpose to the InvalidateMeasure method. InvalidateLayoutМетод должен вызываться при каждом изменении, которое влияет на положение макета и размеры его дочерних элементов.The InvalidateLayout method should be invoked whenever a change is made that affects how the layout positions and sizes its children. Например, Layout класс вызывает InvalidateLayout метод всякий раз, когда дочерний элемент добавляется в макет или удаляется из него.For example, the Layout class invokes the InvalidateLayout method whenever a child is added to or removed from a layout.

InvalidateLayoutМожно переопределить, чтобы реализовать кэш для снижения числа повторяющихся вызовов [ Measure ] (xref: :::no-loc(Xamarin.Forms)::: . Висуалелемент. Measure (System. Double, System. Double, :::no-loc(Xamarin.Forms)::: . Меасурефлагс)) методы дочерних элементов макета.The InvalidateLayout can be overridden to implement a cache to minimize repetitive invocations of the Measure methods of the layout's children. Переопределение InvalidateLayout метода предоставит уведомление о том, что дочерние элементы добавляются в макет или удаляются из него.Overriding the InvalidateLayout method will provide a notification of when children are added to or removed from the layout. Аналогичным образом, OnChildMeasureInvalidated метод можно переопределить, чтобы предоставить уведомление, когда изменяется размер одного из дочерних элементов макета.Similarly, the OnChildMeasureInvalidated method can be overridden to provide a notification when one of the layout's children changes size. Для обоих переопределений методов пользовательский макет должен реагировать на очистку кэша.For both method overrides, a custom layout should respond by clearing the cache. Дополнительные сведения см. в разделе Вычисление и кэширование данных макета.For more information, see Calculate and Cache Layout Data.

Создание пользовательского макетаCreate a Custom Layout

Процесс создания пользовательского макета выглядит следующим образом:The process for creating a custom layout is as follows:

  1. Создайте класс, производный от класса Layout<View>.Create a class that derives from the Layout<View> class. Дополнительные сведения см. в разделе Создание враплайаут.For more information, see Create a WrapLayout.

  2. [ необязательно ] Добавьте свойства, которые поддерживаются связываемыми свойствами, для всех параметров, которые должны быть установлены для класса макета.[ optional ] Add properties, backed by bindable properties, for any parameters that should be set on the layout class. Дополнительные сведения см. в разделе Добавление свойств, поддерживаемых связываемыми свойствами.For more information, see Add Properties Backed by Bindable Properties.

  3. Переопределите OnMeasure метод для вызова [ Measure ] (xref: :::no-loc(Xamarin.Forms)::: . Висуалелемент. Measure (System. Double, System. Double, :::no-loc(Xamarin.Forms)::: . Меасурефлагс)) для всех дочерних элементов макета и возвращают запрошенный размер для макета.Override the OnMeasure method to invoke the Measure method on all the layout's children, and return a requested size for the layout. Дополнительные сведения см. в разделе переопределение метода onmeasure.For more information, see Override the OnMeasure Method.

  4. Переопределите LayoutChildren метод для вызова [ Layout ] (xref: :::no-loc(Xamarin.Forms)::: . Висуалелемент. Layout ( :::no-loc(Xamarin.Forms)::: . Прямоугольник)) для всех дочерних элементов макета.Override the LayoutChildren method to invoke the Layout method on all the layout's children. Не удалось вызвать [ Layout ] (xref: :::no-loc(Xamarin.Forms)::: . Висуалелемент. Layout ( :::no-loc(Xamarin.Forms)::: . Прямоугольник)). метод для каждого дочернего элемента в макете приведет к тому, что дочерний объект никогда не будет получать правильный размер или положение, поэтому дочерний элемент не станет видимым на странице.Failure to invoke the Layout method on each child in a layout will result in the child never receiving a correct size or position, and hence the child will not become visible on the page. Дополнительные сведения см. в разделе переопределение метода лайаутчилдрен.For more information, see Override the LayoutChildren Method.

    Примечание

    При перечислении дочерних элементов OnMeasure в LayoutChildren переопределениях и пропустите все дочерние элементы, IsVisible свойство которых имеет значение false .When enumerating children in the OnMeasure and LayoutChildren overrides, skip any child whose IsVisible property is set to false. Это обеспечит, что пользовательский макет не оставляет место для невидимых дочерних элементов.This will ensure that the custom layout won't leave space for invisible children.

  5. [ необязательно ] Переопределите InvalidateLayout метод, чтобы получать уведомления при добавлении или удалении дочерних элементов в макете.[ optional ] Override the InvalidateLayout method to be notified when children are added to or removed from the layout. Дополнительные сведения см. в разделе переопределение метода инвалидателайаут.For more information, see Override the InvalidateLayout Method.

  6. [ необязательно ] Переопределите OnChildMeasureInvalidated метод, чтобы получать уведомления при изменении размера одного из дочерних элементов макета.[ optional ] Override the OnChildMeasureInvalidated method to be notified when one of the layout's children changes size. Дополнительные сведения см. в разделе переопределение метода ончилдмеасуреинвалидатед.For more information, see Override the OnChildMeasureInvalidated Method.

Примечание

Обратите внимание, что OnMeasure Переопределение не будет вызываться, если размер макета регулируется родительским элементом, а не его потомками.Note that the OnMeasure override won't be invoked if the size of the layout is governed by its parent, rather than its children. Однако переопределение будет вызываться, если одно или оба ограничения имеют бесконечное значение или если класс макета имеет значения, отличные от значений по умолчанию HorizontalOptions или VerticalOptions свойств.However, the override will be invoked if one or both of the constraints are infinite, or if the layout class has non-default HorizontalOptions or VerticalOptions property values. По этой причине LayoutChildren Переопределение не может полагаться на дочерние размеры, полученные во время OnMeasure вызова метода.For this reason, the LayoutChildren override can't rely on child sizes obtained during the OnMeasure method call. Вместо этого LayoutChildren должен вызывать [ Measure ] (xref: :::no-loc(Xamarin.Forms)::: . Висуалелемент. Measure (System. Double, System. Double, :::no-loc(Xamarin.Forms)::: . Меасурефлагс)) для дочерних элементов макета перед вызовом [ Layout ] (xref: :::no-loc(Xamarin.Forms)::: . Висуалелемент. Layout ( :::no-loc(Xamarin.Forms)::: . Прямоугольник)).Instead, LayoutChildren must invoke the Measure method on the layout's children, before invoking the Layout method. Кроме того, размер дочерних элементов, полученных в OnMeasure переопределении, можно кэшировать, чтобы избежать более поздних Measure вызовов в LayoutChildren переопределении, но класс макета должен знать, когда размеры необходимо получить снова.Alternatively, the size of the children obtained in the OnMeasure override can be cached to avoid later Measure invocations in the LayoutChildren override, but the layout class will need to know when the sizes need to be obtained again. Дополнительные сведения см. в разделе Вычисление и кэширование данных макета.For more information, see Calculate and Cache Layout Data.

Затем класс макета можно использовать, добавив его в Page и добавив дочерние элементы в макет.The layout class can then be consumed by adding it to a Page, and by adding children to the layout. Дополнительные сведения см. в разделе Использование враплайаут.For more information, see Consume the WrapLayout.

Создание ВраплайаутCreate a WrapLayout

Пример приложения демонстрирует ориентированный на ориентацию WrapLayout класс, который упорядочивает его дочерние элементы по горизонтали на странице, а затем переносит отображение последующих дочерних элементов к дополнительным строкам.The sample application demonstrates an orientation-sensitive WrapLayout class that arranges its children horizontally across the page, and then wraps the display of subsequent children to additional rows.

WrapLayoutКласс выделяет одинаковый объем пространства для каждого дочернего элемента, называемого размером ячейки , на основе максимального размера дочерних элементов.The WrapLayout class allocates the same amount of space for each child, known as the cell size , based on the maximum size of the children. Дочерние элементы, размер которых меньше размера ячейки, можно разместить в ячейке на основе их HorizontalOptions VerticalOptions значений свойств и.Children smaller than the cell size can be positioned within the cell based on their HorizontalOptions and VerticalOptions property values.

WrapLayoutОпределение класса показано в следующем примере кода:The WrapLayout class definition is shown in the following code example:

public class WrapLayout : Layout<View>
{
  Dictionary<Size, LayoutData> layoutDataCache = new Dictionary<Size, LayoutData>();
  ...
}

Вычисление и кэширование данных макетаCalculate and Cache Layout Data

LayoutDataСтруктура хранит данные о коллекции дочерних элементов в ряде свойств:The LayoutData structure stores data about a collection of children in a number of properties:

  • VisibleChildCount — число дочерних элементов, видимых в макете.VisibleChildCount – the number of children that are visible in the layout.
  • CellSize — максимальный размер всех дочерних элементов, настраивается в соответствии с размером макета.CellSize – the maximum size of all the children, adjusted to the size of the layout.
  • Rows — число строк.Rows – the number of rows.
  • Columns — число столбцов.Columns – the number of columns.

layoutDataCacheПоле используется для хранения нескольких LayoutData значений.The layoutDataCache field is used to store multiple LayoutData values. При запуске приложения LayoutData в словарь будут помещены два объекта layoutDataCache для текущей ориентации: один для аргументов ограничения OnMeasure переопределения, а другой — для width height аргументов LayoutChildren переопределения.When the application starts, two LayoutData objects will be cached into the layoutDataCache dictionary for the current orientation – one for the constraint arguments to the OnMeasure override, and one for the width and height arguments to the LayoutChildren override. При повороте устройства на альбомную ориентацию OnMeasure Переопределение и LayoutChildren Переопределение снова будет вызываться, что приведет к кэшированию других двух LayoutData объектов в словаре.When rotating the device into landscape orientation, the OnMeasure override and the LayoutChildren override will again be invoked, which will result in another two LayoutData objects being cached into the dictionary. Однако при возврате устройства к книжной ориентации дальнейшие вычисления не требуются, поскольку у layoutDataCache уже есть необходимые данные.However, when returning the device to portrait orientation, no further calculations are required because the layoutDataCache already has the required data.

В следующем примере кода показан GetLayoutData метод, который вычисляет свойства LayoutData структурированного объекта на основе определенного размера:The following code example shows the GetLayoutData method, which calculates the properties of the LayoutData structured based on a particular size:

LayoutData GetLayoutData(double width, double height)
{
  Size size = new Size(width, height);

  // Check if cached information is available.
  if (layoutDataCache.ContainsKey(size))
  {
    return layoutDataCache[size];
  }

  int visibleChildCount = 0;
  Size maxChildSize = new Size();
  int rows = 0;
  int columns = 0;
  LayoutData layoutData = new LayoutData();

  // Enumerate through all the children.
  foreach (View child in Children)
  {
    // Skip invisible children.
    if (!child.IsVisible)
      continue;

    // Count the visible children.
    visibleChildCount++;

    // Get the child's requested size.
    SizeRequest childSizeRequest = child.Measure(Double.PositiveInfinity, Double.PositiveInfinity);

    // Accumulate the maximum child size.
    maxChildSize.Width = Math.Max(maxChildSize.Width, childSizeRequest.Request.Width);
    maxChildSize.Height = Math.Max(maxChildSize.Height, childSizeRequest.Request.Height);
  }

  if (visibleChildCount != 0)
  {
    // Calculate the number of rows and columns.
    if (Double.IsPositiveInfinity(width))
    {
      columns = visibleChildCount;
      rows = 1;
    }
    else
    {
      columns = (int)((width + ColumnSpacing) / (maxChildSize.Width + ColumnSpacing));
      columns = Math.Max(1, columns);
      rows = (visibleChildCount + columns - 1) / columns;
    }

    // Now maximize the cell size based on the layout size.
    Size cellSize = new Size();

    if (Double.IsPositiveInfinity(width))
      cellSize.Width = maxChildSize.Width;
    else
      cellSize.Width = (width - ColumnSpacing * (columns - 1)) / columns;

    if (Double.IsPositiveInfinity(height))
      cellSize.Height = maxChildSize.Height;
    else
      cellSize.Height = (height - RowSpacing * (rows - 1)) / rows;

    layoutData = new LayoutData(visibleChildCount, cellSize, rows, columns);
  }

  layoutDataCache.Add(size, layoutData);
  return layoutData;
}

GetLayoutDataМетод выполняет следующие операции:The GetLayoutData method performs the following operations:

  • Он определяет, что вычисляемое LayoutData значение уже находится в кэше, и возвращает его, если он доступен.It determines whether a calculated LayoutData value is already in the cache and returns it if it's available.
  • В противном случае он перебирает все дочерние элементы, вызывая Measure метод для каждого дочернего элемента с неограниченной шириной и высотой, и определяет максимальный размер дочернего элемента.Otherwise, it enumerates through all the children, invoking the Measure method on each child with an infinite width and height, and determines the maximum child size.
  • При условии, что имеется по крайней мере один видимый дочерний элемент, он вычисляет необходимое количество строк и столбцов, а затем вычисляет размер ячейки для дочерних элементов на основе измерений WrapLayout .Provided that there's at least one visible child, it calculates the number of rows and columns required, and then calculates a cell size for the children based on the dimensions of the WrapLayout. Обратите внимание, что размер ячейки, как правило, немного шире, чем максимальный размер дочернего элемента, но он также может быть меньше, если WrapLayout недостаточно для самого широкого дочернего или достаточного размера для самого высокого дочернего элемента.Note that the cell size is usually slightly wider than the maximum child size, but that it could also be smaller if the WrapLayout isn't wide enough for the widest child or tall enough for the tallest child.
  • Он сохраняет новое LayoutData значение в кэше.It stores the new LayoutData value in the cache.

Добавление свойств, которые поддерживаются с помощью привязки свойствAdd Properties Backed by Bindable Properties

WrapLayoutКласс определяет ColumnSpacing Свойства и RowSpacing , значения которых используются для разделения строк и столбцов в макете и для которых поддерживаются привязываемые свойства.The WrapLayout class defines ColumnSpacing and RowSpacing properties, whose values are used to separate the rows and columns in the layout, and which are backed by bindable properties. Свойства, доступные для привязки, показаны в следующем примере кода:The bindable properties are shown in the following code example:

public static readonly BindableProperty ColumnSpacingProperty = BindableProperty.Create(
  "ColumnSpacing",
  typeof(double),
  typeof(WrapLayout),
  5.0,
  propertyChanged: (bindable, oldvalue, newvalue) =>
  {
    ((WrapLayout)bindable).InvalidateLayout();
  });

public static readonly BindableProperty RowSpacingProperty = BindableProperty.Create(
  "RowSpacing",
  typeof(double),
  typeof(WrapLayout),
  5.0,
  propertyChanged: (bindable, oldvalue, newvalue) =>
  {
    ((WrapLayout)bindable).InvalidateLayout();
  });

Обработчик изменения свойств для каждого привязываемого свойства вызывает InvalidateLayout Переопределение метода, чтобы активировать новый проход макета в WrapLayout .The property-changed handler of each bindable property invokes the InvalidateLayout method override to trigger a new layout pass on the WrapLayout. Дополнительные сведения см. в разделе Переопределение метода инвалидателайаут и Переопределение метода ончилдмеасуреинвалидатед.For more information, see Override the InvalidateLayout Method and Override the OnChildMeasureInvalidated Method.

Переопределение метода onmeasureOverride the OnMeasure Method

OnMeasureПереопределение показано в следующем примере кода:The OnMeasure override is shown in the following code example:

protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
  LayoutData layoutData = GetLayoutData(widthConstraint, heightConstraint);
  if (layoutData.VisibleChildCount == 0)
  {
    return new SizeRequest();
  }

  Size totalSize = new Size(layoutData.CellSize.Width * layoutData.Columns + ColumnSpacing * (layoutData.Columns - 1),
                layoutData.CellSize.Height * layoutData.Rows + RowSpacing * (layoutData.Rows - 1));
  return new SizeRequest(totalSize);
}

Переопределение вызывает GetLayoutData метод и создает SizeRequest объект из возвращенных данных, а также учитывает RowSpacing ColumnSpacing значения свойств и.The override invokes the GetLayoutData method and constructs a SizeRequest object from the returned data, while also taking into account the RowSpacing and ColumnSpacing property values. Дополнительные сведения о GetLayoutData методе см. в разделе Вычисление и кэширование данных макета.For more information about the GetLayoutData method, see Calculate and Cache Layout Data.

Важно!

[ Measure ] (Xref: :::no-loc(Xamarin.Forms)::: . Висуалелемент. Measure (System. Double, System. Double, :::no-loc(Xamarin.Forms)::: . Меасурефлагс)) и OnMeasure методы никогда не должны запрашивать бесконечное измерение, возвращая SizeRequest значение со свойством, равным Double.PositiveInfinity .The Measure and OnMeasure methods should never request an infinite dimension by returning a SizeRequest value with a property set to Double.PositiveInfinity. Однако по крайней мере один из аргументов ограничения OnMeasure может иметь значение Double.PositiveInfinity .However, at least one of the constraint arguments to OnMeasure can be Double.PositiveInfinity.

Переопределение метода ЛайаутчилдренOverride the LayoutChildren Method

LayoutChildrenПереопределение показано в следующем примере кода:The LayoutChildren override is shown in the following code example:

protected override void LayoutChildren(double x, double y, double width, double height)
{
  LayoutData layoutData = GetLayoutData(width, height);

  if (layoutData.VisibleChildCount == 0)
  {
    return;
  }

  double xChild = x;
  double yChild = y;
  int row = 0;
  int column = 0;

  foreach (View child in Children)
  {
    if (!child.IsVisible)
    {
      continue;
    }

    LayoutChildIntoBoundingRegion(child, new Rectangle(new Point(xChild, yChild), layoutData.CellSize));
    if (++column == layoutData.Columns)
    {
      column = 0;
      row++;
      xChild = x;
      yChild += RowSpacing + layoutData.CellSize.Height;
    }
    else
    {
      xChild += ColumnSpacing + layoutData.CellSize.Width;
    }
  }
}

Переопределение начинается с вызова GetLayoutData метода, а затем перечисляет все дочерние элементы для изменения размера и размещает их в каждой ячейке дочернего элемента.The override begins with a call to the GetLayoutData method, and then enumerates all of the children to size and position them within each child's cell. Это достигается путем вызова [ LayoutChildIntoBoundingRegion ] (xref: :::no-loc(Xamarin.Forms)::: . Layout. Лайаутчилдинтобаундингрегион ( :::no-loc(Xamarin.Forms)::: . Висуалелемент, :::no-loc(Xamarin.Forms)::: . ). Этот метод используется для размещения дочернего элемента внутри прямоугольника на основе его HorizontalOptions VerticalOptions значений свойств и.This is achieved by invoking the LayoutChildIntoBoundingRegion method, which is used to position a child within a rectangle based on its HorizontalOptions and VerticalOptions property values. Это эквивалентно выполнению вызова [ Layout ] (xref: :::no-loc(Xamarin.Forms)::: . Висуалелемент. Layout ( :::no-loc(Xamarin.Forms)::: . Прямоугольник)).This is equivalent to making a call to the child's Layout method.

Примечание

Обратите внимание, что прямоугольник, переданный в LayoutChildIntoBoundingRegion метод, включает всю область, в которой может находиться дочерний элемент.Note that the rectangle passed to the LayoutChildIntoBoundingRegion method includes the whole area in which the child can reside.

Дополнительные сведения о GetLayoutData методе см. в разделе Вычисление и кэширование данных макета.For more information about the GetLayoutData method, see Calculate and Cache Layout Data.

Переопределение метода ИнвалидателайаутOverride the InvalidateLayout Method

InvalidateLayoutПереопределение вызывается, когда дочерние элементы добавляются в макет или удаляются из макета или когда одно из WrapLayout свойств изменяет значение, как показано в следующем примере кода:The InvalidateLayout override is invoked when children are added to or removed from the layout, or when one of the WrapLayout properties changes value, as shown in the following code example:

protected override void InvalidateLayout()
{
  base.InvalidateLayout();
  layoutInfoCache.Clear();
}

Переопределение делает недействительным макет и отменяет все кэшированные сведения о макете.The override invalidates the layout and discards all the cached layout information.

Примечание

Чтобы предотвратить Layout вызов класса InvalidateLayout при каждом добавлении или удалении дочернего элемента из макета, переопределите [ ShouldInvalidateOnChildAdded ] (xref: :::no-loc(Xamarin.Forms)::: . Layout. Шаулдинвалидатеончилдаддед ( :::no-loc(Xamarin.Forms)::: . View)) и [ ShouldInvalidateOnChildRemoved ] (xref: :::no-loc(Xamarin.Forms)::: . Layout. Шаулдинвалидатеончилдремовед ( :::no-loc(Xamarin.Forms)::: . View)) методы и возвращают false .To stop the Layout class invoking the InvalidateLayout method whenever a child is added to or removed from a layout, override the ShouldInvalidateOnChildAdded and ShouldInvalidateOnChildRemoved methods, and return false. Затем класс макета может реализовать пользовательский процесс при добавлении или удалении дочерних элементов.The layout class can then implement a custom process when children are added or removed.

Переопределение метода ОнчилдмеасуреинвалидатедOverride the OnChildMeasureInvalidated Method

OnChildMeasureInvalidatedПереопределение вызывается при изменении размера одного из дочерних элементов макета и показан в следующем примере кода:The OnChildMeasureInvalidated override is invoked when one of the layout's children changes size, and is shown in the following code example:

protected override void OnChildMeasureInvalidated()
{
  base.OnChildMeasureInvalidated();
  layoutInfoCache.Clear();
}

Переопределение делает недействительным дочерний макет и удаляет все кэшированные сведения о макете.The override invalidates the child layout, and discards all of the cached layout information.

Использование ВраплайаутConsume the WrapLayout

WrapLayoutКласс можно использовать, поместив его в Page производный тип, как показано в следующем примере кода XAML:The WrapLayout class can be consumed by placing it on a Page derived type, as demonstrated in the following XAML code example:

<ContentPage ... xmlns:local="clr-namespace:ImageWrapLayout">
    <ScrollView Margin="0,20,0,20">
        <local:WrapLayout x:Name="wrapLayout" />
    </ScrollView>
</ContentPage>

Ниже приведен эквивалентный код на C#:The equivalent C# code is shown below:

public class ImageWrapLayoutPageCS : ContentPage
{
  WrapLayout wrapLayout;

  public ImageWrapLayoutPageCS()
  {
    wrapLayout = new WrapLayout();

    Content = new ScrollView
    {
      Margin = new Thickness(0, 20, 0, 20),
      Content = wrapLayout
    };
  }
  ...
}

Потомки дочерние элементы могут быть добавлены в WrapLayout при необходимости.Children can then be added to the WrapLayout as required. В следующем примере кода показаны Image элементы, добавляемые в WrapLayout :The following code example shows Image elements being added to the WrapLayout:

protected override async void OnAppearing()
{
    base.OnAppearing();

    var images = await GetImageListAsync();
    if (images != null)
    {
        foreach (var photo in images.Photos)
        {
            var image = new Image
            {
                Source = ImageSource.FromUri(new Uri(photo))
            };
            wrapLayout.Children.Add(image);
        }
    }
}

async Task<ImageList> GetImageListAsync()
{
    try
    {
        string requestUri = "https://raw.githubusercontent.com/xamarin/docs-archive/master/Images/stock/small/stock.json";
        string result = await _client.GetStringAsync(requestUri);
        return JsonConvert.DeserializeObject<ImageList>(result);
    }
    catch (Exception ex)
    {
        Debug.WriteLine($"\tERROR: {ex.Message}");
    }

    return null;
}

Когда отображается страница, содержащая WrapLayout , пример приложения асинхронно обращается к удаленному JSON-файлу, содержащему список фотографий, создает Image элемент для каждой фотографии и добавляет его в WrapLayout .When the page containing the WrapLayout appears, the sample application asynchronously accesses a remote JSON file containing a list of photos, creates an Image element for each photo, and adds it to the WrapLayout. Результат показан на следующих снимках экрана.This results in the appearance shown in the following screenshots:

Пример приложения, портретные снимки экрана

На следующих снимках экрана показано, что WrapLayout после поворота к альбомной ориентации:The following screenshots show the WrapLayout after it's been rotated to landscape orientation:

Пример экрана с альбомной ориентацией на примере приложения iOS пример приложения на снимке экрана с альбомной ориентацией на приложение   UWPSample iOS Application Landscape Screenshot Sample Android Application Landscape Screenshot Sample UWP Application Landscape Screenshot

Количество столбцов в каждой строке зависит от размера фотографии, ширины экрана и количества пикселов на устройство, независимое от устройства.The number of columns in each row depends on the photo size, the screen width, and the number of pixels per device-independent unit. ImageЭлементы асинхронно загружают фотографии, поэтому WrapLayout класс будет получать частые вызовы к своему LayoutChildren методу, так как каждый Image элемент получает новый размер на основе загруженной фотографии.The Image elements asynchronously load the photos, and therefore the WrapLayout class will receive frequent calls to its LayoutChildren method as each Image element receives a new size based on the loaded photo.