將 Xamarin.Forms 自定義轉譯器移轉至 .NET MAUI 處理程式
在 Xamarin.Forms 中,自定義轉譯器可用來自定義控件的外觀和行為,並建立新的跨平臺控制件。 每個自定義轉譯器都有跨平臺控件的參考,而且通常會依賴 INotifyPropertyChanged
傳送屬性變更通知。 .NET 多平臺應用程式 UI (.NET MAUI) 不是使用自定義轉譯器,而是引進了 稱為處理程式的新概念。
處理程式可透過自定義轉譯器提供許多效能改善。 在 Xamarin.Forms 中,類別 ViewRenderer
會建立父元素。 例如,在Android上, ViewGroup
會建立 ,用於輔助定位工作。 在 .NET MAUI 中,類別 ViewHandler<TVirtualView,TPlatformView> 不會建立父元素,這有助於減少視覺階層的大小,並改善應用程式的效能。 處理程式也會將平臺控件與架構分離。 平臺控件只需要處理架構的需求。 這不僅更有效率,而且在需要時更容易擴充或覆寫。 處理程式也適合其他架構重複使用,例如 Comet 和 Fabulous。 如需處理程式的詳細資訊,請參閱 處理程式。
在 Xamarin.Forms 中,OnElementChanged
自定義轉譯器中的 方法會建立平臺控件、初始化預設值、訂閱事件,以及處理轉譯器附加至的OldElement
Xamarin.Forms 元素,以及轉譯器附加至 的 元素。NewElement
此外,單 OnElementPropertyChanged
一方法會定義在跨平臺控件中發生屬性變更時要叫用的作業。 .NET MAUI 可簡化此方法,讓每個屬性變更都由個別方法處理,讓程式代碼建立平臺控件、執行控件設定和執行控件清除,會分成不同的方法。
將每個平臺上自定義轉譯器支援的 Xamarin.Forms 自定義控件移轉至每個平臺上由處理程式支援的 .NET MAUI 自定義控件的程式如下:
- 建立跨平臺控制項的類別,以提供控制件的公用 API。 如需詳細資訊,請參閱 建立跨平臺控件。
- 建立
partial
處理程序類別。 如需詳細資訊,請參閱 建立處理程式。 - 在處理程式類別中,建立 PropertyMapper 字典,以定義在發生跨平臺屬性變更時要採取的動作。 如需詳細資訊,請參閱 建立屬性對應程式。
partial
為每個平臺建立處理程序類別,以建立實作跨平臺控件的原生檢視。 如需詳細資訊,請參閱 建立平臺控件。- 在應用程式的
MauiProgram
類別中使用 ConfigureMauiHandlers 和 AddHandler 方法註冊處理程式。 如需詳細資訊,請參閱 註冊處理程式。
然後,可以取用跨平臺控件。 如需詳細資訊,請參閱 取用跨平臺控件。
或者,可以轉換自定義 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
屬性一次。
建立屬性對應程式
每個處理程式通常會提供 屬性對應程式,定義跨平臺控制件中發生屬性變更時要採取的動作。 類型 PropertyMapper 是 Dictionary
,會將跨平臺控件的屬性對應至其相關聯的 Actions。
注意
屬性對應程式是 Xamarin.Forms 自定義轉譯器中 方法的取代 OnElementPropertyChanged
專案。
PropertyMapper 定義在 .NET MAUI 的 ViewHandler<TVirtualView,TPlatformView> 類別中,而且需要提供兩個泛型自變數:
- 跨平臺控制項的 類別,其衍生自 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)
{
}
}
PropertyMapper是 Dictionary
,其索引鍵為 ,string
其值為泛型 Action
。 string
表示跨平臺控件的屬性名稱,而 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<TVirtualView,TPlatformView> 類別,這需要兩個類型自變數:
- 跨平臺控制項的 類別,其衍生自 View。
- 在平台上實作跨平臺控件的原生檢視類型。 這應該與處理程式中的屬性類型
PlatformView
相同。
重要
類別 ViewHandler<TVirtualView,TPlatformView> 提供 VirtualView
和 PlatformView
屬性。 屬性 VirtualView
可用來從其處理程式存取跨平臺控制件。 屬性 PlatformView
可用來存取實作跨平臺控件之每個平臺上的原生檢視。
每個平台處理程式實作都應該覆寫下列方法:
- CreatePlatformView,其應該建立並傳回實作跨平臺控件的原生檢視。
- ConnectHandler,其應該執行任何原生檢視設定,例如初始化原生檢視和執行事件訂閱。
- DisconnectHandler,其應該執行任何原生檢視清除,例如取消訂閱事件和處置物件。 此方法是刻意不會由 .NET MAUI 叫用。 相反地,您必須從應用程式生命週期中的適當位置自行叫用它。 如需詳細資訊,請參閱 原生檢視清除。
- CreatePlatformView,其應該建立並傳回實作跨平臺控件的原生檢視。
- ConnectHandler,其應該執行任何原生檢視設定,例如初始化原生檢視和執行事件訂閱。
- DisconnectHandler,其應該執行任何原生檢視清除,例如取消訂閱事件和處置物件。 這個方法預設由 .NET MAUI 自動叫用,不過此行為可以變更。 如需詳細資訊,請參閱 控制處理程式中斷連線。
注意
CreatePlatformView、 ConnectHandler和 DisconnectHandler 覆寫是 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<TVirtualView,TPlatformView> 類別,其中泛型 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();
}
}
處理程式會向 ConfigureMauiHandlers 和 AddHandler 方法註冊。 方法的第一個自變數 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();
}
控制處理程式中斷連線
每個平台的處理程式實作都會 DisconnectHandler 覆寫 實作,用來執行原生檢視清除,例如取消訂閱事件和處置物件。 根據預設,處理程式會盡可能自動中斷與其控件的連線,例如在應用程式中向後巡覽時。
在某些情況下,您可能會想要控制處理程式何時與控件中斷連線,這可以使用附加屬性來 HandlerProperties.DisconnectPolicy
達成。 此屬性需要自 HandlerDisconnectPolicy 變數,且列舉定義下列值:
Automatic
,表示處理程式會自動中斷連線。 這是HandlerProperties.DisconnectPolicy
附加屬性的預設值。Manual
,表示叫用 DisconnectHandler() 實作時,處理程式必須手動中斷連線。
下列範例顯示設定 HandlerProperties.DisconnectPolicy
附加屬性:
<controls:CustomEntry x:Name="customEntry"
Text="Hello world"
TextColor="Blue"
HandlerProperties.DisconnectPolicy="Manual" />
將附加屬性設定 HandlerProperties.DisconnectPolicy
為 Manual
時,您必須從應用程式生命週期中的適當位置自行叫用處理程式 DisconnectHandler 的實作。 這可以藉由叫 customEntry.Handler?.DisconnectHandler();
用 來達成。
此外,還有一個 DisconnectHandlers 擴充方法可將處理程式與指定的 IView中斷連接:
video.DisconnectHandlers();
中斷連線時, DisconnectHandlers 方法會傳播到控件樹狀結構,直到它完成或到達已設定手動原則的控件為止。