Compartilhar via


Migrar um renderizador personalizado Xamarin.Forms para um manipulador MAUI do .NET

No Xamarin.Forms, renderizadores personalizados podem ser usados para personalizar a aparência e o comportamento de um controle e criar novos controles entre plataformas. Cada renderizador personalizado tem uma referência ao controle entre plataformas e geralmente depende para INotifyPropertyChanged enviar notificações de alteração de propriedade. Em vez de usar renderizadores personalizados, o .NET Multi-platform App UI (.NET MAUI) introduz um novo conceito chamado manipulador.

Os manipuladores oferecem muitas melhorias de desempenho em relação aos renderizadores personalizados. No Xamarin.Forms, a ViewRenderer classe cria um elemento pai. Por exemplo, no Android, é criado um ViewGroup que é usado para tarefas auxiliares de posicionamento. No .NET MAUI, a classe não cria um elemento pai, o ViewHandler que ajuda a reduzir o tamanho da hierarquia visual e melhorar o desempenho do aplicativo. Os manipuladores também separam os controles de plataforma da estrutura. O controle da plataforma só precisa lidar com as necessidades da estrutura. Isso não é apenas mais eficiente, mas é muito mais fácil estender ou substituir quando necessário. Os manipuladores também são adequados para reutilização por outras estruturas, como Comet e Fabulous. Para obter mais informações sobre manipuladores, consulte Manipuladores.

No Xamarin.Forms, o método em um renderizador personalizado cria o controle de plataforma, inicializa valores padrão, assina eventos e manipula o elemento Xamarin.Forms ao qual o renderizador foi anexado () e o elemento aoOldElement qual o OnElementChanged renderizador está anexado (NewElement). Além disso, um único OnElementPropertyChanged método define as operações a serem invocadas quando uma alteração de propriedade ocorre no controle entre plataformas. O .NET MAUI simplifica essa abordagem, para que cada alteração de propriedade seja manipulada por um método separado e para que o código para criar o controle de plataforma, executar a instalação do controle e executar a limpeza do controle seja separado em métodos distintos.

O processo para migrar um controle personalizado Xamarin.Forms que é apoiado por renderizadores personalizados em cada plataforma para um controle personalizado .NET MAUI que é apoiado por um manipulador em cada plataforma é o seguinte:

  1. Crie uma classe para o controle de plataforma cruzada, que fornece a API pública do controle. Para obter mais informações, consulte Criar o controle entre plataformas.
  2. Crie uma partial classe de manipulador. Para obter mais informações, consulte Criar o manipulador.
  3. Na classe handler, crie um PropertyMapper dicionário, que define as Ações a serem executadas quando ocorrerem alterações de propriedade entre plataformas. Para obter mais informações, consulte Criar o mapeador de propriedades.
  4. Crie partial classes de manipulador para cada plataforma que cria as exibições nativas que implementam o controle entre plataformas. Para obter mais informações, consulte Criar os controles de plataforma.
  5. Registre o manipulador usando os ConfigureMauiHandlers métodos e AddHandler na classe do MauiProgram seu aplicativo. Para obter mais informações, consulte Registrar o manipulador.

Em seguida, o controle multiplataforma pode ser consumido. Para obter mais informações, consulte Consumir o controle entre plataformas.

Como alternativa, renderizadores personalizados que personalizam controles Xamarin.Forms podem ser convertidos para que modifiquem manipuladores MAUI do .NET. Para obter mais informações, consulte Personalizar controles com manipuladores.

Criar o controle multiplataforma

Para criar um controle entre plataformas, você deve criar uma classe que deriva de 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); }
        }
    }
}

O controle deve fornecer uma API pública que será acessada por seu manipulador e controlar os consumidores. Os controles entre plataformas devem derivar de View, que representa um elemento visual usado para colocar layouts e exibições na tela.

Criar o manipulador

Depois de criar seu controle entre plataformas, você deve criar uma partial classe para seu manipulador:

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

A classe handler é uma classe parcial cuja implementação será concluída em cada plataforma com uma classe parcial adicional.

As instruções condicionais using definem o PlatformView tipo em cada plataforma. A instrução condicional using final define PlatformView como sendo igual a System.Object. Isso é necessário para que o PlatformView tipo possa ser usado dentro do manipulador para uso em todas as plataformas. A alternativa seria ter que definir a PlatformView propriedade uma vez por plataforma, usando compilação condicional.

Criar o mapeador de propriedades

Cada manipulador normalmente fornece um mapeador de propriedades, que define quais Ações devem ser executadas quando uma alteração de propriedade ocorre no controle entre plataformas. O PropertyMapper tipo é um Dictionary que mapeia as propriedades do controle de plataforma cruzada para suas Ações associadas.

Observação

O mapeador de propriedades é o substituto para o OnElementPropertyChanged método em um renderizador personalizado Xamarin.Forms.

PropertyMapper é definido na classe genérica ViewHandler do .NET MAUI e requer que dois argumentos genéricos sejam fornecidos:

  • A classe para o controle de plataforma cruzada, que deriva de View.
  • A classe para o manipulador.

O exemplo de código a seguir mostra a classe estendida com a CustomEntryHandlerPropertyMapper definição:

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

O PropertyMapper é um cuja chave é um e cujo valor é um Dictionarystring genérico Action. O string representa o nome da propriedade do controle entre plataformas e o representa um static método que requer o manipulador e o Action controle entre plataformas como argumentos. Por exemplo, a assinatura do MapText método é public static void MapText(CustomEntryHandler handler, CustomEntry view).

