Układy niestandardowe
Interfejs użytkownika aplikacji wieloplatformowej platformy .NET (.NET MAUI) definiuje wiele klas układów, które każda z nich rozmieszcza swoje elementy podrzędne w inny sposób. Układ można traktować jako listę widoków z regułami i właściwościami, które definiują sposób rozmieszczania tych widoków w układzie. Przykłady układów to Grid, AbsoluteLayouti VerticalStackLayout.
Klasy układu .NET MAUI pochodzą z klasy abstrakcyjnej Layout . Ta klasa deleguje układ i pomiar międzyplatformowy do klasy menedżera układu. Klasa Layout zawiera również metodę zastępowalną CreateLayoutManager() , która może służyć do określania menedżera układów.
Każda klasa menedżera układów implementuje ILayoutManager interfejs, który określa, że ArrangeChildren Measure i należy podać implementacje:
- Implementacja Measure wywołuje IView.Measure każdy widok w układzie i zwraca całkowity rozmiar układu, biorąc pod uwagę ograniczenia.
- Implementacja ArrangeChildren określa, gdzie każdy widok powinien być umieszczony w granicach układu, i wywołuje Arrange każdy widok z odpowiednimi granicami. Wartość zwracana jest rzeczywistym rozmiarem układu.
Układy programu .NET MAUI mają wstępnie zdefiniowane menedżery układów do obsługi ich układu. Czasami jednak konieczne jest organizowanie zawartości strony przy użyciu układu, który nie jest udostępniany przez program .NET MAUI. Można to osiągnąć, tworząc własny układ niestandardowy, który wymaga zrozumienia, jak działa proces układu międzyplatformowego programu .NET MAUI.
Proces układu
Wieloplatformowy proces układu programu .NET MAUI opiera się na natywnym procesie układu na każdej platformie. Ogólnie rzecz biorąc, proces układu jest inicjowany przez system układu natywnego. Proces międzyplatformowy jest uruchamiany, gdy układ lub kontrolka zawartości inicjuje go w wyniku pomiaru lub rozmieszczania przez system układu natywnego.
Uwaga
Każda platforma obsługuje układ nieco inaczej. Jednak wieloplatformowy proces układu programu MAUI platformy .NET ma na celu zapewnienie tak niezależnego od platformy, jak to możliwe.
Na poniższym diagramie przedstawiono proces, gdy system układu natywnego inicjuje pomiar układu:
Wszystkie układy MAUI platformy .NET mają jeden widok tworzenia kopii zapasowych na każdej platformie:
- W systemie Android ten widok kopii zapasowej to
LayoutViewGroup
. - W systemach iOS i Mac Catalyst ten widok kopii zapasowej to
LayoutView
. - W systemie Windows ten widok kopii zapasowej to
LayoutPanel
.
Gdy system układu natywnego dla platformy żąda pomiaru jednego z tych widoków zapasowych, widok zapasowy wywołuje metodę Layout.CrossPlatformMeasure . Jest to punkt, w którym kontrolka jest przekazywana z systemu układu natywnego do systemu układów .NET MAUI. Layout.CrossPlatformMeasure wywołuje metodę menedżerów Measure układu. Ta metoda jest odpowiedzialna za mierzenie widoków podrzędnych przez wywołanie IView.Measure każdego widoku w układzie. Widok mierzy jego natywną kontrolkę i aktualizuje jej DesiredSize właściwość na podstawie tej miary. Ta wartość jest zwracana do widoku zapasowego CrossPlatformMeasure
w wyniku metody . Widok zapasowy wykonuje dowolne wewnętrzne przetwarzanie, które musi wykonać, i zwraca mierzony rozmiar do platformy.
Na poniższym diagramie przedstawiono proces, gdy system układu natywnego inicjuje układ układu:
Gdy system układu natywnego dla platformy żąda układu lub układu jednego z tych widoków zapasowych, widok zapasowy wywołuje metodę Layout.CrossPlatformArrange . Jest to punkt, w którym kontrolka jest przekazywana z systemu układu natywnego do systemu układów .NET MAUI. Layout.CrossPlatformArrange wywołuje metodę menedżerów ArrangeChildren układu. Ta metoda jest odpowiedzialna za określenie, gdzie każdy widok powinien być umieszczony w granicach układu, i wywołuje Arrange każdy widok, aby ustawić jego lokalizację. Rozmiar układu jest zwracany do widoku kopii zapasowej CrossPlatformArrange
w wyniku metody . Widok zapasowy wykonuje dowolne wewnętrzne przetwarzanie, które musi wykonać, i zwraca rzeczywisty rozmiar do platformy.
Uwaga
ILayoutManager.Measure może być wywoływana wiele razy przed ArrangeChildren wywołaniem, ponieważ platforma może wymagać wykonania pewnych pomiarów spekulatywnych przed rozmieszczeniem widoków.
Podejścia do układu niestandardowego
Istnieją dwa główne podejścia do tworzenia układu niestandardowego:
- Utwórz niestandardowy typ układu, który jest zwykle podklasą istniejącego typu układu lub Layouttypu , i przesłaniaj CreateLayoutManager() w niestandardowym typie układu. Następnie podaj implementację zawierającą niestandardową logikę ILayoutManager układu. Aby uzyskać więcej informacji, zobacz Tworzenie niestandardowego typu układu.
- Zmodyfikuj zachowanie istniejącego typu układu, tworząc typ implementujący ILayoutManagerFactoryelement . Następnie użyj tej fabryki menedżera układów, aby zastąpić domyślny menedżer układów programu .NET MAUI dla istniejącego układu własną ILayoutManager implementacją zawierającą niestandardową logikę układu. Aby uzyskać więcej informacji, zobacz Modyfikowanie zachowania istniejącego układu.
Tworzenie niestandardowego typu układu
Proces tworzenia niestandardowego typu układu to:
Utwórz klasę, która podklasuje istniejący typ układu lub Layout klasę, i przesłoń CreateLayoutManager() w niestandardowym typie układu. Aby uzyskać więcej informacji, zobacz Podklasa układu.
Utwórz klasę menedżera układów, która pochodzi z istniejącego menedżera układów lub który implementuje ILayoutManager interfejs bezpośrednio. W klasie Menedżer układu należy wykonać:
- Przesłoń lub zaimplementuj metodę Measure w celu obliczenia całkowitego rozmiaru układu, biorąc pod uwagę jego ograniczenia.
- Zastąpij lub zaimplementuj metodę ArrangeChildren , aby rozmiar i umieścić wszystkie elementy podrzędne w układzie.
Aby uzyskać więcej informacji, zobacz Tworzenie menedżera układów.
Zużyj typ układu niestandardowego, dodając go do Pageelementu i dodając elementy podrzędne do układu. Aby uzyskać więcej informacji, zobacz Korzystanie z typu układu.
Do zademonstrowania tego procesu służy rozróżniana HorizontalWrapLayout
orientacja. HorizontalWrapLayout
element jest podobny do HorizontalStackLayout elementu , który rozmieszcza swoje elementy podrzędne w poziomie na całej stronie. Jednak opakowuje wyświetlanie elementów podrzędnych w nowym wierszu, gdy napotka prawą krawędź kontenera
Uwaga
W przykładzie zdefiniowano dodatkowe układy niestandardowe, których można użyć do zrozumienia sposobu tworzenia układu niestandardowego.
Podklasa układu
Aby utworzyć typ układu niestandardowego, musisz najpierw utworzyć podklasę istniejącego typu układu lub klasę Layout . Następnie zastąpij CreateLayoutManager() typ układu i zwróć nowe wystąpienie menedżera układu dla typu układu:
using Microsoft.Maui.Layouts;
public class HorizontalWrapLayout : HorizontalStackLayout
{
protected override ILayoutManager CreateLayoutManager()
{
return new HorizontalWrapLayoutManager(this);
}
}
HorizontalWrapLayout
pochodzi z HorizontalStackLayout , aby używać jego funkcji układu. Układy MAUI platformy .NET delegować układ międzyplatformowy i pomiar do klasy menedżera układu. CreateLayoutManager() W związku z tym przesłonięcia zwraca nowe wystąpienie HorizontalWrapLayoutManager
klasy, czyli menedżera układu omówionego w następnej sekcji.
Tworzenie menedżera układów
Klasa menedżera układów służy do wykonywania układu i pomiaru międzyplatformowego dla niestandardowego typu układu. Powinien pochodzić z istniejącego menedżera układów lub powinien bezpośrednio zaimplementować ILayoutManager interfejs. HorizontalWrapLayoutManager
pochodzi z HorizontalStackLayoutManager programu , aby można było używać jego podstawowych funkcji i elementów członkowskich dostępu w hierarchii dziedziczenia:
using Microsoft.Maui.Layouts;
using HorizontalStackLayoutManager = Microsoft.Maui.Layouts.HorizontalStackLayoutManager;
public class HorizontalWrapLayoutManager : HorizontalStackLayoutManager
{
HorizontalWrapLayout _layout;
public HorizontalWrapLayoutManager(HorizontalWrapLayout horizontalWrapLayout) : base(horizontalWrapLayout)
{
_layout = horizontalWrapLayout;
}
public override Size Measure(double widthConstraint, double heightConstraint)
{
}
public override Size ArrangeChildren(Rect bounds)
{
}
}
Konstruktor HorizontalWrapLayoutManager
przechowuje wystąpienie HorizontalWrapLayout
typu w polu, aby można było uzyskać do niego dostęp w menedżerze układu. Menedżer układu zastępuje Measure również metody i ArrangeChildren z HorizontalStackLayoutManager klasy . W tych metodach zdefiniujesz logikę implementowania układu niestandardowego.
Mierzenie rozmiaru układu
Celem implementacji ILayoutManager.Measure jest obliczenie całkowitego rozmiaru układu. Powinno to zrobić przez wywołanie IView.Measure każdego elementu podrzędnego w układzie. Następnie należy użyć tych danych do obliczenia i zwrócenia całkowitego rozmiaru układu, biorąc pod uwagę jego ograniczenia.
W poniższym przykładzie pokazano implementację Measure HorizontalWrapLayoutManager
klasy :
public override Size Measure(double widthConstraint, double heightConstraint)
{
var padding = _layout.Padding;
widthConstraint -= padding.HorizontalThickness;
double currentRowWidth = 0;
double currentRowHeight = 0;
double totalWidth = 0;
double totalHeight = 0;
for (int n = 0; n < _layout.Count; n++)
{
var child = _layout[n];
if (child.Visibility == Visibility.Collapsed)
{
continue;
}
var measure = child.Measure(double.PositiveInfinity, heightConstraint);
// Will adding this IView put us past the edge?
if (currentRowWidth + measure.Width > widthConstraint)
{
// Keep track of the width so far
totalWidth = Math.Max(totalWidth, currentRowWidth);
totalHeight += currentRowHeight;
// Account for spacing
totalHeight += _layout.Spacing;
// Start over at 0
currentRowWidth = 0;
currentRowHeight = measure.Height;
}
currentRowWidth += measure.Width;
currentRowHeight = Math.Max(currentRowHeight, measure.Height);
if (n < _layout.Count - 1)
{
currentRowWidth += _layout.Spacing;
}
}
// Account for the last row
totalWidth = Math.Max(totalWidth, currentRowWidth);
totalHeight += currentRowHeight;
// Account for padding
totalWidth += padding.HorizontalThickness;
totalHeight += padding.VerticalThickness;
// Ensure that the total size of the layout fits within its constraints
var finalWidth = ResolveConstraints(widthConstraint, Stack.Width, totalWidth, Stack.MinimumWidth, Stack.MaximumWidth);
var finalHeight = ResolveConstraints(heightConstraint, Stack.Height, totalHeight, Stack.MinimumHeight, Stack.MaximumHeight);
return new Size(finalWidth, finalHeight);
}
Metoda Measure(Double, Double) wylicza wszystkie widoczne elementy podrzędne w układzie, wywołując metodę IView.Measure dla każdego elementu podrzędnego. Następnie zwraca całkowity rozmiar układu, uwzględniając ograniczenia i wartości Padding właściwości i Spacing . Metoda jest wywoływana ResolveConstraints w celu zapewnienia, że całkowity rozmiar układu mieści się w jego ograniczeniach.
Ważne
Podczas wyliczania elementów podrzędnych w implementacji ILayoutManager.Measure pomiń wszystkie elementy podrzędne, których Visibility właściwość jest ustawiona na Collapsed. Dzięki temu układ niestandardowy nie pozostawi miejsca dla niewidocznych elementów podrzędnych.
Rozmieszczanie elementów podrzędnych w układzie
Celem implementacji ArrangeChildren jest rozmiar i położenie wszystkich elementów podrzędnych w układzie. Aby określić, gdzie każdy element podrzędny powinien zostać umieszczony w granicach układu, powinien wywołać Arrange element podrzędny z odpowiednimi granicami. Następnie powinna zwrócić wartość reprezentującą rzeczywisty rozmiar układu.
Ostrzeżenie
Niepowodzenie wywołania ArrangeChildren metody dla każdego elementu podrzędnego w układzie spowoduje, że element podrzędny nigdy nie otrzymuje poprawnego rozmiaru lub położenia, dlatego element podrzędny nie stanie się widoczny na stronie.
W poniższym przykładzie pokazano implementację ArrangeChildren HorizontalWrapLayoutManager
klasy :
public override Size ArrangeChildren(Rect bounds)
{
var padding = Stack.Padding;
double top = padding.Top + bounds.Top;
double left = padding.Left + bounds.Left;
double currentRowTop = top;
double currentX = left;
double currentRowHeight = 0;
double maxStackWidth = currentX;
for (int n = 0; n < _layout.Count; n++)
{
var child = _layout[n];
if (child.Visibility == Visibility.Collapsed)
{
continue;
}
if (currentX + child.DesiredSize.Width > bounds.Right)
{
// Keep track of our maximum width so far
maxStackWidth = Math.Max(maxStackWidth, currentX);
// Move down to the next row
currentX = left;
currentRowTop += currentRowHeight + _layout.Spacing;
currentRowHeight = 0;
}
var destination = new Rect(currentX, currentRowTop, child.DesiredSize.Width, child.DesiredSize.Height);
child.Arrange(destination);
currentX += destination.Width + _layout.Spacing;
currentRowHeight = Math.Max(currentRowHeight, destination.Height);
}
var actual = new Size(maxStackWidth, currentRowTop + currentRowHeight);
// Adjust the size if the layout is set to fill its container
return actual.AdjustForFill(bounds, Stack);
}
Metoda ArrangeChildren
wylicza wszystkie widoczne elementy podrzędne w układzie w celu ich rozmiaru i położenia w układzie. Robi to przez wywołanie Arrange każdego elementu podrzędnego z odpowiednimi granicami, które uwzględniają Padding i Spacing układu bazowego. Następnie zwraca rzeczywisty rozmiar układu. Metoda jest wywoływana w celu upewnienia AdjustForFill się, że rozmiar uwzględnia, czy układ ma jego HorizontalLayoutAlignment właściwości i VerticalLayoutAlignment ma wartość LayoutOptions.Fill.
Ważne
Podczas wyliczania elementów podrzędnych w implementacji ArrangeChildren pomiń wszystkie elementy podrzędne, których Visibility właściwość jest ustawiona na Collapsed. Dzięki temu układ niestandardowy nie pozostawi miejsca dla niewidocznych elementów podrzędnych.
Korzystanie z typu układu
Klasę HorizontalWrapLayout
można użyć, umieszczając ją w typie pochodnym Page :
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:layouts="clr-namespace:CustomLayoutDemos.Layouts"
x:Class="CustomLayoutDemos.Views.HorizontalWrapLayoutPage"
Title="Horizontal wrap layout">
<ScrollView Margin="20">
<layouts:HorizontalWrapLayout Spacing="20">
<Image Source="img_0074.jpg"
WidthRequest="150" />
<Image Source="img_0078.jpg"
WidthRequest="150" />
<Image Source="img_0308.jpg"
WidthRequest="150" />
<Image Source="img_0437.jpg"
WidthRequest="150" />
<Image Source="img_0475.jpg"
WidthRequest="150" />
<Image Source="img_0613.jpg"
WidthRequest="150" />
<!-- More images go here -->
</layouts:HorizontalWrapLayout>
</ScrollView>
</ContentPage>
Kontrolki można dodać do elementu zgodnie z HorizontalWrapLayout
potrzebami. W tym przykładzie po wyświetleniu strony zawierającej HorizontalWrapLayout
kontrolki Image są wyświetlane:
Liczba kolumn w każdym wierszu zależy od rozmiaru obrazu, szerokości strony i liczby pikseli na jednostkę niezależną od urządzenia:
Uwaga
Przewijanie jest obsługiwane przez zawijanie w HorizontalWrapLayout
obiekcie ScrollView.
Modyfikowanie zachowania istniejącego układu
W niektórych scenariuszach można zmienić zachowanie istniejącego typu układu bez konieczności tworzenia niestandardowego typu układu. W tych scenariuszach można utworzyć typ, który implementuje ILayoutManagerFactory i używać go do zastępowania domyślnego menedżera układów programu .NET MAUI dla istniejącego układu z własną ILayoutManager implementacją. Dzięki temu można zdefiniować nowy menedżer układów dla istniejącego układu, taki jak udostępnianie niestandardowego menedżera układów dla programu Grid. Może to być przydatne w scenariuszach, w których chcesz dodać nowe zachowanie do układu, ale nie chcesz aktualizować typu istniejącego powszechnie używanego układu w aplikacji.
Proces modyfikowania zachowania istniejącego układu z fabryką menedżera układów polega na:
- Utwórz menedżera układów, który pochodzi z jednego z typów menedżerów układów programu .NET MAUI. Aby uzyskać więcej informacji, zobacz Tworzenie niestandardowego menedżera układów.
- Utwórz typ implementujący ILayoutManagerFactoryelement . Aby uzyskać więcej informacji, zobacz Tworzenie fabryki menedżera układów.
- Zarejestruj fabrykę menedżera układów u dostawcy usług aplikacji. Aby uzyskać więcej informacji, zobacz Rejestrowanie fabryki menedżera układów.
Tworzenie niestandardowego menedżera układów
Menedżer układów służy do wykonywania układu międzyplatformowego i pomiaru układu. Aby zmienić zachowanie istniejącego układu, należy utworzyć niestandardowego menedżera układów, który pochodzi z menedżera układów dla układu:
using Microsoft.Maui.Layouts;
public class CustomGridLayoutManager : GridLayoutManager
{
public CustomGridLayoutManager(IGridLayout layout) : base(layout)
{
}
public override Size Measure(double widthConstraint, double heightConstraint)
{
EnsureRows();
return base.Measure(widthConstraint, heightConstraint);
}
void EnsureRows()
{
if (Grid is not Grid grid)
{
return;
}
// Find the maximum row value from the child views
int maxRow = 0;
foreach (var child in grid)
{
maxRow = Math.Max(grid.GetRow(child), maxRow);
}
// Add more rows if we need them
for (int n = grid.RowDefinitions.Count; n <= maxRow; n++)
{
grid.RowDefinitions.Add(new RowDefinition(GridLength.Star));
}
}
}
W tym przykładzie CustomGridLayoutManager
pochodzi z klasy .NET MAUI GridLayoutManager i zastępuje jej Measure metodę. Ten niestandardowy menedżer układów zapewnia, że w czasie wykonywania RowDefinitions dla elementu Grid zawiera wystarczającą liczbę wierszy do uwzględnienia dla każdej Grid.Row
dołączonej właściwości ustawionej w widoku podrzędnym. Bez tej modyfikacji RowDefinitions element dla Grid elementu musi być określony w czasie projektowania.
Ważne
Podczas modyfikowania zachowania istniejącego menedżera układów nie zapomnij, aby upewnić się, że metoda jest wywoływana base.Measure
z Measure implementacji.
Tworzenie fabryki menedżera układów
Niestandardowy menedżer układów powinien zostać utworzony w fabryce menedżera układów. Jest to osiągane przez utworzenie typu, który implementuje ILayoutManagerFactory interfejs:
using Microsoft.Maui.Layouts;
public class CustomLayoutManagerFactory : ILayoutManagerFactory
{
public ILayoutManager CreateLayoutManager(Layout layout)
{
if (layout is Grid)
{
return new CustomGridLayoutManager(layout as IGridLayout);
}
return null;
}
}
W tym przykładzie wystąpienie jest zwracane, CustomGridLayoutManager
jeśli układ to Grid.
Rejestrowanie fabryki menedżera układów
Fabryka menedżera układów powinna zostać zarejestrowana u dostawcy usług aplikacji w MauiProgram
klasie:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
// Setup a custom layout manager so the default manager for the Grid can be replaced.
builder.Services.Add(new ServiceDescriptor(typeof(ILayoutManagerFactory), new CustomLayoutManagerFactory()));
return builder.Build();
}
}
Następnie, gdy aplikacja renderuje element Grid , będzie używać niestandardowego menedżera układów, aby upewnić się, że w czasie wykonywania RowDefinitions dla Grid elementu zawiera wystarczającą liczbę wierszy do uwzględnienia dla każdej Grid.Row
dołączonej właściwości ustawionej w widokach podrzędnych.
W poniższym przykładzie pokazano właściwość Grid , która ustawia dołączoną Grid.Row
właściwość w widokach podrzędnych, ale nie ustawia RowDefinitions właściwości:
<Grid>
<Label Text="This Grid demonstrates replacing the LayoutManager for an existing layout type." />
<Label Grid.Row="1"
Text="In this case, it's a LayoutManager for Grid which automatically adds enough rows to accommodate the rows specified in the child views' attached properties." />
<Label Grid.Row="2"
Text="Notice that the Grid doesn't explicitly specify a RowDefinitions collection." />
<Label Grid.Row="3"
Text="In MauiProgram.cs, an instance of an ILayoutManagerFactory has been added that replaces the default GridLayoutManager. The custom manager will automatically add the necessary RowDefinitions at runtime." />
<Label Grid.Row="5"
Text="We can even skip some rows, and it will add the intervening ones for us (notice the gap between the previous label and this one)." />
</Grid>
Fabryka menedżerów układów używa niestandardowego menedżera układów, aby upewnić się, że w tym przykładzie jest wyświetlany poprawnie, mimo RowDefinitions że Grid właściwość nie jest ustawiona: