自定义渲染器开发指南:核心技巧与实战案例

2026-06-16阅读 0热度 0
其他

自定义渲染器:EllipseView 属性详解(上)

Xamarin.Forms 中的 BoxView 专用于绘制矩形色块,若要绘制圆形或更通用的椭圆,则需借助 EllipseView。为提升复用性,本书将 EllipseView 纳入第 20 章“异步与文件 I/O”介绍的 Xamarin.FormsBook.Platform 库中。

EllipseView 的设计思路与 BoxView 类似:继承 Color 属性,无需额外定义宽高,直接复用 VisualElement 的 WidthRequest 与 HeightRequest。

Xamarin.FormsBook.Platform 库中的 EllipseView 实现如下:

第二十七章:自定义渲染器(三)

namespace Xamarin.FormsBook.Platform
{
    public class EllipseView : View
    {
        public static readonly BindableProperty ColorProperty =
            BindableProperty.Create("Color", typeof(Color), typeof(EllipseView), Color.Default);

        public Color Color
        {
            set { SetValue(ColorProperty, value); }
            get { return (Color)GetValue(ColorProperty); }
        }

        protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint)
        {
            return new SizeRequest(new Size(40, 40));
        }
    }
}

此处 Color 属性仅声明可绑定属性,未附加 PropertyChanged 处理程序。看似定义后无实际动作,但该属性最终需与渲染器中的原生对象绑定。此外,EllipseView 重写 OnSizeRequest,设置默认尺寸为 40×40,与 BoxView 保持一致。

先从 Windows 平台切入。EllipseView 的 Windows 渲染器比 iOS 和 Android 版本更简洁。第 20 章创建的 Xamarin.FormsBook.Platform 解决方案包含共享项目 Xamarin.FormsBook.Platform.WinRT,被 UWP、Windows 和 WinPhone 库引用,EllipseViewRenderer 即存放于此。

Windows 平台上,EllipseView 由 Windows.UI.Xaml.Shapes 命名空间的 Ellipse 类呈现——此类派生自 Windows.UI.Xaml.FrameworkElement,符合 ViewRenderer 第二个泛型参数的要求。由于该文件需在所有 Windows 平台共享,需通过预处理指令处理 ExportRendererAttribute 和 ViewRenderer 类的命名空间:

using System.ComponentModel;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Shapes;

#if WINDOWS_UWP
using Xamarin.Forms.Platform.UWP;
#else
using Xamarin.Forms.Platform.WinRT;
#endif

[assembly: ExportRenderer(typeof(Xamarin.FormsBook.Platform.EllipseView), 
    typeof(Xamarin.FormsBook.Platform.WinRT.EllipseViewRenderer))]

namespace Xamarin.FormsBook.Platform.WinRT
{
    public class EllipseViewRenderer : ViewRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs args)
        {
            base.OnElementChanged(args);

            if (Control == null)
            {
                SetNativeControl(new Ellipse());
            }

            if (args.NewElement != null)
            {
                SetColor();
            }
        }
        __
    }
}

OnElementChanged 重写方法中,首先检查 Control 属性是否为空,若为空则创建原生 Ellipse 对象并调用 SetNativeControl。此后,Control 属性即指向该 Ellipse。此外,代码段处理了 ElementChangedEventArgs 参数,需要说明其用途:

每个渲染器实例(如 EllipseViewRenderer)持有一个原生对象(Ellipse)。渲染基础架构允许将渲染器实例从一个 Xamarin.Forms 元素剥离并附加到另一个元素上,用于重建或替换元素。此类变更通过 OnElementChanged 传递到渲染器。ElementChangedEventArgs 提供 OldElement 和 NewElement 两个属性(均为 EllipseView)。多数情况下无需关注元素切换,但可利用此机会释放资源。

在典型场景中,每个渲染器实例仅被调用一次 OnElementChanged——对应于绑定的 Xamarin.Forms 视图。此时创建原生元素并调用 SetNativeControl 即可。之后,ViewRenderer 定义的 Control 属性即为原生 Ellipse 对象。

OnElementChanged 被调用时,Xamarin.Forms 对象(EllipseView)可能已创建并设置部分属性。即渲染器需显示元素时,元素可能已携带初始化值。但系统不保证这一点——后续 OnElementChanged 调用可能对应新的 EllipseView。

关键在事件参数的 NewElement 属性。若不为 null(通常如此),则此属性即为当前 Xamarin.Forms 元素,需将其属性同步至原生对象。这正是 SetColor 方法的用途,下文详述其实现。

ViewRenderer 还定义 Element 属性指向 Xamarin.Forms 元素(EllipseView)。若最近一次 OnElementChanged 调用包含非 null 的 NewElement,则 Element 与 NewElement 指向同一对象。

综上,在整个渲染器类中,可依赖以下两个基本属性:

Element —— 当前 Xamarin.Forms 元素,仅在最近 OnElementChanged 调用中 NewElement 非 null 时有效。
Control —— 原生视图、控件或对象,调用 SetNativeControl 后即生效。

Xamarin.Forms 元素的属性可动态变化。例如,EllipseView 的 Color 属性可通过动画驱动。若 Color 由可绑定属性支持,任何变更都会触发 PropertyChanged 事件。

