Bagikan melalui


Tata letak kustom

Browse sample. Telusuri sampel

.NET Multi-platform App UI (.NET MAUI) mendefinisikan beberapa kelas tata letak yang masing-masing mengatur anak-anak mereka dengan cara yang berbeda. Tata letak dapat dianggap sebagai daftar tampilan dengan aturan dan properti yang menentukan cara mengatur tampilan tersebut dalam tata letak. Contoh tata letak termasuk Grid, , AbsoluteLayoutdan VerticalStackLayout.

Kelas tata letak .NET MAUI berasal dari kelas abstrak Layout . Kelas ini mendelegasikan tata letak dan pengukuran lintas platform ke kelas manajer tata letak. Kelas ini Layout juga berisi metode yang dapat CreateLayoutManager() diganti yang dapat digunakan tata letak turunan untuk menentukan manajer tata letak.

Setiap kelas manajer tata letak mengimplementasikan ILayoutManager antarmuka, yang menentukan bahwa Measure dan ArrangeChildren implementasi harus disediakan:

  • Implementasi Measure memanggil IView.Measure pada setiap tampilan dalam tata letak, dan mengembalikan ukuran total tata letak mengingat batasan.
  • Implementasi ArrangeChildren menentukan di mana setiap tampilan harus ditempatkan dalam batas tata letak, dan panggilan Arrange pada setiap tampilan dengan batas yang sesuai. Nilai yang dikembalikan adalah ukuran tata letak yang sebenarnya.

Tata letak .NET MAUI memiliki manajer tata letak yang telah ditentukan sebelumnya untuk menangani tata letaknya. Namun, terkadang perlu untuk mengatur konten halaman menggunakan tata letak yang tidak disediakan oleh .NET MAUI. Ini dapat dicapai dengan menghasilkan tata letak kustom Anda sendiri, yang mengharuskan Anda untuk memiliki pemahaman tentang cara kerja proses tata letak lintas platform .NET MAUI.

Proses tata letak

Proses tata letak lintas platform .NET MAUI dibangun di atas proses tata letak asli pada setiap platform. Umumnya, proses tata letak dimulai oleh sistem tata letak asli. Proses lintas platform berjalan ketika tata letak atau kontrol konten memulainya sebagai akibat dari diukur atau diatur oleh sistem tata letak asli.

Catatan

Setiap platform menangani tata letak sedikit berbeda. Namun, proses tata letak lintas platform .NET MAUI bertujuan untuk sesering mungkin platform-agnostik.

Diagram berikut menunjukkan proses saat sistem tata letak asli memulai pengukuran tata letak:

The process for layout measurement in .NET MAUI

Semua tata letak MAUI .NET memiliki satu tampilan backing di setiap platform:

  • Di Android, tampilan backing ini adalah LayoutViewGroup.
  • Di iOS dan Mac Catalyst, tampilan backing ini adalah LayoutView.
  • Pada Windows, tampilan backing ini adalah LayoutPanel.

Ketika sistem tata letak asli untuk platform meminta pengukuran salah satu tampilan backing ini, tampilan backing memanggil Layout.CrossPlatformMeasure metode . Ini adalah titik di mana kontrol diteruskan dari sistem tata letak asli ke sistem tata letak .NET MAUI. Layout.CrossPlatformMeasure memanggil metode manajer Measure tata letak. Metode ini bertanggung jawab untuk mengukur tampilan anak dengan memanggil IView.Measure pada setiap tampilan dalam tata letak. Tampilan mengukur kontrol aslinya, dan memperbarui propertinya DesiredSize berdasarkan pengukuran tersebut. Nilai ini dikembalikan ke tampilan backing sebagai hasil dari CrossPlatformMeasure metode . Tampilan backing melakukan pemrosesan internal apa pun yang perlu dilakukan, dan mengembalikan ukuran yang diukur ke platform.

Diagram berikut menunjukkan proses saat sistem tata letak asli memulai pengaturan tata letak:

The process for layout arrangement in .NET MAUI

