在觸屏設備上,手指滑動頁面,或者單擊導航選項時,增加導航下橫線滑動的效果:
這個版本有點簡單粗暴,同事在項目中優化了一下算法。這里只是簡單記錄一下大致思路:
1、導航使用 ListView 控件,下面使用 Pivot 控件
大致結構為:
頁面中的 ListView:
<ListView x:Name="listview" VerticalAlignment="Top" Margin="0,40,0,0" Background="White" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.HorizontalScrollMode="Enabled" ScrollViewer.VerticalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollMode="Disabled" SelectionChanged="listView_SelectionChanged"> <ListView.ItemContainerStyle> <Style TargetType="ListViewItem"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ListViewItem"> <!--選中色設置為 紅色--> <ListViewItemPresenter CheckBrush="{x:Null}" ContentMargin="{TemplateBinding Padding}" CheckMode="Inline" ContentTransitions="{x:Null}" CheckBoxBrush="{x:Null}" DragForeground="{x:Null}" DragOpacity="0" DragBackground="{x:Null}" DisabledOpacity="0" FocusBorderBrush="{x:Null}" FocusSecondaryBorderBrush="{x:Null}" HorizontalContentAlignment="Center" PointerOverForeground="{ThemeResource SystemControlHighlightAltBaseHighBrush}" PressedBackground="{x:Null}" PlaceholderBackground="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}" PointerOverBackground="{x:Null}" ReorderHintOffset="{ThemeResource ListViewItemReorderHintThemeOffset}" SelectionCheckMarkVisualEnabled="True" SelectedForeground="Red" SelectedPointerOverBackground="{x:Null}" SelectedBackground="{x:Null}" VerticalContentAlignment="Center" SelectedPressedBackground="{x:Null}" Foreground="Black"/> </ControlTemplate> </Setter.Value> </Setter> </Style> </ListView.ItemContainerStyle> <ListView.ItemsPanel> <ItemsPanelTemplate> <ItemsStackPanel Orientation="Horizontal"/> </ItemsPanelTemplate> </ListView.ItemsPanel> <ListView.ItemTemplate> <DataTemplate> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <TextBlock Text="{Binding Text}" FontSize="20" Margin="20,10"/>
<!--作為位移指示條--> <Rectangle x:Name="rect" Fill="Red" Height="2" HorizontalAlignment="Stretch" Grid.Row="1" Opacity="0" Loaded="rect_Loaded"> <Rectangle.RenderTransform> <CompositeTransform/> </Rectangle.RenderTransform> </Rectangle> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView>
選中時,播放的位移動畫:
<Page.Resources> <!--#region 導航下划線位移動畫--> <Storyboard x:Name="SB_Slide" Completed="SB_Slide_Completed"> <DoubleAnimationUsingKeyFrames x:Name="SB_Slide_TransX" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" > <!--Storyboard.TargetName="rect1"--> <EasingDoubleKeyFrame KeyTime="0" Value="0"/> <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="600"> <EasingDoubleKeyFrame.EasingFunction> <!--回彈效果--> <!--<BackEase EasingMode="EaseOut" Amplitude="0.3"/>--> <QuarticEase EasingMode="EaseOut"/> </EasingDoubleKeyFrame.EasingFunction> </EasingDoubleKeyFrame> </DoubleAnimationUsingKeyFrames> </Storyboard> <!--#endregion--> </Page.Resources>
Pivot 中,重寫 Template,去掉 Header 等多余對象:

