在现代的应用程序开发中,用户界面的流畅性和交互性是至关重要的。Windows Store应用中的FlipView控件因其流畅的翻页效果和直观的导航体验而受到用户的青睐。本文将介绍如何在WPF中开发一个类似的FlipView控件,以实现类似的用户体验。
尽管WPF(Windows Presentation Foundation)是.NET框架的一部分,用于构建Windows客户端应用程序,但许多开发者更倾向于使用WinRT(Windows Runtime)来开发触摸友好的应用。然而,WPF同样能够实现WinRT中的许多特性和行为,而且不需要发布到商店进行分发。因此,将WinRT中的控件移植到WPF中是一个值得考虑的选择。
在深入探讨FlipView控件的开发之前,需要对以下概念有一个基本的了解:
FlipView控件的基本结构相对简单。它包含三个容器,分别用于存储当前项、下一项和上一项。当用户点击导航按钮或执行滑动操作时,整个模板的根网格会进行动画效果,并且根据SelectedIndex更新项。
以下是FlipView控件的ControlTemplate示例:
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<Grid ClipToBounds="True" x:Name="PART_Container">
<local:FlipViewPanel x:Name="PART_Root" IsManipulationEnabled="True" Background="Transparent">
<ContentControl x:Name="PART_PreviousItem" ContentTemplate="{Binding ItemTemplate, RelativeSource={RelativeSource TemplatedParent}}" />
<ContentControl x:Name="PART_NextItem" ContentTemplate="{Binding ItemTemplate, RelativeSource={RelativeSource TemplatedParent}}" />
<ContentControl x:Name="PART_CurrentItem" ContentTemplate="{Binding ItemTemplate, RelativeSource={RelativeSource TemplatedParent}}" />
</local:FlipViewPanel>
<Grid VerticalAlignment="Center" x:Name="PART_ButtonPanel" Visibility="Collapsed">
<RepeatButton x:Name="PART_NextButton" FontFamily="Segoe UI Symbol" Content="?" FontSize="18" Style="{StaticResource NavigationButtonStyle}" Command="{x:Static local:FlipView.NextCommand}" HorizontalAlignment="Right" />
<RepeatButton x:Name="PART_PreviousButton" FontFamily="Segoe UI Symbol" Content="?" FontSize="18" Style="{StaticResource NavigationButtonStyle}" Command="{x:Static local:FlipView.PreviousCommand}" HorizontalAlignment="Left" />
</Grid>
</Grid>
</Border>
动画工厂类负责根据目标值和起始值生成动画。默认情况下,动画的目标属性设置为Translation.X值,因为动画只需要水平方向的平移。动画中会创建并添加两个EasingDoubleKeyFrame,第二个关键帧的KeyTime设置为500毫秒,以控制动画的速度。
public Storyboard GetAnimation(DependencyObject target, double to, double from)
{
Storyboard story = new Storyboard();
Storyboard.SetTargetProperty(story, new PropertyPath("(TextBlock.RenderTransform).(TranslateTransform.X)"));
Storyboard.SetTarget(story, target);
var doubleAnimation = new DoubleAnimationUsingKeyFrames();
EasingDoubleKeyFrame fromFrame = new EasingDoubleKeyFrame(from);
fromFrame.EasingFunction = new ExponentialEase() { EasingMode = EasingMode.EaseOut };
fromFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(0));
EasingDoubleKeyFrame toFrame = new EasingDoubleKeyFrame(to);
toFrame.EasingFunction = new ExponentialEase() { EasingMode = EasingMode.EaseOut };
toFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(200));
doubleAnimation.KeyFrames.Add(fromFrame);
doubleAnimation.KeyFrames.Add(toFrame);
story.Children.Add(doubleAnimation);
return story;
}
当用户点击“下一项”或“上一项”按钮时,会使用AnimationFactory生成一个故事板并启动它。目标值和起始值将基于SelectedIndex。RoutedUICommands可用于控制“下一项”/“上一项”按钮的操作。
public static RoutedUICommand NextCommand = new RoutedUICommand("Next", "Next", typeof(FlipView));
public static RoutedUICommand PreviousCommand = new RoutedUICommand("Previous", "Previous", typeof(FlipView));
一旦控件到达末尾,“下一项”按钮将被禁用。这是通过RoutedUICommand的CanExecute回调控制的。
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
用户可以使用滑动手势来导航项。这由WPF内置的操作API控制。在滑动控件时,框架会触发OnManipulationDelta回调。平移值将应用于根容器的渲染变换。
private void OnRootManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
if (!(this.PART_Root.RenderTransform is MatrixTransform))
{
this.PART_Root.RenderTransform = new MatrixTransform();
}
Matrix matrix = ((MatrixTransform)this.PART_Root.RenderTransform).Matrix;
var delta = e.DeltaManipulation;
if ((this.SelectedIndex == 0 && delta.Translation.X > 0 && this.elasticFactor > 0) || (this.SelectedIndex == this.Items.Count - 1 && delta.Translation.X < 0 && this.elasticFactor > 0))
{
this.elasticFactor -= 0.05;
}
matrix.Translate(delta.Translation.X * elasticFactor, 0);
this.PART_Root.RenderTransform = new MatrixTransform(matrix);
e.Handled = true;
}
如果控件的SelectedIndex为0,则表示选择了第一项,用户不允许导航到上一项。因此,向用户指示无法滑动到上一项是很重要的。控件将维护一个double值的因子变量。在滑动控件时,这个因子将乘以OffsetX值,然后应用于控件。因此,在无法导航到上一项的情况下,因子将减少0.05。因此,用户将感觉到拖动的困难。在离开手指时,将运行一个动画并将其带回原始位置。
if ((this.SelectedIndex == 0 && delta.Translation.X > 0 && this.elasticFactor > 0) || (this.SelectedIndex == this.Items.Count - 1 && delta.Translation.X < 0 && this.elasticFactor > 0))
{
this.elasticFactor -= 0.05;
}
matrix.Translate(delta.Translation.X * elasticFactor, 0);
FlipView控件继承自Selector,因此所有基础框架操作都可以使用这个控件。可以像传统的ItemsControl一样将集合绑定到控件。同时,DataTemplate可以应用于装饰视觉。
<controls:FlipView ItemsSource="{Binding Movies}" SelectedIndex="0">
<controls:FlipView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Image}" Stretch="Fill" />
</DataTemplate>
</controls:FlipView.ItemTemplate>
</controls:FlipView>