第二十三章:触发器和行为(九)

简介: 切换和复选框在第15章“交互式界面”和第16章“数据绑定”中,您了解了如何构造传统的CheckBox视图。 但是,自定义视图的另一种方法是将视图的交互逻辑合并到行为中,然后完全在XAML中实现视觉效果。

切换和复选框
在第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,然后在一秒钟内淡出它以表示事件触发的感觉:
2019_04_09_102038
如果您喜欢在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="&#x2610;"
                       FontSize="Large">
                    <Label.Triggers>
                        <DataTrigger TargetType="Label"
                                    Binding="{Binding Source={x:Reference checkbox},
                                                      Path=IsToggled}"
                                    Value="True">
                            <Setter Property="Text" Value="&#x2611;" />
                        </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);
    }
}

这是结果:
2019_04_09_104603
如果需要特定类型的切换视图的多个实例,则可以将视觉效果封装在从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>

他们在这里:
2019_04_09_110336
当然,一旦你开始考虑使用动画,你可能会开始得到一些关于切换视图可能看起来有趣的(或者可能是完全奇怪的)想法。 为了给你提供更多选项,这里是一个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屏幕显示切换状态:
2019_04_09_110619

目录
相关文章
|
JavaScript Android开发
第二十三章:触发器和行为(十二)
淡化和定向在本书中,您已经看到了几个颜色选择程序,可以通过使用三个Slider元素以交互方式形成颜色。 本章的最后一个示例是另一个颜色选择程序,但是这个程序为您提供了选项:它包含三个标记为“RGB Hex”,“RGB Float”和“HSL”的单选按钮(实际上是简单的Label元素)。
710 0
|
JavaScript Android开发
第二十三章:触发器和行为(七)
行为 触发器和行为通常是串联讨论的,因为它们具有一些应用重叠。 有时候你会感到困惑是否使用触发器或行为,因为似乎要么这样做工作。你可以用触发器做任何事情,你也可以做一个行为。 但是,行为总是涉及一些代码,这是一个派生自Behavior 的类。
952 0
|
JavaScript Android开发
第二十三章:触发器和行为(十一)
单选按钮内置于旧汽车仪表板中的无线电通常具有一排六个(左右)按钮,可以为各种无线电台“编程”。 按下其中一个按钮会导致无线电跳转到该预选电台,并且还会弹出前一个选择按钮。那些旧的汽车收音机现在是古董,但我们的电脑屏幕上的互斥选项仍然由我们称为单选按钮的视觉对象表示。
913 0
|
JavaScript Android开发
第二十三章:触发器和行为(十)
响应水龙头切换视图的各种表现形式演示了一种响应XAML文件中的点击的方法。 如果将tap事件集成到VisualElement类中,您可以使用EventTrigger更直接且更轻松地获取它们。 但是您无法将EventTrigger附加到TapGestureRecognizer。
546 0
|
Android开发
第二十三章:触发器和行为(八)
具有属性的行为Behavior 类派生自Behavior类,该类派生自BindableObject。这表明您的Behavior 派生可以定义自己的可绑定属性。之前你看过一些Action 衍生产品,比如ScaleAction和ShiverAction,它们定义了一些属性以赋予它们更大的灵活性。
651 0
|
JavaScript Android开发
第二十三章:触发器和行为(六)
MultiTrigger中的组合条件Trigger和DataTrigger都有效地监视属性以确定它是否等于特定值。 这称为触发器的条件,如果条件为真,则调用Setter对象的集合。作为程序员,您可能会开始怀疑是否可以在触发器中具有多个条件。
657 0
|
JavaScript Android开发 iOS开发
第二十三章:触发器和行为(五)
数据触发器到目前为止,您只看到在特定对象的上下文中运行的触发器。 触发器通过更改同一对象的另一个属性或通过调用影响该对象的Action来响应对象属性的更改。 EventTrigger同样响应一个对象触发的事件,以在同一个对象上调用Action。
943 0
|
JavaScript Android开发 Windows
第二十三章:触发器和行为(四)
更多事件触发器前一章关于动画的章节展示了一个按钮,它在点击时旋转或缩放。 虽然大多数动画示例都是为了制作有趣的演示而采取极端措施,但是按钮用一点动画来响应点击并不是不合理的。 这是EventTrigger的完美工作。
782 0
|
JavaScript Android开发
第二十三章:触发器和行为(三)
触发动作和动画虽然某些触发器可以完全在XAML中实现,但其他触发器需要一些代码支持。 如您所知,Xamarin.Forms没有直接支持在XAML中实现动画,因此如果您想使用触发器为元素设置动画,则需要一些代码。
1028 0
|
JavaScript Android开发
第二十三章:触发器和行为(二)
触发器 在最普遍(和最模糊)的意义上,触发器是导致响应的条件。 更具体地说,触发器通过设置另一个属性或运行一些代码来响应属性更改或触发事件。 几乎总是,设置的属性或运行的代码涉及用户界面,并在XAML中表示。
983 0
下一篇
DataWorks