<Pivot x:Name="pivot" Grid.Row="1" SelectedItem="{Binding SelectedItem,ElementName=listview,Mode=TwoWay}"> <Pivot.ItemContainerStyle> <Style TargetType="PivotItem"> <!--默認 Item 兩側有大約20px 的 Margin--> <Setter Property="Margin" Value="0"/> </Style> </Pivot.ItemContainerStyle> <Pivot.ItemTemplate> <DataTemplate> <Grid Background="LightBlue"> <TextBlock FontSize="30" Text="{Binding Text}" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="20"/> </Grid> </DataTemplate> </Pivot.ItemTemplate> <Pivot.Template> <!--去掉模板中多余的 UI(Header 等)和動畫--> <ControlTemplate TargetType="Pivot"> <Grid x:Name="RootElement" Background="{TemplateBinding Background}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}"> <Grid.Resources> <Style x:Key="BaseContentControlStyle" TargetType="ContentControl"> <Setter Property="FontFamily" Value="Segoe UI"/> <Setter Property="FontWeight" Value="SemiBold"/> <Setter Property="FontSize" Value="15"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ContentControl"> <ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" ContentTransitions="{TemplateBinding ContentTransitions}" Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" OpticalMarginAlignment="TrimSideBearings" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style x:Key="TitleContentControlStyle" BasedOn="{StaticResource BaseContentControlStyle}" TargetType="ContentControl"> <Setter Property="FontFamily" Value="{ThemeResource PivotTitleFontFamily}"/> <Setter Property="FontWeight" Value="{ThemeResource PivotTitleThemeFontWeight}"/> <Setter Property="FontSize" Value="{ThemeResource PivotTitleFontSize}"/> </Style> </Grid.Resources> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="Orientation"> <VisualState x:Name="Portrait"> <!--<Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Margin" Storyboard.TargetName="TitleContentControl"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotPortraitThemePadding}"/> </ObjectAnimationUsingKeyFrames> </Storyboard>--> </VisualState> <VisualState x:Name="Landscape"> <!--<Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Margin" Storyboard.TargetName="TitleContentControl"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource PivotLandscapeThemePadding}"/> </ObjectAnimationUsingKeyFrames> </Storyboard>--> </VisualState> </VisualStateGroup> <VisualStateGroup x:Name="NavigationButtonsVisibility"> <VisualState x:Name="NavigationButtonsHidden"/> <VisualState x:Name="NavigationButtonsVisible"> <!--<Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="NextButton"> <DiscreteObjectKeyFrame KeyTime="0" Value="1"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsEnabled" Storyboard.TargetName="NextButton"> <DiscreteObjectKeyFrame KeyTime="0" Value="True"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="PreviousButton"> <DiscreteObjectKeyFrame KeyTime="0" Value="1"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsEnabled" Storyboard.TargetName="PreviousButton"> <DiscreteObjectKeyFrame KeyTime="0" Value="True"/> </ObjectAnimationUsingKeyFrames> </Storyboard>--> </VisualState> </VisualStateGroup> <VisualStateGroup x:Name="HeaderStates"> <VisualState x:Name="HeaderDynamic"/> <VisualState x:Name="HeaderStatic"> <!--<Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="Header"> <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="StaticHeader"> <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/> </ObjectAnimationUsingKeyFrames> </Storyboard>--> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <ContentControl x:Name="TitleContentControl" ContentTemplate="{TemplateBinding TitleTemplate}" Content="{TemplateBinding Title}" IsTabStop="False" Margin="{StaticResource PivotPortraitThemePadding}" Style="{StaticResource TitleContentControlStyle}" Visibility="Collapsed"/> <Grid Grid.Row="1"> <!--<Grid.Resources> <ControlTemplate x:Key="NextTemplate" TargetType="Button"> <Border x:Name="Root" BorderBrush="{ThemeResource SystemControlForegroundTransparentBrush}" BorderThickness="{ThemeResource PivotNavButtonBorderThemeThickness}" Background="{ThemeResource SystemControlBackgroundBaseMediumLowBrush}"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal"/> <VisualState x:Name="PointerOver"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Root"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightBaseMediumBrush}"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="Arrow"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltAltMediumHighBrush}"/> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="Pressed"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Root"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightBaseMediumHighBrush}"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="Arrow"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltAltMediumHighBrush}"/> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <FontIcon x:Name="Arrow" Foreground="{ThemeResource SystemControlForegroundAltMediumHighBrush}" FontSize="12" FontFamily="{ThemeResource SymbolThemeFontFamily}" Glyph="" HorizontalAlignment="Center" MirroredWhenRightToLeft="True" UseLayoutRounding="False" VerticalAlignment="Center"/> </Border> </ControlTemplate> <ControlTemplate x:Key="PreviousTemplate" TargetType="Button"> <Border x:Name="Root" BorderBrush="{ThemeResource SystemControlForegroundTransparentBrush}" BorderThickness="{ThemeResource PivotNavButtonBorderThemeThickness}" Background="{ThemeResource SystemControlBackgroundBaseMediumLowBrush}"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal"/> <VisualState x:Name="PointerOver"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Root"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightBaseMediumBrush}"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="Arrow"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltAltMediumHighBrush}"/> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="Pressed"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Root"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightBaseMediumHighBrush}"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="Arrow"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltAltMediumHighBrush}"/> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <FontIcon x:Name="Arrow" Foreground="{ThemeResource SystemControlForegroundAltMediumHighBrush}" FontSize="12" FontFamily="{ThemeResource SymbolThemeFontFamily}" Glyph="" HorizontalAlignment="Center" MirroredWhenRightToLeft="True" UseLayoutRounding="False" VerticalAlignment="Center"/> </Border> </ControlTemplate> </Grid.Resources>--> <ScrollViewer x:Name="ScrollViewer" BringIntoViewOnFocusChange="False" HorizontalSnapPointsAlignment="Center" HorizontalSnapPointsType="MandatorySingle" HorizontalScrollBarVisibility="Hidden" Margin="{TemplateBinding Padding}" Template="{StaticResource ScrollViewerScrollBarlessTemplate}" VerticalSnapPointsType="None" VerticalScrollBarVisibility="Disabled" VerticalScrollMode="Disabled" VerticalContentAlignment="Stretch" ZoomMode="Disabled" ViewChanging="ScrollViewer_ViewChanging" DirectManipulationStarted="ScrollViewer_DirectManipulationStarted" > <PivotPanel x:Name="Panel" VerticalAlignment="Stretch"> <Grid x:Name="PivotLayoutElement"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid.RenderTransform> <CompositeTransform x:Name="PivotLayoutElementTranslateTransform"/> </Grid.RenderTransform> <!--<ContentPresenter x:Name="LeftHeaderPresenter" ContentTemplate="{TemplateBinding LeftHeaderTemplate}" Content="{TemplateBinding LeftHeader}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>--> <ContentControl x:Name="HeaderClipper" Grid.Column="1" HorizontalContentAlignment="Stretch" UseSystemFocusVisuals="True" Visibility="Collapsed"> <ContentControl.Clip> <RectangleGeometry x:Name="HeaderClipperGeometry"/> </ContentControl.Clip> <Grid Background="Transparent"> <PivotHeaderPanel x:Name="StaticHeader" Visibility="Collapsed"/> <PivotHeaderPanel x:Name="Header"> <PivotHeaderPanel.RenderTransform> <TransformGroup> <CompositeTransform x:Name="HeaderTranslateTransform"/> <CompositeTransform x:Name="HeaderOffsetTranslateTransform"/> </TransformGroup> </PivotHeaderPanel.RenderTransform> </PivotHeaderPanel> </Grid> </ContentControl> <!--<Button x:Name="PreviousButton" Background="Transparent" Grid.Column="1" HorizontalAlignment="Left" Height="36" IsTabStop="False" IsEnabled="False" Margin="{ThemeResource PivotNavButtonMargin}" Opacity="0" Template="{StaticResource PreviousTemplate}" UseSystemFocusVisuals="False" VerticalAlignment="Top" Width="20"/> <Button x:Name="NextButton" Background="Transparent" Grid.Column="1" HorizontalAlignment="Right" Height="36" IsTabStop="False" IsEnabled="False" Margin="{ThemeResource PivotNavButtonMargin}" Opacity="0" Template="{StaticResource NextTemplate}" UseSystemFocusVisuals="False" VerticalAlignment="Top" Width="20"/> <ContentPresenter x:Name="RightHeaderPresenter" ContentTemplate="{TemplateBinding RightHeaderTemplate}" Content="{TemplateBinding RightHeader}" Grid.Column="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />--> <ItemsPresenter x:Name="PivotItemPresenter" Grid.ColumnSpan="3" Grid.Row="1"> <ItemsPresenter.RenderTransform> <TransformGroup> <TranslateTransform x:Name="ItemsPresenterTranslateTransform"/> <CompositeTransform x:Name="ItemsPresenterCompositeTransform"/> </TransformGroup> </ItemsPresenter.RenderTransform> </ItemsPresenter> </Grid> </PivotPanel> </ScrollViewer> </Grid> </Grid> </ControlTemplate> </Pivot.Template> </Pivot>
2、事件操作
1)大致思路是,當手指滑動屏幕時,通過 Pivot 中 ScrollViewer 的 ViewChanging 事件,來橫向移動選中 Item 中紅色的矩形(Rectangle),當動畫結束時,再重置當前的 Rectangle 的位移。
如果單擊 ListView 中的 Item,則直接在它的 SelectionChanged 事件中,計算前一個 Rectangle 和新 Rectangle 的相對位置,來播放位移動畫。
2) code behind 中的代碼:
public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); this.Loaded += MainPage_Loaded; } private void MainPage_Loaded(object sender, RoutedEventArgs e) { this.Loaded -= MainPage_Loaded; // 綁定到匿名類型上 pivot.ItemsSource = listview.ItemsSource = new []{ new { Text = "軍事" } , new { Text = "科技" }, new { Text = "國內新聞" },new { Text = "娛樂" }, new { Text = "國際新聞" }, new { Text = "相聲" }, new { Text = "體育賽事" }, new { Text = "綜藝" } }; listview.SelectedIndex = 0; } #region 橫向 Rectangle 的位移 Rectangle rect_old; // 上一次選中的 Rectangle Rectangle rect_current; // 當前選中的 Rectangle double posi_previous; // 手指點擊屏幕時,記錄當前的 pivot 中 ScrollViewer.HorizontalOffset ScrollViewer pivot_sv; bool IsMoving = false;//手指是否在滑動中 private void listView_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (firstRectInited == false) return; // 如果是當前顯示項,則顯示“下橫線” for (int i = 0; i < listview.Items.Count; i++) { if (listview.ContainerFromIndex(i) != null) { var grid = (listview.ContainerFromIndex(i) as ListViewItem).ContentTemplateRoot as Grid; var rect = grid.FindName("rect") as Rectangle; (rect.RenderTransform as CompositeTransform).TranslateX = 0;// 重置橫向位移為 0 if (listview.SelectedIndex == i) // 當前選中項 { listview.ScrollIntoView(listview.SelectedItem);//當滑動 pivot 時,如果 ListView選中項不在視圖內,則顯示 rect_old = rect_current; rect_current = rect; if (IsMoving == false) // 非手指划動 pivot { Rect_Slide(); listview.IsHitTestVisible = false; // 當“划動動畫”在播放的時候,不再接受單擊事件 } } if (IsMoving) rect.Opacity = listview.SelectedIndex == i ? 1 : 0;//選中項顯示下橫向,否則隱藏 } } IsMoving = false; } //播放划動動畫 void Rect_Slide() { if (rect_old != null && rect_current != null) { // 如果設置 Width 屬性,可能會導致列表寬度發生變化,所以這里使用 Scale來縮放下橫線 (rect_old.RenderTransform as CompositeTransform).ScaleX = rect_current.ActualWidth / rect_old.ActualWidth; var old_rect = GetBounds(rect_old, listview); var new_rect = GetBounds(rect_current, listview); // 獲取 ListView 單擊后,兩個 Item之間的距離 SB_Slide_TransX.KeyFrames[1].Value = new_rect.X - old_rect.X; Storyboard.SetTarget(SB_Slide_TransX, rect_old); SB_Slide.Begin(); } } // 獲取子元素相對於父元素的左邊 public Rect GetBounds(FrameworkElement childElement, FrameworkElement parentElement) { GeneralTransform transform = childElement.TransformToVisual(parentElement); return transform.TransformBounds(new Rect(0, 0, childElement.ActualWidth, childElement.ActualHeight)); } // 滑動動畫結束時,重置參數 private void SB_Slide_Completed(object sender, object e) { listview.IsHitTestVisible = true; rect_old.Opacity = 0; (rect_old.RenderTransform as CompositeTransform).ScaleX = 1; rect_current.Opacity = 1; SB_Slide.Stop(); } // 手指橫向移動 Pivot 時,更改下橫向的位移 private void ScrollViewer_ViewChanging(object sender, ScrollViewerViewChangingEventArgs e) { if (IsMoving && rect_current != null && pivot_sv != null) (rect_current.RenderTransform as CompositeTransform).TranslateX = (pivot_sv.HorizontalOffset - posi_previous) / pivot_sv.ActualWidth * rect_current.ActualWidth; } // 手指開始觸摸屏幕時 private void ScrollViewer_DirectManipulationStarted(object sender, object e) { IsMoving = true; pivot_sv = sender as ScrollViewer; posi_previous = pivot_sv.HorizontalOffset; } // 查找第一個 Rectangle bool firstRectInited = false; private void rect_Loaded(object sender, RoutedEventArgs e) { var r = sender as Rectangle; r.Loaded -= rect_Loaded; if (!firstRectInited && listview.ContainerFromIndex(0) != null) { var grid = (listview.ContainerFromIndex(0) as ListViewItem).ContentTemplateRoot as Grid; rect_current = grid.FindName("rect") as Rectangle; rect_current.Opacity = 1; firstRectInited = true; } } #endregion }