Ketika sistem tata letak asli untuk platform meminta pengaturan, atau tata letak, dari salah satu tampilan backing ini, tampilan backing memanggil Layout.CrossPlatformArrange metode . Ini adalah titik di mana kontrol diteruskan dari sistem tata letak asli ke sistem tata letak .NET MAUI. Layout.CrossPlatformArrange memanggil metode manajer ArrangeChildren tata letak. Metode ini bertanggung jawab untuk menentukan di mana setiap tampilan harus ditempatkan dalam batas tata letak, dan memanggil Arrange pada setiap tampilan untuk mengatur lokasinya. Ukuran tata letak dikembalikan ke tampilan backing sebagai hasil dari CrossPlatformArrange metode . Tampilan backing melakukan pemrosesan internal apa pun yang perlu dilakukannya, dan mengembalikan ukuran aktual ke platform.

Catatan

ILayoutManager.Measure dapat dipanggil beberapa kali sebelum ArrangeChildren dipanggil, karena platform mungkin perlu melakukan beberapa pengukuran spekulatif sebelum mengatur tampilan.

Pendekatan tata letak kustom

Ada dua pendekatan utama untuk membuat tata letak kustom:

  1. Buat jenis tata letak kustom, yang biasanya merupakan subkelas dari jenis tata letak yang ada atau Layout, dan ambil alih CreateLayoutManager() dalam jenis tata letak kustom Anda. Kemudian, berikan ILayoutManager implementasi yang berisi logika tata letak kustom Anda. Untuk informasi selengkapnya, lihat Membuat jenis tata letak kustom.
  2. Ubah perilaku jenis tata letak yang ada dengan membuat jenis yang mengimplementasikan ILayoutManagerFactory. Kemudian, gunakan pabrik manajer tata letak ini untuk mengganti manajer tata letak default .NET MAUI untuk tata letak yang ada dengan implementasi Anda sendiri ILayoutManager yang berisi logika tata letak kustom Anda. Untuk informasi selengkapnya, lihat Mengubah perilaku tata letak yang sudah ada.

Membuat jenis tata letak kustom

Proses untuk membuat jenis tata letak kustom adalah dengan:

  1. Buat kelas yang mensubkelas jenis tata letak atau Layout kelas yang ada, dan ambil alih CreateLayoutManager() dalam jenis tata letak kustom Anda. Untuk informasi selengkapnya, lihat Subkelas tata letak.

  2. Buat kelas manajer tata letak yang berasal dari pengelola tata letak yang ada, atau yang mengimplementasikan antarmuka secara ILayoutManager langsung. Di kelas manajer tata letak, Anda harus:

    1. Ambil alih, atau terapkan Measure , metode untuk menghitung ukuran total tata letak mengingat batasannya.
    2. Ambil alih, atau terapkan ArrangeChildren , metode untuk mengukur dan memposisikan semua anak dalam tata letak.

    Untuk informasi selengkapnya, lihat Membuat pengelola tata letak.

  3. Gunakan jenis tata letak kustom Anda dengan menambahkannya ke Page, dan dengan menambahkan turunan ke tata letak. Untuk informasi selengkapnya, lihat Menggunakan jenis tata letak.

Peka HorizontalWrapLayout orientasi digunakan untuk menunjukkan proses ini. HorizontalWrapLayout mirip HorizontalStackLayout dengan di dalamnya mengatur anak-anaknya secara horizontal di seluruh halaman. Namun, ini membungkus tampilan anak-anak ke baris baru ketika menemukan tepi kanan kontainernya

Catatan

Sampel menentukan tata letak kustom tambahan yang dapat digunakan untuk memahami cara menghasilkan tata letak kustom.

Subkelas tata letak

Untuk membuat jenis tata letak kustom, Anda harus terlebih dahulu mensubkelas tipe tata letak yang sudah ada, atau Layout kelas . Kemudian, ambil alih CreateLayoutManager() dalam jenis tata letak Anda dan kembalikan instans baru manajer tata letak untuk jenis tata letak Anda:

using Microsoft.Maui.Layouts;

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

HorizontalWrapLayout berasal dari HorizontalStackLayout untuk menggunakan fungsionalitas tata letaknya. Tata letak MAUI .NET mendelegasikan tata letak lintas platform dan pengukuran ke kelas manajer tata letak. Oleh karena itu, CreateLayoutManager() penimpaan mengembalikan instans HorizontalWrapLayoutManager baru kelas, yang merupakan manajer tata letak yang dibahas di bagian berikutnya.

