.NET MAUI 中自定义控件

今天,我想谈谈并向您展示在.NET MAUI中完全自定义控件的方法。在查看 .NET MAUI 之前,让我们回到几年前,回到Xamarin.Forms时代。那时,我们有几种自定义控件的方法:Behaviors当您不需要访问特定于平台的 API 来自定义控件时,我们会使用这些方法;Effects如果您需要访问特定于平台的 API ,我们也有。

让我们稍微关注一下EffectsAPI。它是由于 Xamarin 缺乏多目标体系结构而创建的。这意味着我们无法在共享级别(在 .NET Standard 中csproj)访问特定于平台的代码。它工作得很好,可以让您免于创建自定义渲染器。

今天,在 .NET MAUI 中,我们可以利用多目标架构的强大功能并访问我们共享项目中特定于平台的 API。那么我们还需要Effects吗?不,因为我们可以访问我们所针对的所有平台的所有代码和 API。

因此,让我们讨论一下在 .NET MAUI 中自定义控件的所有可能性以及您可能会在途中发现的一些龙。为此,我们将自定义控件,Image添加对呈现的图像进行着色的功能。

注意: .NET MAUIEffects如果你想用的话还是支持的,但是不推荐

自定义现有控件
要向现有控件添加其他功能,我们对其进行扩展并添加我们需要的功能。

让我们创建一个新控件,并添加一个新控件,我们将利用它来更改.class ImageTintColor :

ImageBindablePropertyImage

public class ImageTintColor : Image
{
    
    
    public static readonly BindableProperty TintColorProperty =
        BindableProperty.Create(nameof(TintColor), typeof(Color), typeof(TintColorBehavior), 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(TintColorBehavior), 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)
        {
    
    
            // Workaround for when this executes the Handler and PlatformView is null
            control.HandlerChanged += OnHandlerChanged;
            return;
        }

        if (tintColor is not null)
        {
    
    
#if ANDROID
            // Note the use of Android.Widget.ImageView which is an Android-specific API
            // You can find the Android implementation of `ApplyColor` here: https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.android.cs#L9-L12
            ImageExtensions.ApplyColor((Android.Widget.ImageView)control.Handler.PlatformView, tintColor);
#elif IOS
            // Note the use of UIKit.UIImage which is an iOS-specific API
            // You can find the iOS implementation of `ApplyColor` here: 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
            // Note the use of Android.Widget.ImageView which is an Android-specific API
            // You can find the Android implementation of `ClearColor` here: https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.android.cs#L14-L17
            ImageExtensions.ClearColor((Android.Widget.ImageView)control.Handler.PlatformView);
#elif IOS
            // Note the use of UIKit.UIImage which is an iOS-specific API
            // You can find the iOS implementation of `ClearColor` here: 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.ApplyColorImageExtensions.ClearColor

您可能注意到的一件事是null检查Handlerand PlatformView。这是您在途中可能会找到的第一条龙。当Image控件被创建和实例化并被调用的PropertyChanged委托时,可以是. 因此,如果没有那个 null 检查,代码将抛出一个. 这听起来像是一个错误,但它实际上是一个功能!这允许 .NET MAUI 工程团队保持与 Xamarin.Forms 上的控件相同的生命周期,避免从 Forms 迁移到 .NET MAUI 的应用程序的一些重大更改。BindablePropertyHandlernullNullReferenceException

现在我们已经完成了所有设置,我们可以在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委托。为了处理属性更改,我们将使用Mapper. ImageHandler您可以在任何级别添加 Mapper,因为成员是static. 我选择在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
                // Note the use of Android.Widget.ImageView which is an Android-specific API
                // You can find the Android implementation of `ApplyColor` here: https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.android.cs#L9-L12
                ImageExtensions.ApplyColor((Android.Widget.ImageView)control.Handler.PlatformView, tintColor);
#elif IOS
                // Note the use of UIKit.UIImage which is an iOS-specific API
                // You can find the iOS implementation of `ApplyColor` here: https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.ios.cs#L7-L11
                ImageExtensions.ApplyColor((UIKit.UIImageView)handler.PlatformView, tintColor);
#endif
            }
            else
            {
    
    
#if ANDROID
                // Note the use of Android.Widget.ImageView which is an Android-specific API
                // You can find the Android implementation of `ClearColor` here: https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.android.cs#L14-L17
                ImageExtensions.ClearColor((Android.Widget.ImageView)handler.PlatformView);
#elif IOS
                // Note the use of UIKit.UIImage which is an iOS-specific API
                // You can find the iOS implementation of `ClearColor` here: https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.ios.cs#L13-L16
                ImageExtensions.ClearColor((UIKit.UIImageView)handler.PlatformView);
#endif
            }
        });
    }
}

