切换和复选框
在第15章“交互式界面”和第16章“数据绑定”中,您了解了如何构造传统的CheckBox视图。 但是,自定义视图的另一种方法是将视图的交互逻辑合并到行为中,然后完全在XAML中实现视觉效果。 这种方法使您可以灵活地使用标记而不是代码自定义视觉效果。 由于视觉外观不是底层逻辑的一部分,因此您可以在使用该行为时创建临时视觉效果。
这是一个名为ToggleBehavior的Xamarin.FormsBook.Toolkit库中的类。 与Xamarin.Forms Switch元素一样,它定义了一个名为IsToggled的属性,该属性由可绑定属性支持。 ToggleBehavior只是将TapGestureRecognizer安装到它附加到的视觉上,并在检测到点击时切换IsToggled属性的状态:
namespace Xamarin.FormsBook.Toolkit
{
public class ToggleBehavior : Behavior<View>
{
TapGestureRecognizer tapRecognizer;
public static readonly BindableProperty IsToggledProperty =
BindableProperty.Create<ToggleBehavior, bool>(tb => tb.IsToggled, false);
public bool IsToggled
{
set { SetValue(IsToggledProperty, value); }
get { return (bool)GetValue(IsToggledProperty); }
}
protected override void OnAttachedTo(View view)
{
base.OnAttachedTo(view);
tapRecognizer = new TapGestureRecognizer ();
tapRecognizer.Tapped += OnTapped;
view.GestureRecognizers.Add(tapRecognizer);
}
protected override void OnDetachingFrom(View view)
{
base.OnDetachingFrom(view);
view.GestureRecognizers.Remove(tapRecognizer);
tapRecognizer.Tapped -= OnTapped;
}
void OnTapped(object sender, EventArgs args)
{
IsToggled = !IsToggled;
}
}
}
ToggleBehavior类定义了一个属性,这意味着您无法在Style中共享ToggleBehavior。
这是一个简单的应用程序。 ToggleLabel程序将ToggleBehavior附加到Label,并将IsToggled属性与DataTrigger一起使用,以便在“Paused”和“Playing”之间切换Label的文本,可能用于音乐应用程序:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit=
"clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
x:Class="ToggleLabel.ToggleLabelPage">
<Label Text="Paused"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="Center">
<Label.Behaviors>
<toolkit:ToggleBehavior x:Name="toggleBehavior" />
</Label.Behaviors>
<Label.Triggers>
<DataTrigger TargetType="Label"
Binding="{Binding Source={x:Reference toggleBehavior},
Path=IsToggled}"
Value="True">
<Setter Property="Text" Value="Playing" />
</DataTrigger>
</Label.Triggers>
</Label>
</ContentPage>
当然,这样的程序可能需要在切换Label时运行一些代码。 请记住,Behavior派生自BindableObject,这意味着您在行为中定义的任何BindableProperty都会在属性更改时自动生成PropertyChanged事件。
这意味着您可以将处理程序附加到ToggleBehavior的PropertyChanged事件,并检查IsToggled属性中的更改。 这在FormattedTextToggle程序中得到了证明,该程序将ToggleLabel程序扩展为包含一个Frame和一些格式化的文本,这些文本更清楚地表明了tap之间切换的两个选项:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit=
"clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
x:Class="FormattedTextToggle.FormattedTextTogglePage">
<StackLayout>
<Frame HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
OutlineColor="Accent"
BackgroundColor="Transparent">
<Frame.Behaviors>
<toolkit:ToggleBehavior x:Name="toggleBehavior"
PropertyChanged="OnBehaviorPropertyChanged" />
</Frame.Behaviors>
<Label>
<Label.FormattedText>
<FormattedString>
<FormattedString.Spans>
<Span Text="Paused / "
FontSize="Large"
FontAttributes="Bold" />
<Span Text="Playing"
FontSize="Small" />
</FormattedString.Spans>
</FormattedString>
</Label.FormattedText>
<Label.Triggers>
<DataTrigger TargetType="Label"
Binding="{Binding Source={x:Reference toggleBehavior},
Path=IsToggled}"
Value="True">
<Setter Property="FormattedText">
<Setter.Value>
<FormattedString>
<FormattedString.Spans>
<Span Text="Paused"
FontSize="Small" />
<Span Text=" / Playing"
FontSize="Large"
FontAttributes="Bold" />
</FormattedString.Spans>
</FormattedString>
</Setter.Value>
</Setter>
</DataTrigger>
</Label.Triggers>
</Label>
</Frame>
<Label x:Name="eventLabel"
Text=""
FontSize="Large"
Opacity="0"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
ToggleBehavior附加到Frame,Frame包含Label。 (请注意,Frame的BackgroundColor设置为Transparent而不是默认值null。这是在Windows运行时平台上捕获tap事件所必需的。)
该程序演示了一种解决切换按钮常见问题的方法:文本(或图标)是指状态还是动作? 此处的标签通过显示文本“暂停/播放”但使用“暂停”一词大于“播放”一词来清楚显示。 当IsToggled属性为True时,DataTrigger会更改该显示,以使“播放”一词大于“已暂停”一词。
ToggleBehavior上的PropertyChanged事件在代码隐藏文件中处理:
public partial class FormattedTextTogglePage : ContentPage
{
public FormattedTextTogglePage()
{
InitializeComponent();
}
void OnBehaviorPropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (args.PropertyName == "IsToggled")
{
eventLabel.Text = "IsToggled = " + ((ToggleBehavior)sender).IsToggled;
eventLabel.Opacity = 1;
eventLabel.FadeTo(0, 1000);
}
}
}
OnBehaviorPropertyChanged处理程序检查名为“IsToggled”的属性的更改。 请记住,事件处理程序的sender参数不是检测其抽头的可视元素(即Frame),而是ToggleBehavior本身。 代码在页面底部设置Label的Text属性,并将Opacity设置为1,然后在一秒钟内淡出它以表示事件触发的感觉:
如果您喜欢在XAML中定义切换视图的视觉效果但更喜欢更多结构的想法,则Xamarin.FormsBook.Toolkit库有一个名为ToggleBase的类,它派生自ContentView并包含ToggleBehavior。 构造函数将ToggleBehavior添加到类的Behav iors集合中,然后为其附加事件处理程序。 该类还定义了一个Toggled事件及其自己的IsToggled属性,该属性触发该事件:
namespace Xamarin.FormsBook.Toolkit
{
public class ToggleBase : ContentView
{
public event EventHandler<ToggledEventArgs> Toggled;
public static readonly BindableProperty IsToggledProperty =
BindableProperty.Create("IsToggled", typeof(bool), typeof(ToggleBase), false,
BindingMode.TwoWay,
propertyChanged: (bindable, oldValue, newValue) =>
{
EventHandler<ToggledEventArgs> handler = ((ToggleBase)bindable).Toggled;
if (handler != null)
handler(bindable, new ToggledEventArgs((bool)newValue));
});
public ToggleBase()
{
ToggleBehavior toggleBehavior = new ToggleBehavior();
toggleBehavior.PropertyChanged += OnToggleBehaviorPropertyChanged;
Behaviors.Add(toggleBehavior);
}
public bool IsToggled
{
set { SetValue(IsToggledProperty, value); }
get { return (bool)GetValue(IsToggledProperty); }
}
protected void OnToggleBehaviorPropertyChanged(object sender,
PropertyChangedEventArgs args)
{
if (args.PropertyName == "IsToggled")
{
IsToggled = ((ToggleBehavior)sender).IsToggled;
}
}
}
}
ToggleBase类定义了没有视觉效果的切换视图的所有逻辑。 实际上,它不需要ToggleBehaviors类。 它可以安装自己的TapGestureRecognizer,但结果基本相同。
您可以在XAML文件中实例化ToggleBase类,并将视觉效果作为ToggleBase的内容提供。 这是一个名为TraditionalCheckBox的程序,它使用两个Unicode字符作为未选中框和一个复选框,类似于第15章和第16章中的CheckBox视图:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit=
"clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
x:Class="TraditionalCheckBox.TraditionalCheckBoxPage">
<StackLayout>
<toolkit:ToggleBase x:Name="checkbox"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Toggled="OnToggleBaseToggled">
<StackLayout Orientation="Horizontal">
<Label Text="☐"
FontSize="Large">
<Label.Triggers>
<DataTrigger TargetType="Label"
Binding="{Binding Source={x:Reference checkbox},
Path=IsToggled}"
Value="True">
<Setter Property="Text" Value="☑" />
</DataTrigger>
</Label.Triggers>
</Label>
<Label Text="Italicize Text"
FontSize="Large" />
</StackLayout>
</toolkit:ToggleBase>
<Label Text="Sample text to italicize"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
<Label.Triggers>
<DataTrigger TargetType="Label"
Binding="{Binding Source={x:Reference checkbox},
Path=IsToggled}"
Value="True">
<Setter Property="FontAttributes" Value="Italic" />
</DataTrigger>
</Label.Triggers>
</Label>
<Label x:Name="eventLabel"
Text=""
FontSize="Large"
Opacity="0"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
XAML文件使用IsToggled属性作为两个非常相似的数据绑定的源,每个数据绑定都在DataTrigger中。 在这两种情况下,Source属性都设置为ToggleBase实例,Path属性设置为ToggleBase的IsToggled属性。 第一个DataTrigger在空框和复选框之间切换以指示切换的状态,第二个DataTrigger在切换CheckBox时将某些文本设置为斜体。
此外,ToggleBase的Toggled事件在代码隐藏文件中使用淡出标签处理:
public partial class TraditionalCheckBoxPage : ContentPage
{
public TraditionalCheckBoxPage()
{
InitializeComponent();
}
void OnToggleBaseToggled(object sender, ToggledEventArgs args)
{
eventLabel.Text = "IsToggled = " + args.Value;
eventLabel.Opacity = 1;
eventLabel.FadeTo(0, 1000);
}
}
这是结果:
如果需要特定类型的切换视图的多个实例,则可以将视觉效果封装在从ToggleBase派生的类中。
下一个示例派生自ToggleBase,以创建一个非常类似于Xamarin.Forms Switch的视图,除了完全在XAML中创建的视觉效果。 这个“开关克隆”是通过一个在Frame中来回移动的小BoxView实现的。 为了实现动画,Xamarin.FormsBook.Toolkit库包含一个TranslateAction类,其属性为调用TranslateTo提供参数:
namespace Xamarin.FormsBook.Toolkit
{
public class TranslateAction : TriggerAction<VisualElement>
{
public TranslateAction()
{
// Set defaults.
Length = 250;
Easing = Easing.Linear;
}
public double X { set; get; }
public double Y { set; get; }
public int Length { set; get; }
[TypeConverter(typeof(EasingConverter))]
public Easing Easing { set; get; }
protected override void Invoke(VisualElement visual)
{
visual.TranslateXYTo(X, Y, (uint)Length, Easing);
}
}
}
模仿Switch的SwitchClone类是SwitchCloneDemo项目的一部分。 它完全在XAML中完成。 根元素是ToggleBase的基类,x:Class属性表示SwitchClone的派生类。 资源字典定义了几个常量,这些常量允许视觉效果不是太大,但仍然足够大,可以成为正确的触摸目标:
<toolkit:ToggleBase
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit=
"clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
x:Class="SwitchCloneDemo.SwitchClone"
x:Name="toggle">
<toolkit:ToggleBase.Resources>
<ResourceDictionary>
<x:Double x:Key="height">20</x:Double>
<x:Double x:Key="width">50</x:Double>
<x:Double x:Key="halfWidth">25</x:Double>
</ResourceDictionary>
</toolkit:ToggleBase.Resources>
<Frame Padding="2"
OutlineColor="Accent"
BackgroundColor="Transparent">
<AbsoluteLayout WidthRequest="{StaticResource width}">
<BoxView Color="Accent"
WidthRequest="{StaticResource halfWidth}"
HeightRequest="{StaticResource height}">
<BoxView.Triggers>
<DataTrigger TargetType="BoxView"
Binding="{Binding Source={x:Reference toggle},
Path=IsToggled}"
Value="True">
<DataTrigger.EnterActions>
<toolkit:TranslateAction X="{StaticResource halfWidth}"
Length="100" />
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<toolkit:TranslateAction Length="100" />
</DataTrigger.ExitActions>
</DataTrigger>
</BoxView.Triggers>
</BoxView>
</AbsoluteLayout>
</Frame>
</toolkit:ToggleBase>
请注意,根元素的名称为“toggle”。这允许BoxView上DataTrigger中的数据绑定引用ToggleBase类定义的IsToggled属性。 DataTrigger不包含Setter,而是使用EnterActions和ExitActions调用TranslateAction来来回移动BoxView。
SwitchClone的代码隐藏文件只有一个InitializeComponent调用,但是如果你需要其他属性(例如,对于颜色或一些附带的文本),你可以在那里定义它们。
至少这是它最初编码的方式。 后来,该程序拒绝在Windows运行时平台上构建。 也许问题与引用库中的类的XAML文件中的根元素有关。 无论如何,该类的仅代码版本确实有效,这是本章示例代码中包含的版本:
class SwitchClone : ToggleBase
{
const double height = 20;
const double width = 50;
const double halfWidth = 25;
public SwitchClone()
{
BoxView boxView = new BoxView
{
Color = Color.Accent,
WidthRequest = halfWidth,
HeightRequest = height
};
DataTrigger dataTrigger = new DataTrigger(typeof(BoxView))
{
Binding = new Binding("IsToggled", source: this),
Value = true,
};
dataTrigger.EnterActions.Add(new TranslateAction
{
X = halfWidth,
Length = 100
});
dataTrigger.ExitActions.Add(new TranslateAction
{
Length = 100
});
boxView.Triggers.Add(dataTrigger);
Content = new Frame
{
Padding = 2,
OutlineColor = Color.Accent,
BackgroundColor = Color.Transparent,
Content = new AbsoluteLayout
{
WidthRequest = width,
Children =
{
boxView
}
}
};
}
}
SwitchCloneDemoPage类连续显示其中四个切换克隆:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:SwitchCloneDemo"
x:Class="SwitchCloneDemo.SwitchCloneDemoPage">
<Grid VerticalOptions="Center">
<local:SwitchClone Grid.Column="0"
HorizontalOptions="Center" />
<local:SwitchClone Grid.Column="1"
HorizontalOptions="Center" />
<local:SwitchClone Grid.Column="2"
HorizontalOptions="Center" />
<local:SwitchClone Grid.Column="3"
HorizontalOptions="Center" />
</Grid>
</ContentPage>
他们在这里:
当然,一旦你开始考虑使用动画,你可能会开始得到一些关于切换视图可能看起来有趣的(或者可能是完全奇怪的)想法。 为了给你提供更多选项,这里是一个RotateAction类:
namespace Xamarin.FormsBook.Toolkit
{
public class RotateAction : TriggerAction<VisualElement>
{
public RotateAction()
{
// Set defaults.
Anchor = new Point (0.5, 0.5);
Rotation = 0;
Length = 250;
Easing = Easing.Linear;
}
public Point Anchor { set; get; }
public double Rotation { set; get; }
public int Length { set; get; }
[TypeConverter(typeof(EasingConverter))]
public Easing Easing { set; get; }
protected override void Invoke(VisualElement visual)
{
visual.AnchorX = Anchor.X;
visual.AnchorY = Anchor.Y;
visual.RotateTo(Rotation, (uint)Length, Easing);
}
}
}
LeverToggle程序有一个XAML文件,专门用于从两个BoxView元素构造的单个切换开关。 第一个BoxView类似于第二个Box的底座,其功能类似于杠杆。 请注意,第二个BoxView上的DataTrigger包含一个用于更改BoxView颜色的Setter以及用于调用来回移动控制杆的动画的EnterActions和ExitActions:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit=
"clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
x:Class="LeverToggle.LeverTogglePage">
<toolkit:ToggleBase x:Name="toggle"
HorizontalOptions="Center"
VerticalOptions="Center">
<AbsoluteLayout>
<BoxView Color="Gray"
AbsoluteLayout.LayoutBounds="0, 75, 100, 25">
<BoxView.Triggers>
<DataTrigger TargetType="BoxView"
Binding="{Binding Source={x:Reference toggle},
Path=IsToggled}"
Value="True">
<Setter Property="Color" Value="Lime" />
</DataTrigger>
</BoxView.Triggers>
</BoxView>
<BoxView Color="Gray"
AbsoluteLayout.LayoutBounds="45, 0, 10, 100"
AnchorX="0.5"
AnchorY="1"
Rotation="-30">
<BoxView.Triggers>
<DataTrigger TargetType="BoxView"
Binding="{Binding Source={x:Reference toggle},
Path=IsToggled}"
Value="True">
<Setter Property="Color" Value="Lime" />
<DataTrigger.EnterActions>
<toolkit:RotateAction Anchor="0.5, 1" Rotation="30" />
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<toolkit:RotateAction Anchor="0.5, 1" Rotation="-30" />
</DataTrigger.ExitActions>
</DataTrigger>
</BoxView.Triggers>
</BoxView>
</AbsoluteLayout>
</toolkit:ToggleBase>
</ContentPage>
未屏蔽状态显示在Android屏幕上,而iOS和Windows 10 Mobile屏幕显示切换状态: