Xamarin.Forms 사용자 지정 렌더러를 .NET MAUI 처리기로 마이그레이션

Xamarin.Forms에서 사용자 지정 렌더러를 사용하여 컨트롤의 모양과 동작을 사용자 지정하고 플랫폼 간 컨트롤을 새로 만들 수 있습니다. 각 사용자 지정 렌더러는 플랫폼 간 컨트롤에 대한 참조를 가지며 종종 속성 변경 알림을 전송하는 데 의존 INotifyPropertyChanged 합니다. .NET 다중 플랫폼 앱 UI(.NET MAUI)는 사용자 지정 렌더러를 사용하는 대신 처리기라는 새로운 개념을 도입합니다.

처리기는 사용자 지정 렌더러에 비해 많은 성능 향상을 제공합니다. Xamarin.Forms에서 클래스는 ViewRenderer 부모 요소를 만듭니다. 예를 들어 Android에서는 ViewGroup 보조 위치 지정 작업에 사용되는 A가 만들어집니다. .NET MAUI ViewHandler 에서 클래스는 시각적 계층 구조의 크기를 줄이고 앱의 성능을 향상시키는 데 도움이 되는 부모 요소를 만들지 않습니다. 또한 처리기는 프레임워크에서 플랫폼 컨트롤을 분리합니다. 플랫폼 컨트롤은 프레임워크의 요구 사항만 처리해야 합니다. 이는 더 효율적일 뿐만 아니라 필요할 때 확장하거나 재정의하는 것이 훨씬 쉽습니다. 처리기는 혜성멋진 같은 다른 프레임 워크에서 다시 사용하는 데 적합합니다. 처리기에 대한 자세한 내용은 처리기를 참조 하세요.

Xamarin.Forms에서 사용자 지정 렌더러의 OnElementChanged 메서드는 플랫폼 컨트롤을 만들고, 기본값을 초기화하고, 이벤트를 구독하고, 렌더러가 연결된 Xamarin.Forms 요소와 렌더러가 연결된 요소(OldElementNewElement)를 처리합니다. 또한 단일 OnElementPropertyChanged 메서드는 플랫폼 간 컨트롤에서 속성 변경이 발생할 때 호출할 작업을 정의합니다. .NET MAUI는 이 방법을 간소화하여 모든 속성 변경이 별도의 메서드에 의해 처리되고 플랫폼 컨트롤을 만들고, 컨트롤 설정을 수행하고, 컨트롤 클린up을 수행하는 코드가 고유한 메서드로 구분되도록 합니다.

각 플랫폼의 사용자 지정 렌더러에서 지원되는 Xamarin.Forms 사용자 지정 컨트롤을 각 플랫폼의 처리기가 뒷받침하는 .NET MAUI 사용자 지정 컨트롤로 마이그레이션하는 프로세스는 다음과 같습니다.

  1. 플랫폼 간 컨트롤에 대한 클래스를 만듭니다. 이 클래스는 컨트롤의 공용 API를 제공합니다. 자세한 내용은 플랫폼 간 컨트롤 만들기를 참조 하세요.
  2. 처리기 클래스를 partial 만듭니다. 자세한 내용은 처리기 만들기를 참조 하세요.
  3. 처리기 클래스에서 플랫폼 간 속성 변경이 발생할 때 수행할 작업을 정의하는 사전을 만듭니 PropertyMapper 다. 자세한 내용은 속성 매퍼 만들기를 참조 하세요.
  4. 플랫폼 간 컨트롤을 구현하는 네이티브 뷰를 만드는 각 플랫폼에 대한 처리기 클래스를 만듭니 partial 다. 자세한 내용은 플랫폼 컨트롤 만들기를 참조 하세요.
  5. MauiProgram 클래스의 메서드 및 AddHandler 메서드를 ConfigureMauiHandlers 사용하여 처리기를 등록합니다. 자세한 내용은 처리기 등록을 참조 하세요.

그런 다음 플랫폼 간 컨트롤을 사용할 수 있습니다. 자세한 내용은 플랫폼 간 컨트롤 사용을 참조 하세요.

또는 Xamarin.Forms 컨트롤을 사용자 지정하는 사용자 지정 렌더러를 변환하여 .NET MAUI 처리기를 수정할 수 있습니다. 자세한 내용은 처리기를 사용하여 컨트롤 사용자 지정을 참조 하세요.

플랫폼 간 컨트롤 만들기

플랫폼 간 컨트롤을 만들려면 다음에서 View파생되는 클래스를 만들어야 합니다.

