Sdílet prostřednictvím


Vlastní rozložení

Browse sample. Procházení ukázky

.NET Multi-platform App UI (.NET MAUI) definuje více tříd rozložení, které každá z nich uspořádá podřízené položky jiným způsobem. Rozložení si můžete představit jako seznam zobrazení s pravidly a vlastnostmi, které definují, jak tato zobrazení uspořádat v rámci rozložení. Příklady rozložení zahrnují Grid, AbsoluteLayouta VerticalStackLayout.

Třídy rozložení .NET MAUI jsou odvozeny od abstraktní Layout třídy. Tato třída deleguje rozložení a měření napříč platformami na třídu správce rozložení. Třída Layout také obsahuje přepisovatelnou CreateLayoutManager() metodu, která odvozená rozložení mohou použít k určení správce rozložení.

Každá třída správce rozložení implementuje ILayoutManager rozhraní, které určuje, že Measure a ArrangeChildren implementace musí být poskytnuty:

  • Implementace Measure volá IView.Measure každé zobrazení v rozložení a vrátí celkovou velikost rozložení vzhledem k omezením.
  • Implementace ArrangeChildren určuje, kde má být každé zobrazení umístěno v mezích rozložení, a volá Arrange každé zobrazení s příslušnými hranicemi. Návratová hodnota je skutečná velikost rozložení.

Rozložení .NET MAUI mají předdefinované správce rozložení pro zpracování jejich rozložení. Někdy je ale potřeba uspořádat obsah stránky pomocí rozložení, které .NET MAUI neposkytuje. Toho lze dosáhnout tak, že vytvoříte vlastní rozložení, které vyžaduje, abyste porozuměli tomu, jak funguje proces rozložení pro různé platformy .NET MAUI.

Proces rozložení

Proces rozložení pro různé platformy .NET MAUI vychází z nativního procesu rozložení na jednotlivých platformách. Obecně platí, že proces rozložení je inicializován nativním systémem rozložení. Proces pro různé platformy se spustí, když ho ovládací prvek rozložení nebo obsahu iniciuje v důsledku měření nebo uspořádání nativním systémem rozložení.

Poznámka:

Každá platforma zpracovává rozložení trochu jinak. Proces rozložení multiplatformních rozložení platformy .NET MAUI je však co nejvíce nezávislý na platformě.

Následující diagram znázorňuje proces, kdy nativní systém rozložení zahájí měření rozložení:

The process for layout measurement in .NET MAUI

Všechna rozložení .NET MAUI mají na každé platformě jedno záložní zobrazení:

  • Na Androidu je LayoutViewGrouptoto záložní zobrazení .
  • Na iOS a Mac Catalyst, toto zadní zobrazení je LayoutView.
  • Ve Windows je toto záložní zobrazení LayoutPanel.

Když nativní systém rozložení pro platformu požaduje měření jednoho z těchto záložních zobrazení, backing zobrazení volá metodu Layout.CrossPlatformMeasure . Toto je bod, ve kterém se ovládací prvek předává z nativního systému rozložení do systému rozložení .NET MAUI. Layout.CrossPlatformMeasure volá metodu správců Measure rozložení. Tato metoda zodpovídá za měření podřízených zobrazení voláním IView.Measure jednotlivých zobrazení v rozložení. Zobrazení měří svůj nativní ovládací prvek a aktualizuje jeho DesiredSize vlastnost na základě tohoto měření. Tato hodnota se vrátí do zobrazení backing jako výsledek CrossPlatformMeasure metody. Backing view provádí jakékoli interní zpracování, které je potřeba provést, a vrátí jeho naměřenou velikost na platformu.

Následující diagram znázorňuje proces, kdy nativní systém rozložení zahájí uspořádání rozložení:

The process for layout arrangement in .NET MAUI