Cada manipulador de plataforma deve fornecer implementações das Ações, que manipulam as APIs de exibição nativa. Isso garante que, quando uma propriedade é definida em um controle entre plataformas, a exibição nativa subjacente será atualizada conforme necessário. A vantagem dessa abordagem é que ela permite a fácil personalização de controle entre plataformas, pois o mapeador de propriedades pode ser modificado por consumidores de controle entre plataformas sem subclassificação. Para obter mais informações, consulte Personalizar controles com manipuladores.

Criar os controles da plataforma

Depois de criar os mapeadores para seu manipulador, você deve fornecer implementações de manipulador em todas as plataformas. Isso pode ser feito adicionando implementações parciais de manipulador de classe nas pastas filho da pasta Plataformas . Como alternativa, você pode configurar seu projeto para oferecer suporte a multidirecionamento baseado em nome de arquivo, multidirecionamento baseado em pasta ou ambos.

A multisegmentação baseada em nome de arquivo é configurada adicionando o seguinte XML ao arquivo de projeto, como filhos do <Project> nó:

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

Para obter mais informações sobre como configurar a multisegmentação, consulte Configurar a multisegmentação.

Cada classe de manipulador de plataforma deve ser uma classe parcial e derivar da classe genérica ViewHandler , que requer dois argumentos de tipo:

  • A classe para o controle de plataforma cruzada, que deriva de View.
  • O tipo de exibição nativa que implementa o controle entre plataformas na plataforma. Isso deve ser idêntico ao tipo da PlatformView propriedade no manipulador.

Importante

A ViewHandler classe fornece VirtualView e PlatformView propriedades. A VirtualView propriedade é usada para acessar o controle de plataforma cruzada de seu manipulador. A PlatformView propriedade, é usada para acessar a exibição nativa em cada plataforma que implementa o controle entre plataformas.

Cada uma das implementações do manipulador de plataforma deve substituir os seguintes métodos:

  • CreatePlatformView, que deve criar e retornar a exibição nativa que implementa o controle entre plataformas.
  • ConnectHandler, que deve executar qualquer configuração de modo de exibição nativo, como inicializar o modo de exibição nativo e executar assinaturas de eventos.
  • DisconnectHandler, que deve executar qualquer limpeza de exibição nativa, como cancelar a assinatura de eventos e descartar objetos. Esse método não é intencionalmente chamado pelo .NET MAUI. Em vez disso, você mesmo deve invocá-lo a partir de um local adequado no ciclo de vida do seu aplicativo. Para obter mais informações, consulte Limpeza do modo de exibição nativo.

Observação

O CreatePlatformView, ConnectHandlere DisconnectHandler substituições são as substituições para o OnElementChanged método em um renderizador personalizado Xamarin.Forms.

Cada manipulador de plataforma também deve implementar as Ações definidas nos dicionários do mapeador. Além disso, cada manipulador de plataforma também deve fornecer código, conforme necessário, para implementar a funcionalidade do controle de plataforma cruzada na plataforma. Como alternativa, para controles mais complexos, isso pode ser fornecido por um tipo adicional.

O exemplo a seguir mostra a CustomEntryHandler implementação no Android:

#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 deriva ViewHandler da classe, com o argumento genérico CustomEntry especificando o tipo de controle entre plataformas e o argumento especificando o AppCompatEditText tipo de controle nativo.

A CreatePlatformView substituição cria e retorna um AppCompatEditText objeto. A ConnectHandler substituição é o local para executar qualquer configuração de exibição nativa necessária. A DisconnectHandler substituição é o local para executar qualquer limpeza de exibição nativa e, portanto, chama o Dispose método na AppCompatEditText instância.

O manipulador também implementa as Ações definidas no dicionário do mapeador de propriedades. Cada Action é executada em resposta a uma alteração de propriedade no controle entre plataformas e é um static método que requer instâncias de controle entre plataformas como argumentos. Em cada caso, a Action chama métodos definidos no controle nativo.

Registrar o manipulador

Um controle personalizado e seu manipulador devem ser registrados com um aplicativo, antes de poderem ser consumidos. Isso deve ocorrer no CreateMauiApp método na MauiProgram classe em seu projeto de aplicativo, que é o ponto de entrada entre plataformas para o aplicativo:

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

O manipulador é registrado com o ConfigureMauiHandlers método e AddHandler . O primeiro argumento para o método é o tipo de controle entre plataformas, com o AddHandler segundo argumento sendo seu tipo de manipulador.

Observação

Essa abordagem de registro evita a varredura de montagem do Xamarin.Forms, que é lenta e cara.

Consuma o controle multiplataforma

Depois de registrar o manipulador com seu aplicativo, o controle entre plataformas pode ser consumido:

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

Limpeza do modo de exibição nativo

A implementação do manipulador de cada plataforma substitui a implementação, que é usada para executar a DisconnectHandler limpeza de exibição nativa, como cancelar a assinatura de eventos e descartar objetos. No entanto, essa substituição não é intencionalmente invocada pelo .NET MAUI. Em vez disso, você mesmo deve invocá-lo a partir de um local adequado no ciclo de vida do seu aplicativo. Isso pode ocorrer quando a página que contém o controle é navegada para longe, o que faz com que o evento da Unloaded página seja gerado.

Um manipulador de eventos para o evento da Unloaded página pode ser registrado em XAML:

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

O manipulador de eventos para o evento pode invocar o UnloadedDisconnectHandler método em sua Handler instância:

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

Confira também