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

渲染器和事件(1)

大多數Xamarin.Forms元素都是交互式的。他們通過觸發事件來響應用戶輸入。如果在Xamarin.Forms自定義元素中實現事件,則可能還需要在呈現器中爲本機控件觸發的相應事件定義事件處理程序。本節將向您展示如何。
StepSlider元素的靈感來自Windows Slider元素的Xamarin.Forms實現的問題。默認情況下,Xamarin.Forms Slider在Windows上運行時
平臺從0到1只有10個步驟,因此它的值只能爲0,0.1,0.2等,最高可達1.0。
與常規Xamarin.Forms Slider一樣,StepSlider元素具有Minimum,Maximum和Value屬性,但它還定義了Step屬性以指定Minimum和Maximum之間的步數。例如,如果Minimum設置爲5,Maximum設置爲10,Step設置爲20,則Value屬性的可能值爲5.00,5.25,5.50,5.75,6.00等,最多爲10。可能的值數值等於步長值加1。
有趣的是,實現此Step屬性在所有三個平臺上都需要採用不同的方法,但本練習的主要目的是演示如何實現事件。
這是Xamarin.FormsBook.Platform庫中的StepSlider類。 請注意頂部的ValueChanged事件的定義以及Value屬性中的更改觸發該事件。 大部分可綁定屬性定義都用於validateValue方法,它們確保屬性在允許的範圍內,以及coerceValue方法,它們確保屬性在它們之間是一致的:

namespace Xamarin.FormsBook.Platform
{
    public class StepSlider : View
    {
        public event EventHandler<ValueChangedEventArgs> ValueChanged;
        public static readonly BindableProperty MinimumProperty =
            BindableProperty.Create(
                "Minimum",
                typeof(double),
                typeof(StepSlider),
                0.0,
                validateValue: (obj, min) => (double)min < ((StepSlider)obj).Maximum,
                coerceValue: (obj, min) =>
                {
                    StepSlider stepSlider = (StepSlider)obj;
                    stepSlider.Value = stepSlider.Coerce(stepSlider.Value,
                                                         (double)min,
                                                         stepSlider.Maximum);
                    return min;
                });
        public static readonly BindableProperty MaximumProperty =
            BindableProperty.Create(
                "Maximum",
                typeof(double),
                typeof(StepSlider),
                100.0,
                validateValue: (obj, max) => (double)max > ((StepSlider)obj).Minimum,
                coerceValue: (obj, max) =>
                {
                    StepSlider stepSlider = (StepSlider)obj;
                    stepSlider.Value = stepSlider.Coerce(stepSlider.Value,
                                                         stepSlider.Minimum,
                                                         (double)max);
                    return max;
                });
        public static readonly BindableProperty StepsProperty =
            BindableProperty.Create(
                "Steps",
                typeof(int),
                typeof(StepSlider),
                100,
                validateValue: (obj, steps) => (int)steps > 1);
        public static readonly BindableProperty ValueProperty =
            BindableProperty.Create(
                "Value",
                typeof(double),
                typeof(StepSlider),
                0.0,
                BindingMode.TwoWay,
                coerceValue: (obj, value) =>
                {
                    StepSlider stepSlider = (StepSlider)obj;
                    return stepSlider.Coerce((double)value,
                                             stepSlider.Minimum,
                                             stepSlider.Maximum);
                },
                propertyChanged: (obj, oldValue, newValue) =>
                {
                    StepSlider stepSlider = (StepSlider)obj;
                    EventHandler<ValueChangedEventArgs> handler = stepSlider.ValueChanged;
                    if (handler != null)
                        handler(obj, new ValueChangedEventArgs((double)oldValue,
                                                               (double)newValue));
                });
        public double Minimum
        {
            set { SetValue(MinimumProperty, value); }
            get { return (double)GetValue(MinimumProperty); }
        }
        public double Maximum
        {
            set { SetValue(MaximumProperty, value); }
            get { return (double)GetValue(MaximumProperty); }
        }
        public int Steps
        {
            set { SetValue(StepsProperty, value); }
            get { return (int)GetValue(StepsProperty); }
        }
        public double Value
        {
            set { SetValue(ValueProperty, value); }
            get { return (double)GetValue(ValueProperty); }
        }
        double Coerce(double value, double min, double max)
        {
            return Math.Max(min, Math.Min(value, max));
        }
    }
}