Membuat manajer tata letak

Kelas manajer tata letak digunakan untuk melakukan tata letak dan pengukuran lintas platform untuk jenis tata letak kustom Anda. Ini harus berasal dari manajer tata letak yang ada, atau harus langsung mengimplementasikan ILayoutManager antarmuka. HorizontalWrapLayoutManager berasal dari HorizontalStackLayoutManager sehingga dapat menggunakan fungsionalitas yang mendasar dan mengakses anggota dalam hierarki warisannya:

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

HorizontalWrapLayoutManager Konstruktor menyimpan instans jenis HorizontalWrapLayout dalam bidang, sehingga dapat diakses di seluruh manajer tata letak. Manajer tata letak juga mengambil alih Measure metode dan ArrangeChildren dari HorizontalStackLayoutManager kelas . Metode ini adalah tempat Anda akan menentukan logika untuk mengimplementasikan tata letak kustom Anda.

Mengukur ukuran tata letak

Tujuan implementasinya ILayoutManager.Measure adalah untuk menghitung ukuran total tata letak. Ini harus dilakukan dengan memanggil IView.Measure setiap anak dalam tata letak. Kemudian harus menggunakan data ini untuk menghitung dan mengembalikan ukuran total tata letak mengingat batasannya.

Contoh berikut menunjukkan Measure implementasi untuk HorizontalWrapLayoutManager kelas :

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

Metode ini Measure menghitung semua anak yang terlihat dalam tata letak, memanggil IView.Measure metode pada setiap anak. Kemudian mengembalikan ukuran total tata letak, dengan mempertimbangkan batasan dan nilai Padding properti dan Spacing . Metode ResolveConstraints ini dipanggil untuk memastikan bahwa ukuran total tata letak sesuai dengan batasannya.

Penting

Saat menghitung anak-anak dalam ILayoutManager.Measure implementasi, lewati anak mana pun yang propertinya Visibility diatur ke Collapsed. Ini memastikan bahwa tata letak kustom tidak akan meninggalkan ruang untuk anak-anak yang tidak terlihat.

Mengatur turunan dalam tata letak

Tujuan implementasinya ArrangeChildren adalah untuk mengukur dan memposisikan semua anak dalam tata letak. Untuk menentukan di mana setiap anak harus ditempatkan dalam batas tata letak, ia harus memanggil Arrange setiap anak dengan batas yang sesuai. Kemudian harus mengembalikan nilai yang mewakili ukuran tata letak yang sebenarnya.

Peringatan

Kegagalan untuk memanggil ArrangeChildren metode pada setiap anak dalam tata letak akan mengakibatkan anak tidak pernah menerima ukuran atau posisi yang benar, dan karenanya anak tidak akan terlihat di halaman.

Contoh berikut menunjukkan ArrangeChildren implementasi untuk HorizontalWrapLayoutManager kelas :

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

Metode menghitung ArrangeChildren semua anak yang terlihat dalam tata letak untuk mengukur dan memosisikannya dalam tata letak. Ini dilakukan dengan memanggil Arrange setiap anak dengan batas yang sesuai, yang memperhitungkan Padding dan Spacing tata letak yang mendasar. Kemudian mengembalikan ukuran tata letak yang sebenarnya. Metode AdjustForFill ini dipanggil untuk memastikan bahwa ukuran memperhitungkan apakah tata letak memiliki HorizontalLayoutAlignment properti dan VerticalLayoutAlignment diatur ke LayoutOptions.Fill.

Penting

Saat menghitung anak-anak dalam ArrangeChildren implementasi, lewati anak mana pun yang propertinya Visibility diatur ke Collapsed. Ini memastikan bahwa tata letak kustom tidak akan meninggalkan ruang untuk anak-anak yang tidak terlihat.

Menggunakan jenis tata letak

Kelas HorizontalWrapLayout dapat dikonsumsi dengan menempatkannya dalam Page jenis turunan:

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