namespace MyMauiControl.Controls
{
    public class CustomEntry : View
    {
        public static readonly BindableProperty TextProperty =
            BindableProperty.Create(nameof(Text), typeof(string), typeof(CustomEntry), null);

        public static readonly BindableProperty TextColorProperty =
            BindableProperty.Create(nameof(TextColor), typeof(Color), typeof(CustomEntry), null);

        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        public Color TextColor
        {
            get { return (Color)GetValue(TextColorProperty); }
            set { SetValue(TextColorProperty, value); }
        }
    }
}

컨트롤은 해당 처리기에서 액세스할 공용 API를 제공하고 소비자를 제어해야 합니다. 플랫폼 간 컨트롤은 화면에 레이아웃과 보기를 배치하는 데 사용되는 시각적 요소를 나타내는 파생 View되어야 합니다.

처리기 만들기

플랫폼 간 컨트롤을 만든 후 처리기에 대한 클래스를 partial 만들어야 합니다.

#if IOS || MACCATALYST
using PlatformView = Microsoft.Maui.Platform.MauiTextField;
#elif ANDROID
using PlatformView = AndroidX.AppCompat.Widget.AppCompatEditText;
#elif WINDOWS
using PlatformView = Microsoft.UI.Xaml.Controls.TextBox;
#elif (NETSTANDARD || !PLATFORM) || (NET6_0_OR_GREATER && !IOS && !ANDROID)
using PlatformView = System.Object;
#endif
using MyMauiControl.Controls;
using Microsoft.Maui.Handlers;

namespace MyMauiControl.Handlers
{
    public partial class CustomEntryHandler
    {
    }
}

처리기 클래스는 부분 클래스를 추가로 사용하여 각 플랫폼에서 구현이 완료되는 partial 클래스입니다.

조건 using 문은 각 플랫폼의 형식을 PlatformView 정의합니다. 마지막 조건 using 문은 같System.Object도록 정의합니다PlatformView. 이 작업은 모든 플랫폼에서 사용할 수 있도록 처리기 내에서 형식을 사용할 수 있도록 PlatformView 필요합니다. 대안은 조건부 컴파일을 사용하여 플랫폼당 한 번씩 속성을 정의 PlatformView 해야 하는 것입니다.

속성 매퍼 만들기

각 처리기는 일반적으로 플랫폼 간 컨트롤에서 속성 변경이 발생할 때 수행할 작업을 정의하는 속성 매퍼를 제공합니다. 이 형식은 PropertyMapperDictionary 플랫폼 간 컨트롤의 속성을 연결된 Actions에 매핑하는 형식입니다.

참고 항목

속성 매퍼는 Xamarin.Forms 사용자 지정 렌더러의 메서드를 대체 OnElementPropertyChanged 합니다.

PropertyMapper 는 .NET MAUI의 제네릭 ViewHandler 클래스에 정의되며 다음 두 개의 제네릭 인수를 제공해야 합니다.

  • 에서 파생되는 플랫폼 간 컨트롤에 대한 클래스입니다 View.
  • 처리기의 클래스입니다.

다음 코드 예제에서는 정의로 확장된 CustomEntryHandler 클래스를 PropertyMapper 보여줍니다.

public partial class CustomEntryHandler
{
    public static PropertyMapper<CustomEntry, CustomEntryHandler> PropertyMapper = new PropertyMapper<CustomEntry, CustomEntryHandler>(ViewHandler.ViewMapper)
    {
        [nameof(CustomEntry.Text)] = MapText,
        [nameof(CustomEntry.TextColor)] = MapTextColor
    };

    public CustomEntryHandler() : base(PropertyMapper)
    {
    }
}

PropertyMapperstringDictionary 제네릭이고 값은 제네릭Action입니다. 플랫폼 string 간 컨트롤의 속성 이름을 나타내며 Action 처리기 및 플랫폼 간 컨트롤이 인수로 필요한 메서드를 나타냅니다 static . 예를 들어 메서드의 MapText 서명은 .입니다 public static void MapText(CustomEntryHandler handler, CustomEntry view).

각 플랫폼 처리기는 네이티브 뷰 API를 조작하는 Actions의 구현을 제공해야 합니다. 이렇게 하면 플랫폼 간 컨트롤에 속성이 설정되면 기본 네이티브 뷰가 필요에 따라 업데이트됩니다. 이 방법의 장점은 서브클래싱 없이 플랫폼 간 제어 소비자가 속성 매퍼를 수정할 수 있기 때문에 플랫폼 간 제어 사용자 지정이 용이하다는 것입니다. 자세한 내용은 처리기를 사용하여 컨트롤 사용자 지정을 참조 하세요.