Když nativní systém rozložení pro platformu požaduje uspořádání nebo rozložení jednoho z těchto záložních zobrazení, backing zobrazení volá metodu Layout.CrossPlatformArrange . Toto je bod, ve kterém se ovládací prvek předává z nativního systému rozložení do systému rozložení .NET MAUI. Layout.CrossPlatformArrange volá metodu správců ArrangeChildren rozložení. Tato metoda je zodpovědná za určení, kde má být každé zobrazení umístěné v mezích rozložení, a volá Arrange každé zobrazení, aby nastavil jeho umístění. Velikost rozložení se vrátí do backingového zobrazení jako výsledek CrossPlatformArrange metody. Backing view provádí jakékoli interní zpracování, které je potřeba provést, a vrátí skutečnou velikost platformy.

Poznámka:

ILayoutManager.Measure může být volána vícekrát před ArrangeChildren zavolání, protože platforma může před uspořádáním zobrazení potřebovat provést nějaké spekulativní měření.

Přístupy k vlastnímu rozložení

Existují dva hlavní přístupy k vytvoření vlastního rozložení:

  1. Vytvořte vlastní typ rozložení, což je obvykle podtřída existujícího typu rozložení nebo Layouttypu a přepsání CreateLayoutManager() ve vlastním typu rozložení. Pak zadejte implementaci, která obsahuje vlastní logiku ILayoutManager rozložení. Další informace naleznete v tématu Vytvoření vlastního typu rozložení.
  2. Upravte chování existujícího typu rozložení vytvořením typu, který implementuje ILayoutManagerFactory. Potom pomocí této továrny pro správu rozložení nahraďte výchozího správce rozložení .NET MAUI pro existující rozložení vlastní ILayoutManager implementací, která obsahuje vaši vlastní logiku rozložení. Další informace naleznete v tématu Úprava chování existujícího rozložení.

Vytvoření vlastního typu rozložení

Proces vytvoření vlastního typu rozložení je následující:

  1. Vytvořte třídu, která podtřídí existující typ rozložení nebo Layout třídu a přepíše CreateLayoutManager() vlastní typ rozložení. Další informace naleznete v tématu Podtřídy rozložení.

  2. Vytvořte třídu správce rozložení, která je odvozena od existujícího správce rozložení nebo která implementuje ILayoutManager rozhraní přímo. Ve třídě správce rozložení byste měli:

    1. Přepište nebo implementujte metodu Measure pro výpočet celkové velikosti rozložení vzhledem k omezením.
    2. Přepište nebo implementujte metodu ArrangeChildren pro velikost a umístění všech podřízených položek v rámci rozložení.

    Další informace najdete v tématu Vytvoření správce rozložení.

  3. Použití vlastního typu rozložení jeho přidáním do a Pagepřidáním podřízených položek do rozložení. Další informace naleznete v tématu Využití typu rozložení.

K předvedení tohoto procesu se používá orientace HorizontalWrapLayout . HorizontalWrapLayoutHorizontalStackLayout podobá se tomu, že uspořádá své podřízené položky vodorovně přes stránku. Při výskytu pravého okraje kontejneru ale zabalí zobrazení podřízených položek do nového řádku.

Poznámka:

Ukázka definuje další vlastní rozložení, která lze použít k pochopení, jak vytvořit vlastní rozložení.

Podtřídy rozložení

Chcete-li vytvořit vlastní typ rozložení, musíte nejprve podtřídu existující typ rozložení nebo Layout třídu. Potom přepište CreateLayoutManager() typ rozložení a vraťte novou instanci správce rozložení pro váš typ rozložení:

using Microsoft.Maui.Layouts;

public class HorizontalWrapLayout : HorizontalStackLayout
{
    protected override ILayoutManager CreateLayoutManager()
    {
        return new HorizontalWrapLayoutManager(this);
    }
}

