渲染器和事件(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);
}
}
}