分享方式:


將 Xamarin.Forms 自定義轉譯器移轉至 .NET MAUI 處理程式

在 Xamarin.Forms 中,自定義轉譯器可用來自定義控件的外觀和行為,並建立新的跨平臺控制件。 每個自定義轉譯器都有跨平臺控件的參考,而且通常會依賴 INotifyPropertyChanged 傳送屬性變更通知。 .NET 多平臺應用程式 UI (.NET MAUI) 不是使用自定義轉譯器,而是引進了 稱為處理程式的新概念。

處理程式可透過自定義轉譯器提供許多效能改善。 在 Xamarin.Forms 中,類別 ViewRenderer 會建立父元素。 例如,在Android上, ViewGroup 會建立 ,用於輔助定位工作。 在 .NET MAUI 中,類別 ViewHandler 不會建立父元素,這有助於減少視覺階層的大小,並改善應用程式的效能。 處理程式也會將平臺控件與架構分離。 平臺控件只需要處理架構的需求。 這不僅更有效率,而且在需要時更容易擴充或覆寫。 處理程式也適合其他架構重複使用,例如 CometFabulous。 如需處理程式的詳細資訊,請參閱 處理程式

在 Xamarin.Forms 中,OnElementChanged自定義轉譯器中的 方法會建立平臺控件、初始化預設值、訂閱事件,以及處理轉譯器附加至的OldElement Xamarin.Forms 元素,以及轉譯器附加至 的 元素。NewElement 此外,單 OnElementPropertyChanged 一方法會定義在跨平臺控件中發生屬性變更時要叫用的作業。 .NET MAUI 可簡化此方法,讓每個屬性變更都由個別方法處理,讓程式代碼建立平臺控件、執行控件設定和執行控件清除,會分成不同的方法。

將每個平臺上自定義轉譯器支援的 Xamarin.Forms 自定義控件移轉至每個平臺上由處理程式支援的 .NET MAUI 自定義控件的程式如下:

  1. 建立跨平臺控制項的類別,以提供控制件的公用 API。 如需詳細資訊,請參閱 建立跨平臺控件
  2. 建立 partial 處理程序類別。 如需詳細資訊,請參閱 建立處理程式
  3. 在處理程式類別中,建立 PropertyMapper 字典,以定義在發生跨平臺屬性變更時要採取的動作。 如需詳細資訊,請參閱 建立屬性對應程式
  4. partial為每個平臺建立處理程序類別,以建立實作跨平臺控件的原生檢視。 如需詳細資訊,請參閱 建立平臺控件
  5. 在應用程式的 MauiProgram 類別中使用 ConfigureMauiHandlersAddHandler 方法註冊處理程式。 如需詳細資訊,請參閱 註冊處理程式

然後,可以取用跨平臺控件。 如需詳細資訊,請參閱 取用跨平臺控件

或者,可以轉換自定義 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
    {
    }
}

處理程式類別是一個部分類別,其實作將在每個平臺上完成,並具有額外的部分類別。

條件 using 語句會在 PlatformView 每個平台上定義類型。 最終的條件 using 語句定義 PlatformView 等於 System.Object。 這是必要的,如此一來, 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)
    {
    }
}

PropertyMapperDictionary ,其索引鍵為 ,string其值為泛型 Actionstring表示跨平臺控件的屬性名稱,而 Action 表示static需要處理程式和跨平臺控件做為自變數的方法。 例如,方法的 MapText 簽章是 public static void MapText(CustomEntryHandler handler, CustomEntry view)

每個平臺處理程式都必須提供動作的實作,以操作原生檢視 API。 這可確保在跨平臺控件上設定屬性時,基礎原生檢視會視需要更新。 這種方法的優點是,它可讓您輕鬆進行跨平臺控件自定義,因為跨平臺控件取用者可以修改屬性對應程式,而不需要子類別化。 如需詳細資訊,請參閱 使用處理程式自定義控件。

建立平臺控制件

建立處理程式的對應器之後,您必須在所有平臺上提供處理程序實作。 這可以藉由在 [平臺] 資料夾的子資料夾中新增部分類別處理程式實作來完成。 或者,您可以設定專案以支援以檔名為基礎的多重目標,或資料夾型多重目標,或兩者。

以檔名為基礎的多重目標設定方式是將下列 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 相同。

重要

類別 ViewHandler 提供 VirtualViewPlatformView 屬性。 屬性 VirtualView 可用來從其處理程式存取跨平臺控制件。 屬性 PlatformView 可用來存取實作跨平臺控件之每個平臺上的原生檢視。

每個平台處理程式實作都應該覆寫下列方法:

  • CreatePlatformView,其應該建立並傳回實作跨平臺控件的原生檢視。
  • ConnectHandler,其應該執行任何原生檢視設定,例如初始化原生檢視和執行事件訂閱。
  • DisconnectHandler,其應該執行任何原生檢視清除,例如取消訂閱事件和處置物件。 此方法是刻意不會由 .NET MAUI 叫用。 相反地,您必須從應用程式生命週期中的適當位置自行叫用它。 如需詳細資訊,請參閱 原生檢視清除

注意

CreatePlatformViewConnectHandlerDisconnectHandler 覆寫是 Xamarin.Forms 自定義轉譯器中 方法的取代OnElementChanged專案。

每個平台處理程式也應該實作對應程式字典中定義的動作。 此外,每個平台處理程式也應該視需要提供程式碼,以在平臺上實作跨平臺控件的功能。 或者,對於更複雜的控件,這可以由其他類型提供。

下列範例示範 CustomEntryHandler 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 衍生自 ViewHandler 類別,其中泛型 CustomEntry 自變數會指定跨平臺控件類型,以及 AppCompatEditText 指定原生控件類型的自變數。

CreatePlatformView 寫會建立並傳 AppCompatEditText 回物件。 覆 ConnectHandler 寫是執行任何必要原生檢視設定的位置。 覆DisconnectHandler寫是執行任何原生檢視清除的位置,因此會在 實例上AppCompatEditText呼叫 Dispose 方法。

處理程式也會實作屬性對應程式字典中定義的 Actions。 系統會執行每個動作,以回應跨平臺控件上變更的屬性,而且是 static 需要處理程式和跨平臺控件實例做為自變數的方法。 在每個案例中,Action 會呼叫原生控件上定義的方法。

註冊處理程式

自定義控件及其處理程式必須先向應用程式註冊,才能取用。 這應該發生在 CreateMauiApp 應用程式項目中 類別的 MauiProgram 方法中,這是應用程式的跨平臺進入點:

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

處理程式會向 ConfigureMauiHandlersAddHandler 方法註冊。 方法的第一個自變數 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>

原生檢視清除

每個平台的處理程式實作都會 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();
}

另請參閱