如果一個頁面中有很長的列表/內容,很多應用都會在用戶向下滾動時隱藏頁面的頭,給用戶留出更多的閱讀空間,同時提供一個方便的吸頂工具欄,比如淘寶中的店鋪頁面。 下麵是一個比較簡單的實現,如果有同學有更好的實現,歡迎留言,讓我們共同進步。 首先假設我們的頁面整體包含3部分; 結構代碼如下,為了區別清楚,我 ...
如果一個頁面中有很長的列表/內容,很多應用都會在用戶向下滾動時隱藏頁面的頭,給用戶留出更多的閱讀空間,同時提供一個方便的吸頂工具欄,比如淘寶中的店鋪頁面。
下麵是一個比較簡單的實現,如果有同學有更好的實現,歡迎留言,讓我們共同進步。
首先假設我們的頁面整體包含3部分;
- 頁面頭:隨頁面滾動慢慢消失/重現
- 工具欄: 開始時隨頁面滾動,在頁面頭消失後,吸頂,固定不動
- 可滾動內容:一個listview
結構代碼如下,為了區別清楚,我是用不同的背景色做區分:
1 <Page 2 x:Class="App3.MainPage" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:local="using:App3" 6 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 7 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 8 mc:Ignorable="d"> 9 10 <Page.Resources> 11 <Style TargetType="ListViewItem"> 12 <!-- 讓listview item拉伸 --> 13 <Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter> 14 </Style> 15 </Page.Resources> 16 17 <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> 18 <Grid.RowDefinitions> 19 <!-- 頭部高 --> 20 <RowDefinition Height="120" x:Name="headerRow"></RowDefinition> 21 <!-- 工具欄高 --> 22 <RowDefinition Height="48"></RowDefinition> 23 <!-- 其餘內容 --> 24 <RowDefinition Height="*"></RowDefinition> 25 </Grid.RowDefinitions> 26 27 <!-- 頭部定義 --> 28 <Grid Background="LightGray" x:Name="head" Grid.Row="0"> 29 <StackPanel Orientation="Horizontal" 30 HorizontalAlignment="Left" 31 VerticalAlignment="Center" 32 Margin="12, 0, 0, 0"> 33 <Image Source="http://img.alicdn.com/tps/i4/TB12mhwHVXXXXctXVXXAAT2HVXX-63-63.png" 34 Width="80" 35 Height="80"></Image> 36 37 <TextBlock HorizontalAlignment="Left" 38 VerticalAlignment="Center" 39 Margin="12, 0, 0, 0">這是個測試</TextBlock> 40 </StackPanel> 41 </Grid> 42 43 <!-- 工具欄定義 --> 44 <Grid Grid.Row="1" Background="DarkGray"> 45 <StackPanel Orientation="Horizontal"> 46 <HyperlinkButton Margin="18, 0, 0, 0">按鈕1</HyperlinkButton> 47 <HyperlinkButton Margin="12,0, 0, 0">按鈕2</HyperlinkButton> 48 </StackPanel> 49 </Grid> 50 51 <!-- 內容部分 --> 52 <ScrollViewer x:Name="scroller" Grid.Row="2" ViewChanged="scroller_ViewChanged"> 53 <ListView x:Name="list" ScrollViewer.HorizontalScrollMode="Disabled" 54 ScrollViewer.VerticalScrollMode="Disabled"> 55 <ListView.ItemTemplate> 56 <DataTemplate> 57 <Grid Height="120" Background="LightGoldenrodYellow"> 58 <TextBlock Text="{Binding}"></TextBlock> 59 </Grid> 60 </DataTemplate> 61 </ListView.ItemTemplate> 62 </ListView> 63 </ScrollViewer> 64 </Grid> 65 </Page>View Code
效果如下圖:
接下來我們需要在ScrollViewer.ViewChanged事件中處理滾動事件,將頁面頭部分慢慢的隱藏,知道高度為0,進而模擬出工具欄吸頂的狀態,同學們可以在腦子裡想象一下這個場景(●’◡’●)。但是由於在XAML中我們使用了RowDefinition定義的頭部高度,所以就不能直接修改控制項高度了,而是要動態修改這個RowDefinition的高度(我之前也不知道這貨也是可以動態修改的,也是看了其他同學的code學到的)。
1 public sealed partial class MainPage : Page
2 {
3 public List<string> Items { 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的,通過優化更新邏輯也會降低這個異常出現的概率。