當Value屬性更改時,StepSlider類會觸發ValueChanged屬性,但是當用戶操作StepSlider的平臺渲染器時,此類中沒有任何內容可以更改Value屬性。 那是留給渲染器類的。
再一次,讓我們首先看看Xamarin.FormsBook.Platform.WinRT共享項目中的StepSliderRenderer的Windows實現,因爲它更直接一些。 渲染器使用Windows.UI.Xaml.Controls.Slider進行本機控制。 爲了避免Windows Slider和Xamarin.Forms Slider之間的命名空間衝突,using指令定義了win前綴以引用Windows命名空間並使用它來引用Windows Slider:

using System.ComponentModel;
using Xamarin.Forms;
using Win = Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
#if WINDOWS_UWP
using Xamarin.Forms.Platform.UWP;
#else
using Xamarin.Forms.Platform.WinRT;
#endif
[assembly: ExportRenderer(typeof(Xamarin.FormsBook.Platform.StepSlider),
                          typeof(Xamarin.FormsBook.Platform.WinRT.StepSliderRenderer))]
namespace Xamarin.FormsBook.Platform.WinRT
{
    public class StepSliderRenderer : ViewRenderer<StepSlider, Win.Slider>
    {
        protected override void OnElementChanged(ElementChangedEventArgs<StepSlider> args)
        {
            base.OnElementChanged(args);
            if (Control == null)
            {
                SetNativeControl(new Win.Slider());
            }
            if (args.NewElement != null)
            {
                SetMinimum();
                SetMaximum();
                SetSteps();
                SetValue();
                Control.ValueChanged += OnWinSliderValueChanged;
            }
            else
            {
                Control.ValueChanged -= OnWinSliderValueChanged;
            }
        }
        __
    }
}

此渲染器與您之前看到的渲染器之間的最大區別在於,此渲染器在本機Windows Slider的ValueChanged事件上設置了事件處理程序。 (您很快就會看到事件處理程序。)但是,如果args.NewElement變爲null,則表示不再有Xamarin.Forms元素附加到渲染器,並且不再需要事件處理程序。 此外,您很快就會看到事件處理程序引用從ViewRenderer類繼承的Element屬性,並且如果args.NewElement爲null,則該屬性也將爲null。
因此,當args.NewElement變爲null時,OnElementChanged將分離事件處理程序。 同樣,只要args.NewElement變爲null,就應釋放爲渲染器分配的任何資源。
OnElementPropertyChanged方法的重寫檢查StepSlider定義的四個屬性的更改:

namespace Xamarin.FormsBook.Platform.WinRT
{
    public class StepSliderRenderer : ViewRenderer<StepSlider, Win.Slider>
    {
        __
        protected override void OnElementPropertyChanged(object sender,
                                                         PropertyChangedEventArgs args)
        {
            base.OnElementPropertyChanged(sender, args);
            if (args.PropertyName == StepSlider.MinimumProperty.PropertyName)
            {
                SetMinimum();
            }
            else if (args.PropertyName == StepSlider.MaximumProperty.PropertyName)
            {
                SetMaximum();
            }
            else if (args.PropertyName == StepSlider.StepsProperty.PropertyName)
            {
                SetSteps();
            }
            else if (args.PropertyName == StepSlider.ValueProperty.PropertyName)
            {
                SetValue();
            }
        }
        __
    }
}

Windows Slider定義了Minimum,Maximum和Value屬性,就像Xamarin.Forms Slider和新的StepSlider一樣。 但它沒有定義Steps屬性。 相反,它定義了StepFrequency屬性,它與Steps屬性相反。 要重現前面的示例(最小設置爲5,最大設置爲10,步驟設置爲20),您可以將StepFrequency設置爲0.25。 轉換非常簡單:

