사용자 지정 레이아웃
.NET 다중 플랫폼 앱 UI(.NET MAUI)는 각각 다른 방식으로 자식을 정렬하는 여러 레이아웃 클래스를 정의합니다. 레이아웃은 레이아웃 내에서 해당 뷰를 정렬하는 방법을 정의하는 규칙 및 속성이 있는 보기 목록으로 간주할 수 있습니다. 레이아웃의 예로는 Grid, AbsoluteLayout및 VerticalStackLayout.
.NET MAUI 레이아웃 클래스는 추상 Layout 클래스에서 파생됩니다. 이 클래스는 플랫폼 간 레이아웃 및 측정값을 레이아웃 관리자 클래스에 위임합니다. 클래스에는 Layout 파생 레이아웃에서 레이아웃 관리자를 지정하는 데 사용할 수 있는 재정의 가능한 CreateLayoutManager() 메서드도 포함되어 있습니다.
각 레이아웃 관리자 클래스는 인터페이스를 ILayoutManager 구현하며, 이 인터페이스는 해당 Measure 인터페이스와 ArrangeChildren 구현을 제공해야 한다고 지정합니다.
- 구현은 Measure 레이아웃의 각 뷰를 호출 IView.Measure 하고 제약 조건이 지정된 레이아웃의 총 크기를 반환합니다.
- 구현은 ArrangeChildren 각 뷰를 레이아웃 범위 내에 배치해야 하는 위치를 결정하고 적절한 범위를 가진 각 뷰를 호출 Arrange 합니다. 반환 값은 레이아웃의 실제 크기입니다.
.NET MAUI의 레이아웃에는 레이아웃을 처리하기 위해 미리 정의된 레이아웃 관리자가 있습니다. 그러나 .NET MAUI에서 제공하지 않는 레이아웃을 사용하여 페이지 콘텐츠를 구성해야 하는 경우도 있습니다. 이 작업은 고유한 사용자 지정 레이아웃을 생성하여 수행할 수 있습니다. 이를 위해서는 .NET MAUI의 플랫폼 간 레이아웃 프로세스가 작동하는 방식을 이해해야 합니다.
레이아웃 프로세스
.NET MAUI의 플랫폼 간 레이아웃 프로세스는 각 플랫폼의 네이티브 레이아웃 프로세스를 기반으로 합니다. 일반적으로 레이아웃 프로세스는 네이티브 레이아웃 시스템에서 시작됩니다. 플랫폼 간 프로세스는 레이아웃 또는 콘텐츠 컨트롤이 네이티브 레이아웃 시스템에 의해 측정되거나 정렬된 결과로 시작될 때 실행됩니다.
참고 항목
각 플랫폼은 레이아웃을 약간 다르게 처리합니다. 그러나 .NET MAUI의 플랫폼 간 레이아웃 프로세스는 가능한 한 플랫폼에 구애받지 않는 것을 목표로 합니다.
다음 다이어그램은 네이티브 레이아웃 시스템이 레이아웃 측정을 시작하는 프로세스를 보여줍니다.
모든 .NET MAUI 레이아웃에는 각 플랫폼에서 단일 지원 보기가 있습니다.
- Android에서 이 지원 보기는
LayoutViewGroup
. - iOS 및 Mac Catalyst에서 이 지원 보기는
LayoutView
. - Windows에서 이 지원 보기는
LayoutPanel
.
플랫폼의 네이티브 레이아웃 시스템이 이러한 지원 보기 중 하나의 측정을 요청하면 지원 뷰에서 메서드를 호출합니다 Layout.CrossPlatformMeasure . 컨트롤이 네이티브 레이아웃 시스템에서 .NET MAUI의 레이아웃 시스템으로 전달되는 지점입니다. Layout.CrossPlatformMeasure 는 레이아웃 관리자의 메서드를 Measure 호출합니다. 이 메서드는 레이아웃의 각 뷰를 호출 IView.Measure 하여 자식 뷰를 측정합니다. 뷰는 네이티브 컨트롤을 측정하고 해당 측정값에 따라 해당 DesiredSize 속성을 업데이트합니다. 이 값은 메서드의 결과로 지원 뷰에 반환됩니다 CrossPlatformMeasure
. 지원 뷰는 수행해야 하는 내부 처리를 수행하고 측정된 크기를 플랫폼에 반환합니다.
다음 다이어그램은 네이티브 레이아웃 시스템이 레이아웃 배열을 시작하는 프로세스를 보여 줍니다.
플랫폼의 네이티브 레이아웃 시스템에서 이러한 지원 보기 중 하나의 배열 또는 레이아웃을 요청하면 지원 뷰에서 메서드를 Layout.CrossPlatformArrange 호출합니다. 컨트롤이 네이티브 레이아웃 시스템에서 .NET MAUI의 레이아웃 시스템으로 전달되는 지점입니다. Layout.CrossPlatformArrange 는 레이아웃 관리자의 메서드를 ArrangeChildren 호출합니다. 이 메서드는 각 뷰를 레이아웃 범위 내에 배치해야 하는 위치를 결정하고 각 뷰에서 해당 위치를 설정하도록 호출 Arrange 합니다. 레이아웃의 크기는 메서드의 CrossPlatformArrange
결과로 지원 뷰로 반환됩니다. 지원 뷰는 수행해야 하는 내부 처리를 수행하고 실제 크기를 플랫폼에 반환합니다.
참고 항목
ILayoutManager.Measure 는 호출되기 전에 ArrangeChildren 여러 번 호출될 수 있습니다. 플랫폼이 보기를 정렬하기 전에 일부 추측 측정을 수행해야 할 수 있기 때문입니다.
사용자 지정 레이아웃 접근 방식
사용자 지정 레이아웃을 만드는 두 가지 기본 방법이 있습니다.
- 일반적으로 기존 레이아웃 형식의 하위 클래스인 사용자 지정 레이아웃 형식을 만들고 사용자 지정 레이아웃 형식에서 재정 CreateLayoutManager() 의Layout합니다. 그런 다음 사용자 지정 레이아웃 논리를 포함하는 구현을 제공합니다 ILayoutManager . 자세한 내용은 사용자 지정 레이아웃 유형 만들기를 참조 하세요.
- 구현하는 형식을 만들어 기존 레이아웃 형식의 동작을 수정합니다 ILayoutManagerFactory. 그런 다음, 이 레이아웃 관리자 팩터리를 사용하여 기존 레이아웃에 대한 .NET MAUI의 기본 레이아웃 관리자를 사용자 지정 레이아웃 논리가 포함된 고유한 ILayoutManager 구현으로 바꿉니다. 자세한 내용은 기존 레이아웃의 동작 수정을 참조 하세요.
사용자 지정 레이아웃 유형 만들기
사용자 지정 레이아웃 유형을 만드는 프로세스는 다음과 같습니다.
기존 레이아웃 형식 또는 클래스를 서브클래싱하고 사용자 지정 레이아웃 형식에서 재정 CreateLayoutManager() 의 Layout 하는 클래스를 만듭니다. 자세한 내용은 레이아웃 하위 클래스를 참조 하세요.
기존 레이아웃 관리자에서 파생되거나 인터페이스를 직접 구현하는 레이아웃 관리자 클래스를 ILayoutManager 만듭니다. 레이아웃 관리자 클래스에서 다음을 수행해야 합니다.
- 해당 제약 조건이 지정된 경우 레이아웃의 총 크기를 계산하는 메서드를 재정의하거나 구현 Measure 합니다.
- 레이아웃 내에서 모든 자식의 ArrangeChildren 크기를 조정하고 배치하는 메서드를 재정의하거나 구현합니다.
자세한 내용은 레이아웃 관리자 만들기를 참조 하세요.
사용자 지정 레이아웃 유형을 추가하고 레이아웃에 Page자식을 추가하여 사용합니다. 자세한 내용은 레이아웃 유형 사용을 참조 하세요.
방향 구분 HorizontalWrapLayout
은 이 프로세스를 보여 주는 데 사용됩니다. HorizontalWrapLayout
는 자식을 페이지 전체에 가로로 정렬한다는 점과 비슷합니다 HorizontalStackLayout . 그러나 컨테이너의 오른쪽 가장자리가 발견되면 자식 표시를 새 행으로 래핑합니다.
참고 항목
이 샘플에서는 사용자 지정 레이아웃을 생성하는 방법을 이해하는 데 사용할 수 있는 추가 사용자 지정 레이아웃을 정의합니다.
레이아웃 서브클래스
사용자 지정 레이아웃 형식을 만들려면 먼저 기존 레이아웃 형식 또는 클래스를 Layout 서브클래싱해야 합니다. 그런 다음 레이아웃 유형을 재정 CreateLayoutManager() 의하고 레이아웃 유형에 대한 레이아웃 관리자의 새 인스턴스를 반환합니다.
using Microsoft.Maui.Layouts;
public class HorizontalWrapLayout : HorizontalStackLayout
{
protected override ILayoutManager CreateLayoutManager()
{
return new HorizontalWrapLayoutManager(this);
}
}
HorizontalWrapLayout
는 HorizontalStackLayout 레이아웃 기능을 사용하기 위해 파생됩니다. .NET MAUI 레이아웃은 플랫폼 간 레이아웃 및 측정값을 레이아웃 관리자 클래스에 위임합니다. 따라서 재정의 HorizontalWrapLayoutManager
는 CreateLayoutManager() 다음 섹션에서 설명하는 레이아웃 관리자인 클래스의 새 인스턴스를 반환합니다.
레이아웃 관리자 만들기
레이아웃 관리자 클래스는 사용자 지정 레이아웃 유형에 대해 플랫폼 간 레이아웃 및 측정을 수행하는 데 사용됩니다. 기존 레이아웃 관리자에서 파생되거나 인터페이스를 ILayoutManager 직접 구현해야 합니다. HorizontalWrapLayoutManager
는 HorizontalStackLayoutManager 기본 기능을 사용하고 상속 계층 구조의 멤버에 액세스할 수 있도록 파생됩니다.
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
생성자는 레이아웃 관리자 전체에서 액세스할 수 있도록 형식의 HorizontalWrapLayout
인스턴스를 필드에 저장합니다. 또한 레이아웃 관리자는 클래스의 메서드와 ArrangeChildren 메서드를 Measure 재정의합니다HorizontalStackLayoutManager. 이러한 메서드는 사용자 지정 레이아웃을 구현하는 논리를 정의하는 위치입니다.
레이아웃 크기 측정
구현의 ILayoutManager.Measure 목적은 레이아웃의 총 크기를 계산하는 것입니다. 레이아웃에서 각 자식에 대해 호출 IView.Measure 하여 이 작업을 수행해야 합니다. 그런 다음 이 데이터를 사용하여 제약 조건이 지정된 레이아웃의 총 크기를 계산하고 반환해야 합니다.
다음 예제에서는 클래스에 Measure 대한 구현을 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);
}
이 메서드는 Measure
레이아웃에서 표시되는 모든 자식을 열거하여 각 자식에 대해 메서드를 IView.Measure 호출합니다. 그런 다음 제약 조건 및 속성 값을 고려하여 레이아웃의 PaddingSpacing 총 크기를 반환합니다. ResolveConstraints 레이아웃의 총 크기가 해당 제약 조건 내에 맞는지 확인하기 위해 메서드가 호출됩니다.
Important
구현에서 ILayoutManager.Measure 자식을 열거하는 경우 속성이 Visibility .로 설정된 Collapsed모든 자식을 건너뜁니다. 이렇게 하면 사용자 지정 레이아웃이 보이지 않는 자식에 대한 공간을 남기지 않습니다.
레이아웃에서 자식 정렬
구현의 ArrangeChildren 목적은 레이아웃 내에서 모든 자식의 크기를 조정하고 배치하는 것입니다. 각 자식이 레이아웃 범위 내에 배치되어야 하는 위치를 확인하려면 해당 범위가 있는 각 자식에 대해 호출 Arrange 해야 합니다. 그런 다음 레이아웃의 실제 크기를 나타내는 값을 반환해야 합니다.
Warning
레이아웃의 ArrangeChildren 각 자식에서 메서드를 호출하지 않으면 자식이 올바른 크기나 위치를 받지 못하므로 자식이 페이지에 표시되지 않습니다.
다음 예제에서는 클래스에 ArrangeChildren 대한 구현을 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);
}
이 메서드는 ArrangeChildren
레이아웃에서 표시되는 모든 자식을 열거하여 레이아웃 내에서 크기를 조정하고 배치합니다. 기본 레이아웃과 Spacing 해당 레이아웃을 Arrange 고려하여 적절한 범위를 가진 각 자식에 Padding 대해 호출하여 이 작업을 수행합니다. 그런 다음 레이아웃의 실제 크기를 반환합니다. AdjustForFill 레이아웃에 해당 및 속성이 설정되어 LayoutOptions.Fill있는지 여부를 HorizontalLayoutAlignmentVerticalLayoutAlignment 고려하여 크기가 고려되도록 메서드가 호출됩니다.
Important
구현에서 ArrangeChildren 자식을 열거하는 경우 속성이 Visibility .로 설정된 Collapsed모든 자식을 건너뜁니다. 이렇게 하면 사용자 지정 레이아웃이 보이지 않는 자식에 대한 공간을 남기지 않습니다.
레이아웃 유형 사용
클래스는 HorizontalWrapLayout
파생 형식에 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>
필요에 따라 컨트롤을 HorizontalWrapLayout
추가할 수 있습니다. 이 예제에서는 해당 페이지가 표시 HorizontalWrapLayout
되면 컨트롤이 Image 표시됩니다.
각 행의 열 수는 이미지 크기, 페이지 너비 및 디바이스 독립적 단위당 픽셀 수에 따라 달라집니다.
참고 항목
스크롤은 .에 ScrollView래핑하여 HorizontalWrapLayout
지원됩니다.
기존 레이아웃의 동작 수정
일부 시나리오에서는 사용자 지정 레이아웃 형식을 만들지 않고도 기존 레이아웃 형식의 동작을 변경할 수 있습니다. 이러한 시나리오에서는 구현 ILayoutManagerFactory 하는 형식을 만들고 이를 사용하여 기존 레이아웃에 대한 .NET MAUI의 기본 레이아웃 관리자를 사용자 고유 ILayoutManager 의 구현으로 바꿀 수 있습니다. 이렇게 하면 사용자 지정 레이아웃 관리자를 제공하는 등 기존 레이아웃에 대한 새 레이아웃 관리자를 정의할 수 Grid있습니다. 이는 레이아웃에 새 동작을 추가하려고 하지만 앱에서 널리 사용되는 기존 레이아웃의 형식을 업데이트하지 않으려는 시나리오에 유용할 수 있습니다.
레이아웃 관리자 팩터리를 사용하여 기존 레이아웃의 동작을 수정하는 프로세스는 다음과 같습니다.
- .NET MAUI의 레이아웃 관리자 유형 중 하나에서 파생되는 레이아웃 관리자를 만듭니다. 자세한 내용은 사용자 지정 레이아웃 관리자 만들기를 참조 하세요.
- 를 구현하는 형식을 만듭니다 ILayoutManagerFactory. 자세한 내용은 레이아웃 관리자 팩터리 만들기를 참조하세요.
- 앱의 서비스 공급자에 레이아웃 관리자 팩터리를 등록합니다. 자세한 내용은 레이아웃 관리자 팩터리 등록을 참조 하세요.
사용자 지정 레이아웃 관리자 만들기
레이아웃 관리자는 레이아웃에 대해 플랫폼 간 레이아웃 및 측정을 수행하는 데 사용됩니다. 기존 레이아웃의 동작을 변경하려면 레이아웃에 대한 레이아웃 관리자에서 파생되는 사용자 지정 레이아웃 관리자를 만들어야 합니다.
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));
}
}
}
이 예제 CustomGridLayoutManager
에서는 .NET MAUI의 클래스에서 파생되고 해당 Measure 메서드를 재정의 GridLayoutManager 합니다. 이 사용자 지정 레이아웃 관리자는 런타임에 자식 보기에 설정된 각 Grid.Row
연결된 속성을 설명하기에 충분한 행을 런타임 RowDefinitionsGrid 에 포함하도록 합니다. 이 수정 RowDefinitions 이 없으면 디자인 타임에 Grid for를 지정해야 합니다.
Important
기존 레이아웃 관리자의 동작을 수정할 때 구현에서 Measure 메서드를 호출 base.Measure
해야 합니다.
레이아웃 관리자 팩터리 만들기
레이아웃 관리자 팩터리에 사용자 지정 레이아웃 관리자를 만들어야 합니다. 인터페이스를 구현하는 형식을 만들어 이 작업을 ILayoutManagerFactory 수행합니다.
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;
}
}
이 예제에서는 레이아웃이 . CustomGridLayoutManager
인 경우 인스턴스가 Grid반환됩니다.
레이아웃 관리자 팩터리 등록
레이아웃 관리자 팩터리는 클래스에서 앱의 서비스 공급자에 등록해야 합니다 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();
}
}
그런 다음 앱이 렌더링 Grid 될 때 사용자 지정 레이아웃 관리자를 사용하여 런타임 RowDefinitionsGrid 에 자식 보기에 설정된 각 Grid.Row
연결된 속성을 설명하기에 충분한 행을 포함합니다.
다음 예제에서는 자식 보기에서 연결된 속성을 설정 Grid.Row
하지만 속성을 설정하지 않는 방법을 RowDefinitions 보여 Grid 있습니다.
<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>
레이아웃 관리자 팩터리는 사용자 지정 레이아웃 관리자를 사용하여 이 예제의 속성이 Grid 설정되지 않았음에도 불구하고 RowDefinitions 올바르게 표시되도록 합니다.
.NET MAUI