HorizontalWrapLayout odvozuje od HorizontalStackLayout použití jeho funkce rozložení. Rozložení .NET MAUI delegují rozložení a měření napříč platformami na třídu správce rozložení. Přepsání CreateLayoutManager() proto vrátí novou instanci HorizontalWrapLayoutManager třídy, což je správce rozložení, který je popsán v další části.

Vytvoření správce rozložení

Třída správce rozložení slouží k provádění rozložení a měření pro váš vlastní typ rozložení. Měl by být odvozen od existujícího správce rozložení, nebo by měl přímo implementovat ILayoutManager rozhraní. HorizontalWrapLayoutManager je odvozen od toho HorizontalStackLayoutManager , aby mohl používat své základní funkce a přístupové členy v hierarchii dědičnosti:

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 ukládá instanci HorizontalWrapLayout typu do pole, aby k němu bylo možné přistupovat v celém správci rozložení. Správce rozložení také přepíše Measure a ArrangeChildren metody z HorizontalStackLayoutManager třídy. Tyto metody definují logiku pro implementaci vlastního rozložení.

Měření velikosti rozložení

Účelem ILayoutManager.Measure implementace je vypočítat celkovou velikost rozložení. To by mělo provést voláním IView.Measure jednotlivých podřízených v rozložení. Tato data by pak měla použít k výpočtu a vrácení celkové velikosti rozložení vzhledem k omezením.

Následující příklad ukazuje implementaci Measure třídy HorizontalWrapLayoutManager :

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 vytvoří výčet všech viditelných podřízených položek v rozložení a vyvolá metodu u každého podřízeného objektu IView.Measure . Potom vrátí celkovou velikost rozložení s ohledem na omezení a hodnoty Padding vlastností Spacing . Volá ResolveConstraints se metoda, která zajistí, aby celková velikost rozložení odpovídala jeho omezením.

Důležité

Při vytváření výčtu podřízených položek v implementaci ILayoutManager.Measure přeskočte všechny podřízené položky, jejichž Visibility vlastnost je nastavena na Collapsed. Tím zajistíte, že vlastní rozložení neopustí prostor pro neviditelné podřízené položky.

Uspořádání podřízených položek v rozložení

Účelem ArrangeChildren implementace je velikost a umístění všech podřízených položek v rámci rozložení. Aby bylo možné určit, kde má být každý podřízený prvek umístěn v mezích rozložení, měl by volat Arrange každé podřízené s příslušnými hranicemi. Pak by se měla vrátit hodnota, která představuje skutečnou velikost rozložení.

Upozorňující

Selhání vyvolání ArrangeChildren metody u každého podřízeného objektu v rozložení způsobí, že dítě nikdy neobdrží správnou velikost nebo pozici, a proto se podřízená položka na stránce nezobrazí.

Následující příklad ukazuje implementaci ArrangeChildren třídy HorizontalWrapLayoutManager :

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 vytvoří výčet všech viditelných podřízených položek v rozložení, aby je velikost a umístění v rámci rozložení. To dělá vyvoláním Arrange každého dítěte s příslušnými hranicemi, které berou v úvahu Padding a Spacing podkladové rozložení. Pak vrátí skutečnou velikost rozložení. Volá AdjustForFill se metoda, aby se zajistilo, že velikost bere v úvahu, zda má rozložení jeho HorizontalLayoutAlignment a VerticalLayoutAlignment vlastnosti nastavené na LayoutOptions.Fill.

Důležité

Při vytváření výčtu podřízených položek v implementaci ArrangeChildren přeskočte všechny podřízené položky, jejichž Visibility vlastnost je nastavena na Collapsed. Tím zajistíte, že vlastní rozložení neopustí prostor pro neviditelné podřízené položky.

Využití typu rozložení

Třídu HorizontalWrapLayout lze použít tak, že ji umístíte do odvozeného Page typu:

<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>

Ovládací prvky lze přidat podle HorizontalWrapLayout potřeby. Když se v tomto příkladu zobrazí stránka obsahující tuto HorizontalWrapLayout stránku, Image zobrazí se ovládací prvky:

