本篇之所以起這樣一個名字,是因為重點並非如何自定義控制項,不涉及創建CustomControl和UserControl使用的Template和XAML概念。而是通過繼承的方法來擴展一個現有的類,在繼承的子類中增加屬性和擴展行為。 我們在《UWP開發入門(七)——下拉刷新》中提到過嵌套ScrollVie ...
本篇之所以起這樣一個名字,是因為重點並非如何自定義控制項,不涉及創建CustomControl和UserControl使用的Template和XAML概念。而是通過繼承的方法來擴展一個現有的類,在繼承的子類中增加屬性和擴展行為。
我們在《UWP開發入門(七)——下拉刷新》中提到過嵌套ScrollViewer的實現思路,本篇我們對ListView的第一個擴展行為,即是摒棄嵌套的做法,而是通過訪問ListView內部的ScrollViewer控制項,來監聽ViewChanged事件。
訪問ListView內部的ScrollViewer,必定離不開VisualTreeHelper類中的以下兩個方法:
public static DependencyObject GetChild(DependencyObject reference, System.Int32 childIndex); public static System.Int32 GetChildrenCount(DependencyObject reference);
可以將這兩個方法進一步組合得到:
static T FindFirstChild<T>(FrameworkElement element) where T : FrameworkElement { int childrenCount = VisualTreeHelper.GetChildrenCount(element); var children = new FrameworkElement[childrenCount]; for (int i = 0; i < childrenCount; i++) { var child = VisualTreeHelper.GetChild(element, i) as FrameworkElement; children[i] = child; if (child is T) return (T)child; } for (int i = 0; i < childrenCount; i++) if (children[i] != null) { var subChild = FindFirstChild<T>(children[i]); if (subChild != null) return subChild; } return null; }
該方法通過遞歸來遍歷FrameworkElement內部的元素,並返回第一個符合類型的元素。ListViewEx的第一個擴展如下:
public class ListViewEx : ListView, INotifyPropertyChanged { private ScrollViewer _scrollViewer; public event EventHandler LoadHistoryEvent; public event PropertyChangedEventHandler PropertyChanged; protected void OnProperyChanged([CallerMemberName] string name = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } public ListViewEx() { this.Loaded += ListViewEx_Loaded; this.Unloaded += ListViewEx_Unloaded; } private void ListViewEx_Unloaded(object sender, RoutedEventArgs e) { this.Unloaded -= ListViewEx_Unloaded; if (_scrollViewer != null) { _scrollViewer.ViewChanged -= Sv_ViewChanged; } } private void ListViewEx_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e) { this.Loaded -= ListViewEx_Loaded; _scrollViewer = FindFirstChild<ScrollViewer>(this); if (_scrollViewer != null) { _scrollViewer.ViewChanged += Sv_ViewChanged; } } private async void Sv_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e) { if (e.IsIntermediate == false && _scrollViewer.VerticalOffset < 1) { _scrollViewer.ChangeView(null, 50, null); await Task.Delay(10); LoadHistoryEvent?.Invoke(this, EventArgs.Empty); } }
嗯嗯,可以看到優雅的 -= event和恰到好處的null check,啊啊!忍不住想點個贊!在ViewChanged事件監測到滾動條到達頂部後,果斷觸發ListViewEx內定義的LoadHistoryEvent來通知更新數據。
本篇如果僅增加一個LoadHistoryEvent,又會被人非議是在補完前篇,一篇拆成兩篇寫……那好吧,我們再給ListViewEx增加第二個擴展GoBottomVisiblity屬性,顧名思義即是ListViewEx向上滾動幾屏以後,顯示一個GoBottom的按鈕。
在ListViewEx的類中,首先定義屬性GoBottomVisibility,然後同樣是在ViewChanged事件中,計算是否顯示GoBottom按鈕。
public Visibility GoBottomVisiblity { get { return _goBottomVisiblity; } set { _goBottomVisiblity = value; this.OnProperyChanged(); } } private void Sv_ViewChanged2(object sender, ScrollViewerViewChangedEventArgs e) { if (e.IsIntermediate == false) { CheckGoBottomVisibility(); } } private void CheckGoBottomVisibility() { if (_scrollViewer.VerticalOffset + _scrollViewer.ViewportHeight < _scrollViewer.ScrollableHeight) { GoBottomVisiblity = Visibility.Visible; } else { GoBottomVisiblity = Visibility.Collapsed; } }
代碼沒法全部貼上來,一個是太長了顯得啰嗦,二是會被管理員說沒內涵從首頁刪掉……
大體上本篇就是給ListView擴展了LoadHistoryEvent事件和GoBottomVisibility屬性。最後說說怎麼用,XAML里使用ListViewEx代替預設的ListView,會發現多出一個LoadHistoryEvent,掛上載入數據的事件就OK了。然後在列表的下部畫一個三角箭頭,Visibility綁定到ListViewEx的GoBottomVisibility屬性上就收工了。
<Grid> <local:ListViewEx x:Name="listViewEx" ItemsSource="{x:Bind Items}" LoadHistoryEvent="ListViewEx_LoadHistoryEvent"></local:ListViewEx> <Grid Margin="10" VerticalAlignment="Bottom" HorizontalAlignment="Center" Tapped="Grid_Tapped" Visibility="{x:Bind listViewEx.GoBottomVisiblity,Mode=OneWay}"> <Ellipse Fill="LightGray" Width="30" Height="30"></Ellipse> <Polyline Stroke="White" Points="10,10 15,20 20,10"></Polyline> </Grid> </Grid>
完整代碼放在GayHub上,地址:https://github.com/manupstairs/UWPSamples
非常感謝各位捧場,能夠點開頁面看到這裡,拜謝了!Orz