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

简介: 渲染器和属性(1)Xamarin.Forms包含一个BoxView元素,用于显示矩形颜色块。 你有没有希望你有类似的东西画一个圆圈,或使它更通用,椭圆?这就是EllipseView的目的。 但是,因为您可能希望在多个应用程序中使用EllipseView,所以它在第20章“异步和文件I / O”中介绍的Xamarin.FormsBook.Platform库中实现。

渲染器和属性(1)

Xamarin.Forms包含一个BoxView元素,用于显示矩形颜色块。 你有没有希望你有类似的东西画一个圆圈,或使它更通用,椭圆?
这就是EllipseView的目的。 但是,因为您可能希望在多个应用程序中使用EllipseView,所以它在第20章“异步和文件I / O”中介绍的Xamarin.FormsBook.Platform库中实现。
BoxView自己定义了一个属性 - Color类型的Color属性 - EllipseView也可以这样做。 它不需要属性来设置椭圆的宽度和高度,因为它从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中定义的Color属性必须与渲染器渲染的对象上的属性相关联。
EllipseView中唯一的其他代码是OnSizeRequest的覆盖,用于设置椭圆的默认大小,与BoxView相同。
让我们从Windows平台开始吧。事实证明,EllipseView的Windows渲染器比iOS和Android渲染器更简单。
您可能还记得,第20章中创建的Xamarin.FormsBook.Platform解决方案具有允许在各种Windows平台之间共享代码的工具:Xamarin.FormsBook.Platform.UWP库,Xamarin.FormsBook.Platform.Windows库,和Xamarin.FormsBook.Platform.WinPhone库都引用了Xamarin.FormsBook.Platform.WinRT库,它根本不是一个库,而是一个共享项目。这个共享项目是所有Windows平台的EllipseViewRenderer类可以驻留的位置。
在Windows平台上,EllipseView可以由Windows.UI.Xaml.Shapes命名空间中名为Ellipse的强制Windows元素呈现,因为Ellipse满足从Windows.UI.Xaml.FrameworkElement派生的条件。
Ellipse被指定为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<EllipseView, Ellipse>
    {
        protected override void OnElementChanged(ElementChangedEventArgs<EllipseView> args)
        {
            base.OnElementChanged(args);
            if (Control == null)
            {
                SetNativeControl(new Ellipse());
            }
            if (args.NewElement != null)
            {
                SetColor();
            }
        }
        __
    }
}

正如您现在所期望的那样,OnElementChanged覆盖首先检查Control属性是否为null,如果是,则创建本机对象,在本例中为Ellipse,并将其传递给SetNativeControl。 此后,Control属性设置为此Ellipse对象。
此OnElementChanged重写还包含一些涉及ElementChangedEventArgs参数的附加代码。 这需要一点解释:
每个渲染器实例在此示例中,此EllipseViewRenderer类的实例包含本机对象的单个实例,在此示例中为Ellipse。
但是,渲染基础结构具有将渲染器实例附加到Xamarin.Forms元素并将其分离并将另一个Xamarin.Forms元素附加到同一渲染器的功能。 也许Xamarin.Forms需要重新创建元素或替换另一个元素,以准备与渲染器关联的元素。
通过调用OnElementChanged将此类更改传递给渲染器。 ElementChangedEventArgs参数包括两个属性,OldElement和NewElement,两者都是ElementChangedEventArgs的泛型参数中指示的类型,在本例中为EllipseView。在许多情况下,您不必担心从单个渲染器实例附加和分离的不同Xamarin.Forms元素。但在某些情况下,您可能希望利用此机会清理或释放渲染器使用的某些资源。
在最简单和最常见的情况下,每个渲染器实例将为使用该渲染器的Xamarin.Forms视图调用OnElementChanged。您将使用对OnElementChanged的调用来创建本机元素并将其传递给SetNativeControl,如您所见。在调用SetNativeControl之后,ViewRenderer定义的Control属性是本机对象,在本例中是Ellipse。
在您调用OnElementChanged时,可能已经创建了Xamarin.Forms对象(在本例中为EllipseView),并且可能还设置了一些属性。 (换句话说,在渲染器需要显示元素时,可能会使用一些属性设置初始化该元素。)但系统的设计使其不一定如此。随后对OnElementChanged的调用可能表明已创建了EllipseView。
重要的是事件参数的NewElement属性。 如果该属性不为null(这是正常情况),则该属性是Xamarin.Forms元素,您应该将该Xamarin.Forms元素的属性设置传输到本机对象。 这是调用上面显示的SetColor方法的目的。 你很快就会看到那种方法的主体。
ViewRenderer定义了一个名为Element的属性,它将其设置为Xamarin.Forms元素,在本例中为EllipseView。 如果最近对OnElementChanged的调用包含非null的NewElement属性,则Element是同一个对象。
总之,这些是您可以在整个渲染器类中使用的两个基本属性:

  • Element - Xamarin.Forms元素,如果最近的OnElementChanged调用具有非null的NewElement属性,则该元素有效。
  • Control-本机视图,窗口小部件或控件对象,在调用SetNativeView后有效。

如您所知,Xamarin.Forms元素的属性可以更改。 例如,EllipseView的Color属性可能是动画的。 如果Color等属性由可绑定属性支持,则对该属性的任何更改都会导致触发PropertyChanged事件。
还会向渲染器通知该属性更改。 附加到渲染器的Xamarin.Forms元素中对可绑定属性的任何更改也会导致在ViewRenderer类中调用受保护的虚拟OnElementPropertyChanged方法。 在此特定示例中,对EllipseView中任何可绑定属性的任何更改(包括Color属性)都会生成对OnElementPropertyChanged的调用。 您的渲染器应覆盖该方法并检查哪个属性已更改:

namespace Xamarin.FormsBook.Platform.WinRT
{
    public class EllipseViewRenderer : ViewRenderer<EllipseView, Ellipse>
    {
        __
        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中的调用。通常情况下,在使用属性设置初始化元素后调用OnElementChanged。
但是,SetColor可以对Xamarin.Forms元素和本机控件的存在做出一些有效的假设:当从OnElementChanged调用SetColor时,已创建本机控件且NewElement为非null。这意味着Control和Element属性都是有效的。调用OnElementPropertyChanged时,Element属性也有效,因为这是刚刚更改其属性的对象。
这意味着SetColor方法可以简单地将颜色从Element(Xamarin.Forms元素)传输到Control(本机对象)。为了避免名称空间冲突,此SetColor方法完全限定对名为Color的任何结构的所有引用:

namespace Xamarin.FormsBook.Platform.WinRT
{
    public class EllipseViewRenderer : ViewRenderer<EllipseView, Ellipse>
    {

        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对象具有名为Fill的属性Brush属性。 默认情况下,此属性为null,如果EllipseView的Color属性为Color.Default,则SetColor方法将其设置为null。 否则,必须将Xamarin.Forms Color转换为Windows Color,然后将其传递给SolidColorBrush构造函数。 SolidColorBrush对象设置为Ellipse的Fill属性。
这是Windows版本,但是当需要为EllipseView创建iOS和Android渲染器时,您可能会感到有些不安。 这里再次是ViewRenderer的第二个泛型参数的约束:

  • iOS:TNativeView受限于UIKit.UIView
  • Android:TNativeView仅限于Android.View.Views
  • Windows:TNativeElement仅限于Windows.UI.Xaml.FrameworkElement

这意味着要为iOS制作EllipseView渲染器,您需要一个显示椭圆的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())
            {
                //Create ellipse geometry based on rect field.
                CGPath path = new CGPath();
                path.AddEllipseInRect(rect);
                path.CloseSubpath();
                //Add geometry to graphics context and draw it.
                color.SetFill();
                graphics.AddPath(path);
                graphics.DrawPath(CGPathDrawingMode.Fill);
            }
        }
        public void SetColor(UIColor color)
        {
            this.color = color;
            SetNeedsDisplay();
        }
    }
}

该类重写OnDraw方法以创建椭圆的图形路径,然后在图形上下文中绘制它。 它使用的颜色存储为一个字段,最初设置为UIColor.Clear,它是透明的。 但是,您会注意到底部的SetColor方法。 这为类提供了新的颜色,然后调用SetNeedsDisplay,它使绘图表面无效并生成对OnDraw的另一个调用。
另请注意,UIView的BackgroundColor在UIColor.Clear的构造函数中设置。 如果没有该设置,视图在椭圆未覆盖的区域中具有黑色背景。

目录
相关文章
|
14天前
|
iOS开发 UED
实现一个自定义的iOS动画效果
【4月更文挑战第9天】本文将详细介绍如何在iOS平台上实现一个自定义的动画效果。我们将通过使用Core Animation框架来实现这个动画效果,并展示如何在不同的场景中使用它。文章的目标是帮助读者理解如何使用Core Animation框架来创建自定义动画,并提供一个简单的示例代码。
13 1
|
3月前
|
JavaScript atlas Kotlin
深度解读dragonBones使用SpriteFrame任意换肤的实现
深度解读dragonBones使用SpriteFrame任意换肤的实现
44 0
|
9月前
|
JavaScript 定位技术
WebGis——Pixi开发vue项目之使用遮罩实现图形缓慢填充颜色(三)
WebGis——Pixi开发vue项目之使用遮罩实现图形缓慢填充颜色(三)
|
移动开发 开发工具 git
关于ReactNative0.56版本Flatlist列表内容跳动的问题
关于ReactNative0.56版本Flatlist列表内容跳动的问题
145 0
关于ReactNative0.56版本Flatlist列表内容跳动的问题
SwiftUI—借助sizeCategory预览不同字体下的文本视图
SwiftUI—借助sizeCategory预览不同字体下的文本视图
120 0
SwiftUI—借助sizeCategory预览不同字体下的文本视图
SwiftUI—方便用户选择项目的Picker拾取器
SwiftUI—方便用户选择项目的Picker拾取器
389 0
SwiftUI—方便用户选择项目的Picker拾取器
SwiftUI直通车系列(5)—— 自定义绘制
SwiftUI直通车系列(5)—— 自定义绘制
127 0
SwiftUI直通车系列(5)—— 自定义绘制
SwiftUI直通车系列(5)—— 自定义绘制(二)
SwiftUI直通车系列(5)—— 自定义绘制
133 0
SwiftUI直通车系列(5)—— 自定义绘制(二)
第二十七章:自定义渲染器(六)
有趣的是,Android SeekBar小部件具有与Steps属性等效的功能,但不等同于Minimum和Maximum属性! 这怎么可能? SeekBar实际上定义了一个名为Max的整数属性,SeekBar的Progress属性始终是一个从0到Max的整数。
734 0
|
Windows
第二十七章:自定义渲染器(五)
渲染器和事件(1) 大多数Xamarin.Forms元素都是交互式的。他们通过触发事件来响应用户输入。如果在Xamarin.Forms自定义元素中实现事件,则可能还需要在呈现器中为本机控件触发的相应事件定义事件处理程序。
687 0