在触屏设备上,手指滑动页面,或者单击导航选项时,增加导航下横线滑动的效果:
这个版本有点简单粗暴,同事在项目中优化了一下算法。这里只是简单记录一下大致思路:
1、导航使用 ListView 控件,下面使用 Pivot 控件
大致结构为:
页面中的 ListView:
选中时,播放的位移动画:
Pivot 中,重写 Template,去掉 Header 等多余对象:
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 }