渲染器和属性(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的构造函数中设置。 如果没有该设置,视图在椭圆未覆盖的区域中具有黑色背景。