明天,我想谈谈并向您展现在.NET MAUI中齐全自定义控件的办法。在查看 .NET MAUI 之前,让咱们回到几年前,回到 Xamarin.Forms 时代。那时,咱们有很多自定义控件的办法, 比方当您不须要拜访平台特有的 API 来自定义控件时,能够应用Behaviors ;如果您须要拜访平台特有的 API,能够应用 Effects。

让咱们略微关注一下Effects API。它是因为 Xamarin 不足多指标体系结构而创立的。这意味着咱们无奈在共享级别(在 .NET 规范 csproj 中)拜访特定于平台的代码。它工作得很好,能够让您免于创立自定义渲染器。

明天,在 .NET MAUI 中,咱们能够利用多指标架构的弱小性能,并在咱们的共享我的项目中拜访特定于平台的 API。那么咱们还须要 Effects 吗?不须要了,因为咱们能够拜访咱们所须要的所有平台的所有代码和 API。

那么让咱们谈谈在 .NET MAUI 中自定义一个控件的所有可能性以及在此过程中您能够遇到的一些阻碍。为此,咱们将自定义 Image 控件,增加对出现的图像进行着色的性能。

留神:如果您想应用 Effects ,.NET MAUI依然反对,但不倡议应用源代码参考来自 .NET MAUI Community Toolkit 的IconTintColor。

自定义现有控件