此变更会通知渲染器。渲染器上附加的 Xamarin.Forms 元素中,任何可绑定属性变化都会导致 ViewRenderer 调用受保护的虚方法 OnElementPropertyChanged。本例中,EllipseView 内任何可绑定属性变化(包括 Color)都会触发该方法。渲染器需重写 OnElementPropertyChanged,并检查具体属性:

namespace Xamarin.FormsBook.Platform.WinRT
{
    public class EllipseViewRenderer : ViewRenderer
    {
        __
        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs args)
        {
            base.OnElementPropertyChanged(sender, args);

            if (args.PropertyName == EllipseView.ColorProperty.PropertyName)
            {
                SetColor();
            }
        }
        __
    }
}

若 Color 属性变更,事件参数 PropertyName 为“Color”——即创建 EllipseView.ColorProperty 可绑定属性时指定的字符串。为避免拼写错误,OnElementPropertyChanged 通过可绑定属性获取实际字符串,然后将新 Color 值同步至原生对象(Windows Ellipse)。

SetColor 方法仅在 OnElementChanged 和 OnElementPropertyChanged 两处调用。切勿因认为 OnElementChanged 之前属性不会变化而跳过该调用。通常情况下,元素初始化属性后才调用 OnElementChanged。

SetColor 假设 Xamarin.Forms 元素和原生控件均已就绪:从 OnElementChanged 调用时,原生控件已创建,NewElement 不为 null,Control 和 Element 均有效。从 OnElementPropertyChanged 调用时,Element 同样有效(正是该元素刚修改了属性)。

因此,SetColor 可简洁地将颜色从 Element(Xamarin.Forms 元素)传递至 Control(原生对象)。为避免命名冲突,所有名为 Color 的结构均使用完全限定名称:

namespace Xamarin.FormsBook.Platform.WinRT
{
    public class EllipseViewRenderer : ViewRenderer
    {
        void SetColor()
        {
            if (Element.Color == Xamarin.Forms.Color.Default)
            {
                Control.Fill = null;
            }
            else
            {
                Xamarin.Forms.Color color = Element.Color;
                global::Windows.UI.Color winColor =
                    global::Windows.UI.Color.FromArgb((byte)(color.A * 255),
                                                      (byte)(color.R * 255),
                                                      (byte)(color.G * 255),
                                                      (byte)(color.B * 255));
                Control.Fill = new SolidColorBrush(winColor);
            }
        }
    }
}

Windows 的 Ellipse 对象拥有 Brush 类型的 Fill 属性,默认值为 null。若 EllipseView 的 Color 属性为 Color.Default,SetColor 则设置 Fill 为 null。否则,需将 Xamarin.Forms 的 Color 转换为 Windows 的 Color,传递给 SolidColorBrush 构造函数,再赋值给 Ellipse 的 Fill 属性。

Windows 版本至此完成。但为 EllipseView 创建 iOS 和 Android 渲染器时,会面临更多挑战。回顾 ViewRenderer 第二个泛型参数的约束:

  • iOS:TNativeView 必须继承自 UIKit.UIView
  • Android:TNativeView 必须继承自 Android.View.Views
  • Windows:TNativeElement 必须继承自 Windows.UI.Xaml.FrameworkElement

针对 iOS,需要绘制椭圆的 UIView 派生类。标准库中并无现成类,需手动创建。这正是 iOS 渲染器制作的第一步。因此,Xamarin.FormsBook.Platform.iOS 库中定义了 EllipseUIView 类,继承自 UIView,专用于绘制椭圆:

using CoreGraphics;
using UIKit;

namespace Xamarin.FormsBook.Platform.iOS
{
    public class EllipseUIView : UIView
    {
        UIColor color = UIColor.Clear;

        public EllipseUIView()
        {
            BackgroundColor = UIColor.Clear;
        }

        public override void Draw(CGRect rect)
        {
            base.Draw(rect);

            using (CGContext graphics = UIGraphics.GetCurrentContext())
            {
                // 根据矩形区域创建椭圆几何路径
                CGPath path = new CGPath();
                path.AddEllipseInRect(rect);
                path.CloseSubpath();

                // 将路径添加到图形上下文并绘制
                color.SetFill();
                graphics.AddPath(path);
                graphics.DrawPath(CGPathDrawingMode.Fill);
            }
        }

        public void SetColor(UIColor color)
        {
            this.color = color;
            SetNeedsDisplay();
        }
    }
}

该类重写 Draw 方法,创建椭圆路径后通过图形上下文的 Fill 模式绘制。颜色存储于字段中,初始为 UIColor.Clear(透明)。底部的 SetColor 方法设置新颜色并调用 SetNeedsDisplay——该方法使绘图表面失效,从而触发 Draw 再次执行。此外,构造函数将 UIView 的 BackgroundColor 设为 UIColor.Clear,避免椭圆未覆盖区域显示黑色背景。

免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

相关阅读

更多
欢迎回来 登录或注册后,可保存提示词和历史记录
登录后可同步收藏、历史记录和常用模板
注册即表示同意服务条款与隐私政策