今天,我想談?wù)劜⑾蚰故驹?NET MAUI中完全自定義控件的方法。在查看.NET MAUI之前,讓我們回到幾年前,回到 Xamarin.Forms 時(shí)代。那時(shí),我們有很多自定義控件的方法, 比如當(dāng)您不需要訪問平臺(tái)特有的 API 來(lái)自定義控件時(shí),可以使用Behaviors;如果您需要訪問平臺(tái)特有的 API,可以使用 Effects。
讓我們稍微關(guān)注一下Effects API。它是由于 Xamarin 缺乏多目標(biāo)體系結(jié)構(gòu)而創(chuàng)建的。這意味著我們無(wú)法在共享級(jí)別(在 .NET 標(biāo)準(zhǔn)?csproj?中)訪問特定于平臺(tái)的代碼。它工作得很好,可以讓您免于創(chuàng)建自定義渲染器。
今天,在 .NET MAUI 中,我們可以利用多目標(biāo)架構(gòu)的強(qiáng)大功能,并在我們的共享項(xiàng)目中訪問特定于平臺(tái)的 API。那么我們還需要?Effects?嗎?不需要了,因?yàn)槲覀兛梢栽L問我們所需要的所有平臺(tái)的所有代碼和 API。
那么讓我們談?wù)勗?.NET MAUI 中自定義一個(gè)控件的所有可能性以及在此過(guò)程中您可以遇到的一些障礙。為此,我們將自定義?Image?控件,添加對(duì)呈現(xiàn)的圖像進(jìn)行著色的功能。
注意:如果您想使用?Effects?,.NET MAUI仍然支持,但不建議使用
源代碼參考來(lái)自 .NET MAUI Community Toolkit 的IconTintColor。
自定義現(xiàn)有控件?
要向現(xiàn)有控件添加額外的功能,需要我們對(duì)其進(jìn)行擴(kuò)展并添加所需的功能。
讓我們創(chuàng)建一個(gè)新控件,class ImageTintColor : Image?并添加一個(gè)新的
public class ImageTintColor : Image
{
public static readonly BindableProperty TintColorProperty =
BindableProperty.Create(nameof(TintColor), typeof(Color), typeof(ImageTintColor), propertyChanged: OnTintColorChanged);
public Color? TintColor
{
get => (Color?)GetValue(TintColorProperty);
set => SetValue(TintColorProperty, value);
}
static void OnTintColorChanged(BindableObject bindable, object oldValue, object newValue)
{
// ...
}
}
熟悉 Xamarin.Forms 的人會(huì)認(rèn)識(shí)到這一點(diǎn);它與您將在 Xamarin.Forms 應(yīng)用程序中編寫的代碼幾乎相同。
.NET MAUI 平臺(tái)特定的 API 工作將在?OnTintColorChanged?委托上進(jìn)行。讓我們來(lái)看看。
public class ImageTintColor : Image
{
public static readonly BindableProperty TintColorProperty =
BindableProperty.Create(nameof(TintColor), typeof(Color), typeof(ImageTintColor), propertyChanged: OnTintColorChanged);
public Color? TintColor
{
get => (Color?)GetValue(TintColorProperty);
set => SetValue(TintColorProperty, value);
}
static void OnTintColorChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (ImageTintColor)bindable;
var tintColor = control.TintColor;
if (control.Handler is null || control.Handler.PlatformView is null)
{
// 執(zhí)行 Handler 且 PlatformView 為 null 時(shí)的解決方法
control.HandlerChanged += OnHandlerChanged;
return;
}
if (tintColor is not null)
{
#if ANDROID
// 注意 Android.Widget.ImageView 的使用,它是一個(gè) Android 特定的 API
// 您可以在這里找到`ApplyColor`的Android實(shí)現(xiàn):https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.android.cs#L9-L12
ImageExtensions.ApplyColor((Android.Widget.ImageView)control.Handler.PlatformView, tintColor);
#elif IOS
// 注意 UIKit.UIImage 的使用,它是一個(gè) iOS 特定的 API
// 您可以在這里找到`ApplyColor`的iOS實(shí)現(xiàn):https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.ios.cs#L7-L11
ImageExtensions.ApplyColor((UIKit.UIImageView)control.Handler.PlatformView, tintColor);
#endif
}
else
{
#if ANDROID
// 注意 Android.Widget.ImageView 的使用,它是一個(gè) Android 特定的 API
// 您可以在這里找到 `ClearColor` 的 Android 實(shí)現(xiàn):https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.android.cs#L14-L17
ImageExtensions.ClearColor((Android.Widget.ImageView)control.Handler.PlatformView);
#elif IOS
// 注意 UIKit.UIImage 的使用,它是一個(gè) iOS 特定的 API
// 您可以在這里找到`ClearColor`的iOS實(shí)現(xiàn):https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.ios.cs#L13-L16
ImageExtensions.ClearColor((UIKit.UIImageView)control.Handler.PlatformView);
#endif
}
void OnHandlerChanged(object s, EventArgs e)
{
OnTintColorChanged(control, oldValue, newValue);
control.HandlerChanged -= OnHandlerChanged;
}
}
}
因?yàn)?.NET MAUI 使用多目標(biāo),我們可以訪問平臺(tái)的詳細(xì)信息并按照我們想要的方式自定義控件。ImageExtensions.ApplyColor?和?ImageExtensions.ClearColor?方法是添加或刪除圖像色調(diào)的輔助方法。
您可能會(huì)注意到?Handler?和?PlatformView?的 null 檢查。這可能是您在使用過(guò)程中遇到的第一個(gè)阻礙。在創(chuàng)建和實(shí)例化?Image?控件并調(diào)用?BindableProperty?的?PropertyChanged?委托時(shí),Handler?可以為?null。因此,如果不進(jìn)行 null 檢查,代碼將拋出?NullReferenceException。這聽起來(lái)像一個(gè)bug,但它實(shí)際上是一個(gè)特性!這使 .NET MAUI 工程團(tuán)隊(duì)能夠保持與 Xamarin.Forms 上的控件相同的生命周期,從而避免從 Forms 遷移到 .NET MAUI 的應(yīng)用程序的一些重大更改。
現(xiàn)在我們已經(jīng)完成了所有設(shè)置,可以在?ContentPage?中使用控件了。在下面的代碼片段中,您可以看到如何在 XAML 中使用它:
<ContentPage x:Class="MyMauiApp.ImageControl"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyMauiApp"
Title="ImageControl"
BackgroundColor="White">
<local:ImageTintColor x:Name="ImageTintColorControl"
Source="shield.png"
TintColor="Orange" />
</ContentPage>
使用附加屬性和 PropertyMapper?
自定義控件的另一種方法是使用?AttachedProperties,當(dāng)您不需要將其綁定到特定的自定義控件時(shí)是 使用BindableProperty。
下面是我們?nèi)绾螢?TintColor 創(chuàng)建一個(gè) AttachedProperty:
public static class TintColorMapper
{
public static readonly BindableProperty TintColorProperty = BindableProperty.CreateAttached("TintColor", typeof(Color), typeof(Image), null);
public static Color GetTintColor(BindableObject view) => (Color)view.GetValue(TintColorProperty);
public static void SetTintColor(BindableObject view, Color? value) => view.SetValue(TintColorProperty, value);
public static void ApplyTintColor()
{
// ...
}
}
同樣,我們?cè)?Xamarin.Forms 上為?AttachedProperty?提供了樣板,但如您所見,我們沒有?PropertyChanged?委托。為了處理屬性更改,我們將使用?ImageHandler?中的?Mapper。您可以在任何級(jí)別添加 Mapper,因?yàn)槌蓡T是靜態(tài)的。我選擇在?TintColorMapper?類中執(zhí)行此操作,如下所示。
public static class TintColorMapper
{
public static readonly BindableProperty TintColorProperty = BindableProperty.CreateAttached("TintColor", typeof(Color), typeof(Image), null);
public static Color GetTintColor(BindableObject view) => (Color)view.GetValue(TintColorProperty);
public static void SetTintColor(BindableObject view, Color? value) => view.SetValue(TintColorProperty, value);
public static void ApplyTintColor()
{
ImageHandler.Mapper.Add("TintColor", (handler, view) =>
{
var tintColor = GetTintColor((Image)handler.VirtualView);
if (tintColor is not null)
{
#if ANDROID
// 注意 Android.Widget.ImageView 的使用,它是一個(gè) Android 特定的 API
// 您可以在這里找到`ApplyColor`的Android實(shí)現(xiàn):https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.android.cs#L9-L12
ImageExtensions.ApplyColor((Android.Widget.ImageView)control.Handler.PlatformView, tintColor);
#elif IOS
// 注意 UIKit.UIImage 的使用,它是一個(gè) iOS 特定的 API
// 您可以在這里找到`ApplyColor`的iOS實(shí)現(xiàn):https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.ios.cs#L7-L11
ImageExtensions.ApplyColor((UIKit.UIImageView)handler.PlatformView, tintColor);
#endif
}
else
{
#if ANDROID
// 注意 Android.Widget.ImageView 的使用,它是一個(gè) Android 特定的 API
// 您可以在這里找到 `ClearColor` 的 Android 實(shí)現(xiàn):https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.android.cs#L14-L17
ImageExtensions.ClearColor((Android.Widget.ImageView)handler.PlatformView);
#elif IOS
// 注意 UIKit.UIImage 的使用,它是一個(gè) iOS 特定的 API
// 您可以在這里找到`ClearColor`的iOS實(shí)現(xiàn):https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.ios.cs#L13-L16
ImageExtensions.ClearColor((UIKit.UIImageView)handler.PlatformView);
#endif
}
});
}
}
代碼與之前顯示的幾乎相同,只是使用了另一個(gè) API 實(shí)現(xiàn),在本例中是?AppendToMapping?方法。如果您不想要這種行為,可以改用?CommandMapper,它將在屬性更改或操作發(fā)生時(shí)觸發(fā)。
請(qǐng)注意,當(dāng)我們處理?Mapper?和?CommandMapper?時(shí),我們將為項(xiàng)目中使用該處理程序的所有控件添加此行為。在這種情況下,所有Image控件都會(huì)觸發(fā)此代碼。在某些情況下這可能并不是您想要的,如果您需要更具體的方法,?PlatformBehavior?方法將會(huì)非常適合。
現(xiàn)在我們已經(jīng)設(shè)置好了所有內(nèi)容,可以在頁(yè)面中使用控件了,在下面的代碼片段中,您可以看到如何在 XAML 中使用它。
<ContentPage x:Class="MyMauiApp.ImageControl"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyMauiApp"
Title="ImageControl"
BackgroundColor="White">
<Image x:Name="Image"
local:TintColorMapper.TintColor="Fuchsia"
Source="shield.png" />
</ContentPage>
使用平臺(tái)行為
PlatformBehavior?是在 .NET MAUI 上創(chuàng)建的新 API,它讓您在需要以安全的方式訪問平臺(tái)特有的 API 時(shí),可以更輕松地自定義控件(這是安全的因?yàn)樗_保?Handler?和?PlatformView?不為?null?)。它有兩種方法來(lái)重寫:OnAttachedTo?和?OnDetachedFrom。此 API 用于替換 Xamarin.Forms 中的?Effect?API 并利用多目標(biāo)體系結(jié)構(gòu)。
在此示例中,我們將使用部分類來(lái)實(shí)現(xiàn)特定于平臺(tái)的 API:
//文件名 : ImageTintColorBehavior.cs
public partial class IconTintColorBehavior
{
public static readonly BindableProperty TintColorProperty =
BindableProperty.Create(nameof(TintColor), typeof(Color), typeof(IconTintColorBehavior), propertyChanged: OnTintColorChanged);
public Color? TintColor
{
get => (Color?)GetValue(TintColorProperty);
set => SetValue(TintColorProperty, value);
}
}
上面的代碼將被我們所針對(duì)的所有平臺(tái)編譯。
現(xiàn)在讓我們看看?Android?平臺(tái)的代碼:
//文件名: ImageTintColorBehavior.android.cs
public partial class IconTintColorBehavior : PlatformBehavior<Image, ImageView>
// 注意 ImageView 的使用,它是 Android 特定的 API{
protected override void OnAttachedTo(Image bindable, ImageView platformView) =>
ImageExtensions.ApplyColor(bindable, platformView);
// 您可以在這里找到`ApplyColor`的Android實(shí)現(xiàn):https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.android.cs#L9-L12
protected override void OnDetachedFrom(Image bindable, ImageView platformView) =>
ImageExtensions.ClearColor(platformView);
// 您可以在這里找到 `ClearColor` 的 Android 實(shí)現(xiàn):https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.android.cs#L14-L17
}
這是?iOS?平臺(tái)的代碼:
//文件名: ImageTintColorBehavior.ios.cs
public partial class IconTintColorBehavior : PlatformBehavior<Image, UIImageView>
// 注意 UIImageView 的使用,它是一個(gè) iOS 特定的 API
{
protected override void OnAttachedTo(Image bindable, UIImageView platformView) =>
ImageExtensions.ApplyColor(bindable, platformView);
// 你可以在這里找到`ApplyColor`的iOS實(shí)現(xiàn):https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.ios.cs#L7-L11
protected override void OnDetachedFrom(Image bindable, UIImageView platformView) =>
ImageExtensions.ClearColor(platformView);
// 你可以在這里找到`ClearColor`的iOS實(shí)現(xiàn):https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.ios.cs#L13-L16
}
正如您所看到的,我們不需要關(guān)心是否?Handler?為?null?,因?yàn)?PlatformBehavior<T, U>?會(huì)為我們處理。
我們可以指定此行為涵蓋的平臺(tái)特有的 API 的類型。如果您想為多個(gè)類型應(yīng)用控件,則無(wú)需指定平臺(tái)視圖的類型(例如,使用 PlatformBehavior<T> );您可能想在多個(gè)控件中應(yīng)用您的行為,在這種情況下,platformView 將是 Android 上的 PlatformBehavior<View> 和 iOS 上的 PlatformBehavior<UIView>。
而且用法更好,您只需要調(diào)用?Behavior?即可:
<ContentPage x:Class="MyMauiApp.ImageControl"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyMauiApp"
Title="ImageControl"
BackgroundColor="White">
<Image x:Name="Image"
Source="shield.png">
<Image.Behaviors>
<local:IconTintColorBehavior TintColor="Fuchsia">
</Image.Behaviors>
</Image>
</ContentPage>
注意:當(dāng)?Handler?與?VirtualView?斷開連接時(shí),即觸發(fā)?Unloaded?事件時(shí),PlatformBehavior 將調(diào)用?OnDetachedFrom。Behavior?API 不會(huì)自動(dòng)調(diào)用?OndetachedFrom?方法,作為開發(fā)者需要自己處理。
總結(jié)
在這篇文章中,我們討論了自定義控件以及與平臺(tái)特有的 API 交互的各種方式。沒有正確或錯(cuò)誤的方法,所有這些都是有效的解決方案,您只需要看看哪種方法更適合您的情況。我想說(shuō)的是,在大多數(shù)情況下,您會(huì)想要使用?PlatformBehavior,因?yàn)樗荚谑褂枚嗄繕?biāo)方法并確保在控件不再使用時(shí)清理資源。要了解更多信息,請(qǐng)查看有關(guān)自定義控件的文檔。
關(guān)注微軟開發(fā)者M(jìn)SDN了解更多文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-479862.html
點(diǎn)擊了解更多~文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-479862.html
到了這里,關(guān)于在 .NET MAUI 中如何更好地自定義控件的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!