如果一个页面中有很长的列表/内容,很多应用都会在用户向下滚动时隐藏页面的头,给用户留出更多的阅读空间,同时提供一个方便的吸顶工具栏,比如淘宝中的店铺页面。
下面是一个比较简单的实现,如果有同学有更好的实现,欢迎留言,让我们共同进步。
首先假设我们的页面整体包含3部分;
- 页面头:随页面滚动慢慢消失/重现
- 工具栏: 开始时随页面滚动,在页面头消失后,吸顶,固定不动
- 可滚动内容:一个listview
结构代码如下,为了区别清楚,我是用不同的背景色做区分:
19 10 11 15 16 1718 6519 20 26 27 2821 22 23 24 25 29 42 43 4433 4136 37 这是个测试 4045 50 51 5246 49按钮1 47按钮2 4853 6455 6356 6257 6158 6059
View Code
效果如下图:
接下来我们需要在ScrollViewer.ViewChanged事件中处理滚动事件,将页面头部分慢慢的隐藏,知道高度为0,进而模拟出工具栏吸顶的状态,同学们可以在脑子里想象一下这个场景(●’’●)。但是由于在XAML中我们使用了RowDefinition定义的头部高度,所以就不能直接修改控件高度了,而是要动态修改这个RowDefinition的高度(我之前也不知道这货也是可以动态修改的,也是看了其他同学的code学到的)。
1 public sealed partial class MainPage : Page 2 { 3 public ListItems { get; set; } = Enumerable.Repeat("this is a test item", 30).ToList; 4 5 public MainPage 6 { 7 this.InitializeComponent; 8 9 this.Loaded += MainPage_Loaded; 10 } 11 12 private void MainPage_Loaded(object sender, RoutedEventArgs e) 13 { 14 this.list.ItemsSource = this.Items; 15 } 16 17 private void scroller_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e) 18 { 19 //得到scrollerview的滚动高度 20 var verticalOffset = scroller.VerticalOffset; 21 22 //计算当前header应该显示的高度 23 var delta = _headerDefaultHeight - verticalOffset; 24 25 //高一定大于0!!! 26 headerRow.Height = new GridLength(delta < 0 ? 0 : delta); 27 } 28 29 // 为了方便这里hard code,也可以从行定义中取得 30 double _headerDefaultHeight = 120D; 31 }
View Code
一个简单的头部隐藏,工具栏吸顶的小功能就实现了。
但是如果你仔细观察,头部在消失的过程中,布局有变化,强迫症表示好难受啊。
为了解决这个问题,首先让我们分析原因,其实很简单,因为在代码中一直在动态的修改头的高度,而头部控件中都是动态的布局。为了怎么解决这个问题呢?简单一点就是修改margin,既然是因为高度变化了导致布局变化,那么我们就让可显示区域变化相应的高度,这样布局就没有影响了!
修改后代码如下(只贴出修改部分):
1 private void scroller_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e) 2 { 3 //得到scrollerview的滚动高度 4 var verticalOffset = scroller.VerticalOffset; 5 6 //计算当前header应该显示的高度 7 var delta = _headerDefaultHeight - verticalOffset; 8 9 //高一定大于0!!! 10 headerRow.Height = new GridLength(delta < 0 ? 0 : delta); 11 12 // 将head的位置提升,改变可显示区域 13 head.Margin = new Thickness(0, -verticalOffset, 0, 0); 14 }
View Code
这样再来看看滚动的效果!
到这里你以为完了么?不,这里面还有一个小坑在前面等着你去踩!如果你在一些比较慢的机器上(比如一些低端的win phone)运行这个页面,快速滚动的话你会有碰到一个很迷的异常,“Layout cycle detected.”,而且根本定位不到异常的具体位置。。。相信有些同学也遇到过这个问题,通常这是因为更新UI太频繁,简单粗暴的方法是加上Task.Delay,时间可以短一些,比如1ms。同时上面的代码其实还有很大的优化空间,比如我们的ViewChanged事件里其实不是必须每次都要更新UI的,通过优化更新逻辑也会降低这个异常出现的概率。