在 .NET MAUI 中自定义控件

内容纲要

今天,我想讨论并向您展示在 .NET MAUI 中完全自定义控件的方法。在看.NET MAUI之前,让我们回到几年前,回到Xamarin.Forms时代。当时,我们有几种方法可以自定义控件:当您不需要访问特定于平台的API来自定义控件时,可以使用行为;如果您需要访问特定于平台的API,我们就有效果

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

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

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

注意:如果您想使用它,.NET MAUI仍然支持,但不建议这样做Effects

自定义现有控件

若要向现有控件添加其他功能,请对其进行扩展并添加所需的功能。

让我们创建一个新控件,并添加一个新控件,我们将利用该控件来更改 的色调颜色。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

您可能注意到的一件事是 检查 和 。这是你在路上可能发现的第一条龙。创建并实例化控件并调用 的委托时,可以是 。因此,如果没有空值检查,代码将抛出一个 .这可能听起来像一个错误,但它实际上是一个功能!这使 .NET MAUI 工程团队能够保持控件在 Xamarin.Forms 上具有的相同生命周期,从而避免了将从 Forms 迁移到 .NET MAUI 的应用程序的一些重大更改。nullHandlerPlatformViewImagePropertyChangedBindablePropertyHandlernullNullReferenceException

现在我们已经设置好了所有内容,我们可以在.在下面的代码段中,您可以看到如何在 XAML 中使用它:ContentPage

<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>

使用附加属性和属性映射器

自定义控件的另一种方法是使用 ,这是当您不需要将其绑定到特定自定义控件时的一种风格。AttachedPropertiesBindableProperty

以下是我们为 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 上有 用于 的样板,但如您所见,我们没有委托。为了处理属性更改,我们将在 .您可以在任何级别添加映射器,因为成员是 。我选择在课堂上做这件事,正如你在下面看到的。AttachedPropertyPropertyChangedMapperImageHandlerstaticTintColorMapper

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实现,在本例中为方法。如果您不希望出现此行为,请改用 ,它将在属性更改或操作发生时触发。AppendToMappingCommandMapper

请注意,当我们使用 和 时,我们将为项目中使用该处理程序的所有控件添加此行为。在这种情况下,所有控件都将触发此代码。在某些情况下,这不是你想要的,如果你下一种方式更具体,使用将完全适合。MapperCommandMapperImagePlatformBehavior

因此,现在我们已经设置了所有内容,我们可以在页面中使用我们的控件,在下面的代码段中,您可以看到如何在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 时更轻松地自定义控件(安全,因为它确保 和 不是 )。它有两种方法:和。此 API 的存在是为了替换 Xamarin.Forms 中的 API,并利用多目标体系结构。HandlerPlatformViewnulloverrideOnAttachedToOnDetachedFromEffect

在此示例中,我们将用于实现特定于平台的 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
}

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

我们可以指定此行为涵盖的特定于平台的 API 的类型。如果要将控件应用于多个类型,则无需指定平台视图的类型(例如,使用 );您可能希望在多个控件中应用您的控件,在这种情况下,平台视图将是 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>

注意:当断开与 时,将调用 ,换句话说,当事件被触发时。API不会自动调用该方法,作为开发人员,您需要自己处理它。PlatformBehaviorOnDetachedFromHandlerVirtualViewUnloadedBehaviorOnDetachedFrom

结论

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

给TA打赏
共{{data.count}}人
人已打赏
.NET MAUI

做一个高德地图的 iOS / Android .NET MAUI 控件系列 - 创建控件

2022-7-11 19:19:23

.NET MAUI开发工具

Visual Studio 2022 正式支持 .NET MAUI 开发

2022-8-19 16:56:55

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
今日签到
有新私信 私信列表
搜索