代码与之前显示的几乎相同,只是使用另一个 API 实现,在本例中是AppendToMapping方法。如果您不想要这种行为,请CommandMapper改用,它只会在属性更改或操作发生时触发。

请注意,当我们使用Mapperand处理时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 时,可以更轻松地自定义控件的任务(安全,因为它确保Handlerand PlatformVieware not null)。它有两种方法来override:OnAttachedTo和OnDetachedFrom. 此 API 用于替换EffectXamarin.Forms 中的 API 并利用多目标体系结构。

在此示例中,我们将使用来实现特定于平台的 API:partial class

//FileName : ImageTintColorBehavior.cs

public partial class ImageTintColorBehavior
{
    
    
    public static readonly BindableProperty TintColorProperty =
        BindableProperty.Create(nameof(TintColor), typeof(Color), typeof(TintColorBehavior), propertyChanged: OnTintColorChanged);

    public Color? TintColor
    {
    
    
        get => (Color?)GetValue(TintColorProperty);
        set => SetValue(TintColorProperty, value);
    }
}

上面的代码将被我们所针对的所有平台编译。

现在让我们看看Android平台的代码:

//FileName: ImageTintColorBehavior.android.cs

public partial class IconTintColorBehavior : PlatformBehavior<Image, ImageView> // Note the use of ImageView which is an Android-specific API
{
    
    
    protected override void OnAttachedTo(Image bindable, ImageView platformView) =>
        ImageExtensions.ApplyColor(bindable, platformView); // You can find the Android implementation of `ApplyColor` here: https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.android.cs#L9-L12

    protected override void OnDetachedFrom(Image bindable, ImageView platformView) =>
        ImageExtensions.ClearColor(platformView); // You can find the Android implementation of `ClearColor` here: https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.android.cs#L14-L17
}

这是iOS平台的代码:

//FileName: ImageTintColorBehavior.ios.cs

public partial class IconTintColorBehavior : PlatformBehavior<Image, UIImageView> // Note the use of UIImageView which is an iOS-specific API
{
    
    
    protected override void OnAttachedTo(Image bindable, UIImageView platformView) => 
        ImageExtensions.ApplyColor(bindable, platformView); // You can find the iOS implementation of `ApplyColor` here: https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.ios.cs#L7-L11

    protected override void OnDetachedFrom(Image bindable, UIImageView platformView) => 
        ImageExtensions.ClearColor(platformView); // You can find the iOS implementation of `ClearColor` here: https://github.com/pictos/MFCC/blob/1ef490e507385e050b0cfb6e4f5d68f0cb0b2f60/MFCC/TintColorExtension.ios.cs#L13-L16
}

如您所见,我们不需要关心是否Handler为null,因为这是由 为我们处理的。PlatformBehavior<T, U>

我们可以指定此行为涵盖的特定于平台的 API 的类型。如果要对一种以上类型应用控件,则不需要指定平台视图的类型(例如 use );您可能希望在多个控件中应用您的控件,在这种情况下,platformView 将是on和on 。PlatformBehavior<T>BehaviorPlatformBehavior<View>AndroidPlatformBehavior<UIView>iOS

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

注意:将在与断开连接时PlatformBehavior调用,换句话说,当事件被触发时。API不会自动调用方法,开发者需要自己处理。OnDetachedFromHandlerVirtualViewUnloadedBehaviorOnDetachedFrom

结论
在这篇博文中,我们讨论了自定义控件以及与特定于平台的 API 交互的各种方法。没有办法right,wrong所有这些都是有效的解决方案,你只需要看看哪个更适合你的情况。我想说的是,在大多数情况下,您都想使用它,PlatformBehavior因为它旨在与多目标方法一起使用,并确保在不再使用控件时清理资源。要了解更多信息,请查看有关自定义控件的文档。

猜你喜欢

转载自blog.csdn.net/u014249305/article/details/125886769