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

渲染器和屬性(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的構造函數中設置。 如果沒有該設置,視圖在橢圓未覆蓋的區域中具有黑色背景。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章