namespace Xamarin.FormsBook.Platform.WinRT
{
    public class StepSliderRenderer : ViewRenderer<StepSlider, Win.Slider>
    {
        __
        void SetMinimum()
        {
            Control.Minimum = Element.Minimum;
        }
        void SetMaximum()
        {
            Control.Maximum = Element.Maximum;
        }
        void SetSteps()
        {
            Control.StepFrequency = (Element.Maximum - Element.Minimum) / Element.Steps;
        }
        void SetValue()
        {
            Control.Value = Element.Value;
        }
        __
     }
}

最後,這是Windows Slider的ValueChanged處理程序。 這有責任在StepSlider中設置Value屬性,然後觸發自己的ValueChanged事件。 但是,存在一種用於從渲染器設置值的特殊方法。 此方法稱爲SetValueFromRenderer,由IElementController接口定義,並由Xamarin.Forms Element類實現:

namespace Xamarin.FormsBook.Platform.WinRT
{
    public class StepSliderRenderer : ViewRenderer<StepSlider, Win.Slider>
    {
        __
        void OnControlValueChanged(object sender, RangeBaseValueChangedEventArgs args)
        {
            ((IElementController)Element).SetValueFromRenderer(StepSlider.ValueProperty,
                                                               args.NewValue);
        }
    }
}

iOS UISlider具有MinValue,MaxValue和Value屬性,並定義了ValueChanged事件,但它沒有像Steps或StepFrequency屬性那樣的東西。 相反,Xamarin.FormsBook.Platform.iOS中的iOS StepSliderRenderer類在從ValueChanged事件處理程序調用SetValueFromRenderer之前對Value屬性進行手動調整:

using System;
using System.ComponentModel;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(Xamarin.FormsBook.Platform.StepSlider),
                          typeof(Xamarin.FormsBook.Platform.iOS.StepSliderRenderer))]
namespace Xamarin.FormsBook.Platform.iOS
{
    public class StepSliderRenderer : ViewRenderer<StepSlider, UISlider>
    {
        int steps;
        protected override void OnElementChanged(ElementChangedEventArgs<StepSlider> args)
        {
            base.OnElementChanged(args);
            if (Control == null)
            {
                SetNativeControl(new UISlider());
            }
            if (args.NewElement != null)
            {
                SetMinimum();
                SetMaximum();
                SetSteps();
                SetValue();
                Control.ValueChanged += OnUISliderValueChanged;
            }
            else
            {
                Control.ValueChanged -= OnUISliderValueChanged;
            }
        }
        protected override void OnElementPropertyChanged(object sender,
                                                         PropertyChangedEventArgs args)
        {
            base.OnElementPropertyChanged(sender, args);
            if (args.PropertyName == StepSlider.MinimumProperty.PropertyName)
            {
                SetMinimum();
            }
            else if (args.PropertyName == StepSlider.MaximumProperty.PropertyName)
            {
                SetMaximum();
            }
            else if (args.PropertyName == StepSlider.StepsProperty.PropertyName)
            {
                SetSteps();
            }
            else if (args.PropertyName == StepSlider.ValueProperty.PropertyName)
            {
                SetValue();
            }
        }
        void SetMinimum()
        {
            Control.MinValue = (float)Element.Minimum;
        }
        void SetMaximum()
        {
            Control.MaxValue = (float)Element.Maximum;
        }
        void SetSteps()
        {
            steps = Element.Steps;
        }
        void SetValue()
        {
            Control.Value = (float)Element.Value;
        }
        void OnUISliderValueChanged(object sender, EventArgs args)
        {
            double increment = (Element.Maximum - Element.Minimum) / Element.Steps;
            double value = increment * Math.Round(Control.Value / increment);
            ((IElementController)Element).SetValueFromRenderer(StepSlider.ValueProperty, value);
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章