要向现有控件增加额定的性能,须要咱们对其进行扩大并增加所需的性能。让咱们创立一个新控件,class ImageTintColor : Image 并增加一个新的

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 的人会意识到这一点;它与您将在 Xamarin.Forms 应用程序中编写的代码简直雷同。
.NET MAUI 平台特定的 API 工作将在 OnTintColorChanged 委托上进行。让咱们来看看。

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)        {            // 执行 Handler 且 PlatformView 为 null 时的解决办法            control.HandlerChanged += OnHandlerChanged;            return;        }        if (tintColor is not null)        {#if ANDROID            // 留神 Android.Widget.ImageView 的应用,它是一个 Android 特定的 API            // 您能够在这里找到`ApplyColor`的Android实现: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 的应用,它是一个 iOS 特定的 API            // 您能够在这里找到`ApplyColor`的iOS实现: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 的应用,它是一个 Android 特定的 API            // 您能够在这里找到 `ClearColor` 的 Android 实现: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 的应用,它是一个 iOS 特定的 API            // 您能够在这里找到`ClearColor`的iOS实现: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;        }    }}

因为 .NET MAUI 应用多指标,咱们能够拜访平台的详细信息并依照咱们想要的形式自定义控件。ImageExtensions.ApplyColor 和 ImageExtensions.ClearColor 办法是增加或删除图像色调的辅助办法。
您可能会留神到 Handler 和 PlatformView 的 null 查看。这可能是您在应用过程中遇到的第一个妨碍。在创立和实例化 Image 控件并调用 BindableProperty 的 PropertyChanged 委托时,Handler 能够为 null。因而,如果不进行 null 查看,代码将抛出 NullReferenceException。这听起来像一个bug,但它实际上是一个个性!这使 .NET MAUI 工程团队可能放弃与 Xamarin.Forms 上的控件雷同的生命周期,从而防止从 Forms 迁徙到 .NET MAUI 的应用程序的一些重大更改。
当初咱们曾经实现了所有设置,能够在 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,当您不须要将其绑定到特定的自定义控件时是 应用BindableProperty。
上面是咱们如何为 TintColor 创立一个 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()    {        // ...    }}

同样,咱们在 Xamarin.Forms 上为 AttachedProperty 提供了样板,但如您所见,咱们没有 PropertyChanged 委托。为了解决属性更改,咱们将应用 ImageHandler 中的 Mapper。您能够在任何级别增加 Mapper,因为成员是动态的。我抉择在 TintColorMapper 类中执行此操作,如下所示。

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 的应用,它是一个 Android 特定的 API                // 您能够在这里找到`ApplyColor`的Android实现: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 的应用,它是一个 iOS 特定的 API                // 您能够在这里找到`ApplyColor`的iOS实现: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 的应用,它是一个 Android 特定的 API                // 您能够在这里找到 `ClearColor` 的 Android 实现:https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.android.cs#L14-L17                ImageExtensions.ClearColor((Android.Widget.ImageView)handler.PlatformView);#elif IOS                // 留神 UIKit.UIImage 的应用,它是一个 iOS 特定的 API                // 您能够在这里找到`ClearColor`的iOS实现:https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.ios.cs#L13-L16                ImageExtensions.ClearColor((UIKit.UIImageView)handler.PlatformView);#endif            }        });    }}

代码与之前显示的简直雷同,只是应用了另一个 API 实现,在本例中是 AppendToMapping 办法。如果您不想要这种行为,能够改用 CommandMapper,它将在属性更改或操作产生时触发。

请留神,当咱们解决 Mapper 和 CommandMapper 时,咱们将为我的项目中应用该处理程序的所有控件增加此行为。在这种状况下,所有Image控件都会触发此代码。在某些状况下这可能并不是您想要的,如果您须要更具体的办法, PlatformBehavior 办法将会非常适合。

当初咱们曾经设置好了所有内容,能够在页面中应用控件了,在上面的代码片段中,您能够看到如何在 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>

应用平台行为

PlatformBehavior 是在 .NET MAUI 上创立的新 API,它让您在须要以平安的形式拜访平台特有的 API 时,能够更轻松地自定义控件(这是平安的因为它确保 Handler 和 PlatformView 不为 null )。它有两种办法来重写:OnAttachedTo 和 OnDetachedFrom。此 API 用于替换 Xamarin.Forms 中的 Effect API 并利用多指标体系结构。

在此示例中,咱们将应用局部类来实现特定于平台的 API:

//文件名 : ImageTintColorBehavior.cspublic 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);    }}

下面的代码将被咱们所针对的所有平台编译。
当初让咱们看看 Android 平台的代码:

//文件名: ImageTintColorBehavior.android.cspublic partial class IconTintColorBehavior : PlatformBehavior<Image, ImageView> // 留神 ImageView 的应用,它是 Android 特定的 API{    protected override void OnAttachedTo(Image bindable, ImageView platformView) =>        ImageExtensions.ApplyColor(bindable, platformView); // 您能够在这里找到`ApplyColor`的Android实现: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 实现:https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.android.cs#L14-L17}

这是 iOS 平台的代码:

//文件名: ImageTintColorBehavior.ios.cspublic partial class IconTintColorBehavior : PlatformBehavior<Image, UIImageView> // 留神 UIImageView 的应用,它是一个 iOS 特定的 API{    protected override void OnAttachedTo(Image bindable, UIImageView platformView) =>         ImageExtensions.ApplyColor(bindable, platformView); // 你能够在这里找到`ApplyColor`的iOS实现: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实现:https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.ios.cs#L13-L16}

正如您所看到的,咱们不须要关怀是否 Handler 为 null ,因为 PlatformBehavior<T, U> 会为咱们解决。
咱们能够指定此行为涵盖的平台特有的 API 的类型。如果您想为多个类型利用控件,则无需指定平台视图的类型(例如,应用 PlatformBehavior<T> );您可能想在多个控件中利用您的行为,在这种状况下,platformView 将是 Android 上的 PlatformBehavior<View> 和 iOS 上的 PlatformBehavior<UIView>。

而且用法更好,您只须要调用 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>

留神:当 Handler 与 VirtualView 断开连接时,即触发 Unloaded 事件时,PlatformBehavior 将调用 OnDetachedFrom。Behavior API 不会主动调用 OndetachedFrom 办法,作为开发者须要本人解决。

总结

在这篇文章中,咱们探讨了自定义控件以及与平台特有的 API 交互的各种形式。没有正确或谬误的办法,所有这些都是无效的解决方案,您只须要看看哪种办法更适宜您的状况。我想说的是,在大多数状况下,您会想要应用 PlatformBehavior,因为它旨在应用多指标办法并确保在控件不再应用时清理资源。要理解更多信息,请查看无关自定义控件的文档。


长按辨认二维码
关注微软中国MSDN

点击理解更多~