Kontrol dapat ditambahkan ke HorizontalWrapLayout sesuai kebutuhan. Dalam contoh ini, ketika halaman yang berisi HorizontalWrapLayout muncul, Image kontrol ditampilkan:

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

Jumlah kolom di setiap baris tergantung pada ukuran gambar, lebar halaman, dan jumlah piksel per unit independen perangkat:

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

Catatan

Pengguliran didukung dengan membungkus HorizontalWrapLayout dalam ScrollView.

Mengubah perilaku tata letak yang ada

Dalam beberapa skenario, Anda mungkin ingin mengubah perilaku jenis tata letak yang ada tanpa harus membuat jenis tata letak kustom. Untuk skenario ini, Anda dapat membuat jenis yang mengimplementasikan ILayoutManagerFactory dan menggunakannya untuk mengganti manajer tata letak default .NET MAUI untuk tata letak yang ada dengan implementasi Anda sendiri ILayoutManager . Ini memungkinkan Anda menentukan manajer tata letak baru untuk tata letak yang ada, seperti menyediakan manajer tata letak kustom untuk Grid. Ini dapat berguna untuk skenario di mana Anda ingin menambahkan perilaku baru ke tata letak tetapi tidak ingin memperbarui jenis tata letak yang sudah ada yang banyak digunakan di aplikasi Anda.

Proses untuk memodifikasi perilaku tata letak yang ada, dengan pabrik manajer tata letak, adalah:

  1. Buat manajer tata letak yang berasal dari salah satu jenis manajer tata letak .NET MAUI. Untuk informasi selengkapnya, lihat Membuat pengelola tata letak kustom.
  2. Buat jenis yang mengimplementasikan ILayoutManagerFactory. Untuk informasi selengkapnya, lihat Membuat pabrik pengelola tata letak.
  3. Daftarkan pabrik pengelola tata letak Anda dengan penyedia layanan aplikasi. Untuk informasi selengkapnya, lihat Mendaftarkan pabrik pengelola tata letak.

Membuat manajer tata letak kustom

Manajer tata letak digunakan untuk melakukan tata letak dan pengukuran lintas platform untuk tata letak. Untuk mengubah perilaku tata letak yang ada, Anda harus membuat manajer tata letak kustom yang berasal dari manajer tata letak untuk tata letak:

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

Dalam contoh ini, CustomGridLayoutManager berasal dari kelas .NET MAUI GridLayoutManager , dan mengambil alih metodenya Measure . Manajer tata letak kustom ini memastikan bahwa pada runtime RowDefinitions untuk menyertakan baris yang Grid cukup untuk memperhitungkan setiap Grid.Row properti terlampir yang diatur dalam tampilan anak. Tanpa modifikasi ini, RowDefinitions untuk Grid itu perlu ditentukan pada waktu desain.

Penting

Saat memodifikasi perilaku pengelola tata letak yang ada, jangan lupa untuk memastikan bahwa Anda memanggil base.Measure metode dari implementasi Anda Measure .

Membuat pabrik manajer tata letak

Manajer tata letak kustom harus dibuat di pabrik pengelola tata letak. Ini dicapai dengan membuat jenis yang mengimplementasikan ILayoutManagerFactory antarmuka:

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

Dalam contoh ini, CustomGridLayoutManager instans dikembalikan jika tata letaknya adalah Grid.

Mendaftarkan pabrik pengelola tata letak

Pabrik pengelola tata letak harus terdaftar di penyedia layanan aplikasi Anda di kelas Anda MauiProgram :

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

Kemudian, ketika aplikasi merender Grid , aplikasi akan menggunakan manajer tata letak kustom untuk memastikan bahwa pada runtime RowDefinitions untuk Grid menyertakan baris yang cukup untuk memperhitungkan setiap Grid.Row properti terlampir yang diatur dalam tampilan anak.

Contoh berikut menunjukkan Grid yang mengatur Grid.Row properti terlampir dalam tampilan anak, tetapi tidak mengatur RowDefinitions properti :

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

Pabrik pengelola tata letak menggunakan manajer tata letak kustom untuk memastikan bahwa Grid dalam contoh ini ditampilkan dengan benar, meskipun RowDefinitions properti tidak diatur:

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