Screenshot of the horizontal wrap layout on a Mac with two columns.

Počet sloupců v každém řádku závisí na velikosti obrázku, šířce stránky a počtu pixelů na jednotku nezávislou na zařízení:

Screenshot of the horizontal wrap layout on a Mac with five columns.

Poznámka:

Posouvání je podporováno zabalením znaku HorizontalWrapLayoutScrollView.

Úprava chování existujícího rozložení

V některých scénářích můžete chtít změnit chování existujícího typu rozložení, aniž byste museli vytvářet vlastní typ rozložení. V těchto scénářích můžete vytvořit typ, který implementuje ILayoutManagerFactory a použije ho k nahrazení výchozího správce rozložení .NET MAUI pro existující rozložení vlastní ILayoutManager implementací. To umožňuje definovat nového správce rozložení pro existující rozložení, například poskytnutí vlastního správce rozložení pro Grid. To může být užitečné pro scénáře, kdy chcete do rozložení přidat nové chování, ale nechcete aktualizovat typ existujícího široce používaného rozložení v aplikaci.

Proces úprav chování existujícího rozložení s objektem pro správu rozložení je následující:

  1. Vytvořte správce rozložení, který je odvozený z jednoho z typů správce rozložení .NET MAUI. Další informace najdete v tématu Vytvoření vlastního správce rozložení.
  2. Vytvořte typ, který implementuje ILayoutManagerFactory. Další informace naleznete v tématu Vytvoření objektu pro správu rozložení.
  3. Zaregistrujte objekt pro správu rozložení u poskytovatele služeb aplikace. Další informace naleznete v tématu Registrace objektu pro správu rozložení.

Vytvoření vlastního správce rozložení

Správce rozložení slouží k provádění rozložení a měření pro rozložení napříč platformami. Pokud chcete změnit chování existujícího rozložení, měli byste vytvořit vlastního správce rozložení, který je odvozen od správce rozložení pro toto rozložení:

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));
        }
    }
}

V tomto příkladu CustomGridLayoutManager se odvozuje z třídy .NET MAUI GridLayoutManager a přepíše její Measure metodu. Tento vlastní správce rozložení zajišťuje, že za běhu RowDefinitionsGrid obsahuje dostatek řádků pro účet pro každou Grid.Row připojenou vlastnost nastavenou v podřízeném zobrazení. Bez této změny RowDefinitions je potřeba zadat v době návrhu Grid .

Důležité

Při úpravě chování existujícího správce rozložení nezapomeňte zajistit, abyste metodu base.Measure volali z Measure implementace.

Vytvoření objektu pro správu rozložení

Vlastní správce rozložení by měl být vytvořen v objektu pro správu rozložení. Toho dosáhnete vytvořením typu, který implementuje ILayoutManagerFactory rozhraní:

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;
    }
}

V tomto příkladu CustomGridLayoutManager se vrátí instance, pokud je rozložení .Grid

Registrace objektu pro správu rozložení

Objekt pro správu rozložení by měl být zaregistrovaný u poskytovatele služeb vaší aplikace ve vaší MauiProgram třídě:

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();
    }
}

Když pak aplikace vykreslí vlastního správce rozložení, aby se zajistilo, že za běhu RowDefinitions bude Grid obsahovat dostatek řádků pro účet pro každou Grid.Row připojenou Grid vlastnost nastavenou v podřízených zobrazeních.

Následující příklad ukazuje Grid , která nastaví připojenou Grid.Row vlastnost v podřízených zobrazeních, ale nenastaví RowDefinitions vlastnost:

<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>

Objekt pro správu rozložení používá vlastního správce rozložení k zajištění správného zobrazení v tomto příkladu Grid bez ohledu na RowDefinitions vlastnost, která se nenastavuje:

Screenshot of a Grid customized by using a layout manager factory.