写在前面:
本文借鉴于Xamarin Forms Triggers vs Behaviors vs Effects(By Adam Pedley)。刚刚举办完成的微软技术暨生态大会,美国的微软高级副总裁潘女士介绍了James公司的客户案例,国内如果有需要Xamarin和Azure方向的咨询服务可以联系James Zhou。
在Xamarin.Forms开发中,如果你想针对Forms提供的系统控件进行小型样式更改或复杂的平台特定布局和行为定制,例如无边框输入框、自定义ListView单元格样式等等,你可能需要使用Renderer去各平台实现相应功能。Renderer是Xamarin.Forms分平台渲染的一种很好的方式,并且可以自定义相应的属性和方法以实现业务逻辑需求,但是如果你在项目中采用太多Renderer的话,它可能会很快使你的项目代码变得非常笨重。
其实Xamarin.Forms给我们提供了许多方法可以根据特定的标准,数据或事件修改UI,除了Renderer以外,Triggers,Behaviors,Effects也可以实现相应的功能,但是对于不同场景而言,采用哪种方式往往会令大家头疼,今天主要给大家介绍一下三种的区别。
Triggers
Triggers翻译为中文叫触发器,其中包含Property Triggers、Data Triggers、Event Triggers、Multi Triggers四种,而Triggers的基本原理就是可以被一个事件或属性触发,之后可以更改该控件的属性或者调用一段代码以响应触发器。
例如:当Entry获取焦点时改变Entry背景颜色
public class BackgroundColorTrigger : TriggerAction
{
protected override void Invoke(Entry sender)
{
sender.BackgroundColor = Color.Yellow;
}
}
然后我们可以它添加到Entry上并定义Trigger的触发。
// Add to Page Attributes (Above Trigger is in Namespace Mobile.Trigger)
xmlns:trigger="clr-namespace:Mobile.Trigger"
Behaviors
Behaviors允许你在不需要自定义控件的情况下添加功能。相反,功能是在自定义Behaviors类中实现的,并附加到控件上,就像它是控件本身的一部分一样,而且其复用性强。
例如:在实际开发中经常会用到的EventToCommandBehavior
public class EventToCommandBehavior : BehaviorBase
{
Delegate eventHandler;
public static readonly BindableProperty EventNameProperty = BindableProperty.Create ("EventName", typeof(string), typeof(EventToCommandBehavior), null, propertyChanged: OnEventNameChanged);
public static readonly BindableProperty CommandProperty = BindableProperty.Create ("Command", typeof(ICommand), typeof(EventToCommandBehavior), null);
public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create ("CommandParameter", typeof(object), typeof(EventToCommandBehavior), null);
public static readonly BindableProperty InputConverterProperty = BindableProperty.Create ("Converter", typeof(IValueConverter), typeof(EventToCommandBehavior), null);
public string EventName {
get { return (string)GetValue (EventNameProperty); }
set { SetValue (EventNameProperty, value); }
}
public ICommand Command {
get { return (ICommand)GetValue (CommandProperty); }
set { SetValue (CommandProperty, value); }
}
public object CommandParameter {
get { return GetValue (CommandParameterProperty); }
set { SetValue (CommandParameterProperty, value); }
}
public IValueConverter Converter {
get { return (IValueConverter)GetValue (InputConverterProperty); }
set { SetValue (InputConverterProperty, value); }
}
protected override void OnAttachedTo (View bindable)
{
base.OnAttachedTo (bindable);
RegisterEvent (EventName);
}
protected override void OnDetachingFrom (View bindable)
{
DeregisterEvent (EventName);
base.OnDetachingFrom (bindable);
}
void RegisterEvent (string name)
{
if (string.IsNullOrWhiteSpace (name)) {
return;
}
EventInfo eventInfo = AssociatedObject.GetType ().GetRuntimeEvent (name);
if (eventInfo == null) {
throw new ArgumentException (string.Format ("EventToCommandBehavior: Can't register the '{0}' event.", EventName));
}
MethodInfo methodInfo = typeof(EventToCommandBehavior).GetTypeInfo ().GetDeclaredMethod ("OnEvent");
eventHandler = methodInfo.CreateDelegate (eventInfo.EventHandlerType, this);
eventInfo.AddEventHandler (AssociatedObject, eventHandler);
}
void DeregisterEvent (string name)
{
if (string.IsNullOrWhiteSpace (name)) {
return;
}
if (eventHandler == null) {
return;
}
EventInfo eventInfo = AssociatedObject.GetType ().GetRuntimeEvent (name);
if (eventInfo == null) {
throw new ArgumentException (string.Format ("EventToCommandBehavior: Can't de-register the '{0}' event.", EventName));
}
eventInfo.RemoveEventHandler (AssociatedObject, eventHandler);
eventHandler = null;
}
void OnEvent (object sender, object eventArgs)
{
if (Command == null) {
return;
}
object resolvedParameter;
if (CommandParameter != null) {
resolvedParameter = CommandParameter;
} else if (Converter != null) {
resolvedParameter = Converter.Convert (eventArgs, typeof(object), null, null);
} else {
resolvedParameter = eventArgs;
}
if (Command.CanExecute (resolvedParameter)) {
Command.Execute (resolvedParameter);
}
}
static void OnEventNameChanged (BindableObject bindable, object oldValue, object newValue)
{
var behavior = (EventToCommandBehavior)bindable;
if (behavior.AssociatedObject == null) {
return;
}
string oldEventName = (string)oldValue;
string newEventName = (string)newValue;
behavior.DeregisterEvent (oldEventName);
behavior.RegisterEvent (newEventName);
}
}
然后我们将其使用在ListView上,ListView在给我们提供的Command中并没有ItemSelectedCommand,这在我们实际开发中的binding不好实现,通常我们会使用到Behavior
// Add to Page attributes
xmlns:behavior="clr-namespace:Mobile.Behavior"
Effects
Effects允许定制每个平台上的本地控件,通常用于小型样式更改。Effects跟Renderer在实现以及原理上有很大程度的相似点。Rendere通常来定制控件的UI和Event,而Effects通常用于实现Xamarin.Forms并未提供的UI样式。
例如:更改Entry的Placeholder字体颜色
//Under iOS Projects
[assembly:ResolutionGroupName ("Xamarin")]
[assembly:ExportEffect (typeof(PlaceholderEffect), "PlaceholderEffect")]
namespace Mobile.iOS
{
public class PlaceholderEffect : PlatformEffect
{
protected override void OnAttached()
{
Control.AttributedPlaceholder = new NSAttributedString(Control.Placeholder, null, UIColor.Red);
}
protected override void OnDetached()
{
}
protected override void OnElementPropertyChanged(PropertyChangedEventArgs args)
{
base.OnElementPropertyChanged(args);
// You can do effects only when certain properties change here.
}
}
}
然后我们需要在PCL层自定义一个Effect以提供给PCL层可以引用到各平台的实现。
public class PlaceholderEffect : RoutingEffect
{
public PlaceholderEffect () : base ("Xamarin.PlaceholderEffect")
{
}
}
接着我们需要在Xaml中进行添加引用
// Add this to your page attributes (change Mobile.Effects to whatever full namespace your placeholder effect is in)
xmlns:local="clr-namespace:Mobile.Effects"
到这里Composable Customizations with Xamarin.Forms的介绍就完成了,希望能对您有所帮助。