플랫폼 컨트롤 만들기

처리기에 대한 매퍼를 만든 후에는 모든 플랫폼에서 처리기 구현을 제공해야 합니다. 이 작업은 Platforms 폴더의 자식 폴더에 부분 클래스 처리기 구현을 추가하여 수행할 수 있습니다 . 또는 파일 이름 기반 다중 대상 지정 또는 폴더 기반 다중 대상 지정 또는 둘 다를 지원하도록 프로젝트를 구성할 수 있습니다.

파일 이름 기반 다중 대상 지정은 다음 XML을 프로젝트 파일에 노드의 <Project> 자식으로 추가하여 구성됩니다.

<!-- Android -->
<ItemGroup Condition="$(TargetFramework.StartsWith('net8.0-android')) != true">
  <Compile Remove="**\*.Android.cs" />
  <None Include="**\*.Android.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>

<!-- iOS and Mac Catalyst -->
<ItemGroup Condition="$(TargetFramework.StartsWith('net8.0-ios')) != true AND $(TargetFramework.StartsWith('net8.0-maccatalyst')) != true">
  <Compile Remove="**\*.MaciOS.cs" />
  <None Include="**\*.MaciOS.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>

<!-- Windows -->
<ItemGroup Condition="$(TargetFramework.Contains('-windows')) != true ">
  <Compile Remove="**\*.Windows.cs" />
  <None Include="**\*.Windows.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>

다중 대상 지정 구성에 대한 자세한 내용은 다중 대상 지정 구성을 참조 하세요.

각 플랫폼 처리기 클래스는 부분 클래스여야 하며 두 가지 형식 인수가 필요한 제네릭 ViewHandler 클래스에서 파생되어야 합니다.

  • 에서 파생되는 플랫폼 간 컨트롤에 대한 클래스입니다 View.
  • 플랫폼에서 플랫폼 간 컨트롤을 구현하는 네이티브 뷰의 형식입니다. 처리기의 속성 형식과 PlatformView 동일해야 합니다.

Important

클래스가 ViewHandler 제공하고 속성을 제공합니다 VirtualViewPlatformView . 이 VirtualView 속성은 해당 처리기에서 플랫폼 간 컨트롤에 액세스하는 데 사용됩니다. 이 PlatformView 속성은 플랫폼 간 컨트롤을 구현하는 각 플랫폼의 네이티브 뷰에 액세스하는 데 사용됩니다.

각 플랫폼 처리기 구현은 다음 메서드를 재정의해야 합니다.

  • CreatePlatformView- 플랫폼 간 컨트롤을 구현하는 네이티브 뷰를 만들고 반환해야 합니다.
  • ConnectHandler네이티브 뷰 초기화 및 이벤트 구독 수행과 같은 네이티브 보기 설정을 수행해야 합니다.
  • DisconnectHandler- 이벤트에서 구독 취소 및 개체 삭제와 같은 네이티브 뷰 클린up을 수행해야 합니다. 이 메서드는 의도적으로 .NET MAUI에서 호출되지 않습니다. 대신 앱의 수명 주기에서 적절한 위치에서 직접 호출해야 합니다. 자세한 내용은 네이티브 보기 클린up을 참조하세요.

참고 항목

CreatePlatformView, ConnectHandlerDisconnectHandler 재정의는 Xamarin.Forms 사용자 지정 렌더러의 메서드를 대체 OnElementChanged 합니다.

또한 각 플랫폼 처리기는 매퍼 사전에 정의된 작업을 구현해야 합니다. 또한 각 플랫폼 처리기는 필요에 따라 플랫폼에서 플랫폼 간 제어의 기능을 구현하는 코드를 제공해야 합니다. 또는 더 복잡한 컨트롤의 경우 추가 형식으로 제공할 수 있습니다.

다음 예제에서는 Android의 CustomEntryHandler 구현을 보여줍니다.

#nullable enable
using AndroidX.AppCompat.Widget;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using MyMauiControl.Controls;

namespace MyMauiControl.Handlers
{
    public partial class CustomEntryHandler : ViewHandler<CustomEntry, AppCompatEditText>
    {
        protected override AppCompatEditText CreatePlatformView() => new AppCompatEditText(Context);

        protected override void ConnectHandler(AppCompatEditText platformView)
        {
            base.ConnectHandler(platformView);

            // Perform any control setup here
        }

        protected override void DisconnectHandler(AppCompatEditText platformView)
        {
            // Perform any native view cleanup here
            platformView.Dispose();
            base.DisconnectHandler(platformView);
        }

        public static void MapText(CustomEntryHandler handler, CustomEntry view)
        {
            handler.PlatformView.Text = view.Text;
            handler.PlatformView?.SetSelection(handler.PlatformView?.Text?.Length ?? 0);
        }

        public static void MapTextColor(CustomEntryHandler handler, CustomEntry view)
        {
            handler.PlatformView?.SetTextColor(view.TextColor.ToPlatform());
        }
    }
}

CustomEntryHandler는 플랫폼 간 컨트롤 형식을 지정하는 제네릭 인수와 AppCompatEditText 네이 CustomEntry 티브 컨트롤의 형식을 지정하는 인수를 사용하여 클래스에서 ViewHandler 파생됩니다.

재정의는 CreatePlatformView 개체를 AppCompatEditText 만들고 반환합니다. 재정의 ConnectHandler 는 필요한 네이티브 뷰 설정을 수행할 위치입니다. 재정의는 DisconnectHandler 클린 네이티브 뷰를 수행할 위치이므로 인스턴스에서 메서드를 DisposeAppCompatEditText 호출합니다.

또한 처리기는 속성 매퍼 사전에 정의된 작업을 구현합니다. 각 작업은 플랫폼 간 컨트롤에서 변경되는 속성에 대한 응답으로 실행되며 처리기 및 플랫폼 간 컨트롤 인스턴스를 인수로 요구하는 메서드입니다 static . 각 경우에 작업은 네이티브 컨트롤에 정의된 메서드를 호출합니다.

처리기 등록

사용자 지정 컨트롤 및 해당 처리기를 사용하려면 먼저 앱에 등록해야 합니다. 이 문제는 앱 프로젝트의 클래스에서 MauiProgram 앱의 플랫폼 간 진입점인 메서드에서 발생 CreateMauiApp 합니다.

using Microsoft.Extensions.Logging;
using MyMauiControl.Controls;
using MyMauiControl.Handlers;

namespace MyMauiControl;

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");
      })
      .ConfigureMauiHandlers(handlers =>
      {
        handlers.AddHandler(typeof(CustomEntry), typeof(CustomEntryHandler));
      });

#if DEBUG
    builder.Logging.AddDebug();
#endif

    return builder.Build();
  }
}

처리기는 및 AddHandler 메서드에 ConfigureMauiHandlers 등록됩니다. 메서드의 AddHandler 첫 번째 인수는 플랫폼 간 컨트롤 형식이며 두 번째 인수는 처리기 형식입니다.

참고 항목

이 등록 방법은 느리고 비용이 많이 드는 Xamarin.Forms의 어셈블리 검색을 방지합니다.

플랫폼 간 컨트롤 사용

앱에 처리기를 등록한 후 플랫폼 간 컨트롤을 사용할 수 있습니다.

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:controls="clr-namespace:MyMauiControl.Controls"
             x:Class="MyMauiControl.MainPage">
    <Grid>
        <controls:CustomEntry Text="Hello world"
                              TextColor="Blue" />
    </Grid>
</ContentPage>

네이티브 뷰 클린up

각 플랫폼의 처리기 구현은 이벤트 구독 취소 및 개체 삭제와 같은 네이티브 뷰 클린 수행하는 데 사용되는 구현을 재정 DisconnectHandler 의합니다. 그러나 이 재정의는 .NET MAUI에서 의도적으로 호출되지 않습니다. 대신 앱의 수명 주기에서 적절한 위치에서 직접 호출해야 합니다. 컨트롤이 포함된 페이지가 탐색되어 페이지의 Unloaded 이벤트가 발생할 수 있습니다.

페이지 Unloaded 이벤트에 대한 이벤트 처리기를 XAML에 등록할 수 있습니다.

<ContentPage ...
             xmlns:controls="clr-namespace:MyMauiControl.Controls"
             Unloaded="ContentPage_Unloaded">
    <Grid>
        <controls:CustomEntry x:Name="customEntry"
                              ... />
    </Grid>
</ContentPage>

그러면 이벤트에 대한 Unloaded 이벤트 처리기가 해당 인스턴스에서 Handler 메서드를 호출할 DisconnectHandler 수 있습니다.

void ContentPage_Unloaded(object sender, EventArgs e)
{
    customEntry.Handler?.DisconnectHandler();
}

참고 항목