(UWP開發)更為合理的一種ListView下拉刷新(PullToRefresh)實現方法


最近在做的一個項目需要用到下拉刷新,但是參考了現在網絡上比較普遍的方法,覺得都不太好,因為要在外部套上一個SrollViewer,容易出現滾動錯誤。於是剛開始的時候就把思路定到了ListView內部的ScrollViewer上。

最初的想法是在ScrollViewer的Manipulation相關事件上下手,確實做好了,效果也不錯,如圖:

當時得意滿滿的看着自己的作品,心里是說不出的激動啊,結果放在手機上想試試觸屏設備的效果,結果發現好坑爹:在觸屏設備上,手指在ListView的上下滑動默認是移動其滾動條,但是改變了ManipulationModes之后,手指在屏幕上滑動就不能夠使ListView滾動了(一臉懵逼)。無奈啊,只能放棄這個方法了。

之后又在想新的途徑來實現同樣的效果,但是發現都沒有很好的辦法,於是我的思路又繞回了起點:還是像網絡上的方法一樣,先把刷新的Header事先隱藏起來,當ScrollViewer滾動出Header之后開始刷新。只不過這個ScrollViewer不是外部嵌套的,是在ListView內部的。實現后的效果如下:

好吧,下面言歸正傳,講講詳細的實現方法:

1.首先是在Xmal中,我們改一下ListView的Template。我在ScrollViewer最外層放了一個Grid,定義了兩行。第一行放的就是Header,也就是刷新的時候會一直轉的那個區域。第二行是ItemPresenter,顯示ListView的內容。其中值得注意的是,第一行的高度是30。

代碼如下:

 1                         <ControlTemplate TargetType="ListView">
 2                             <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
 3                                 <ScrollViewer x:Name="ScrollViewer" AutomationProperties.AccessibilityView="Raw" BringIntoViewOnFocusChange="{TemplateBinding ScrollViewer.BringIntoViewOnFocusChange}" HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}" HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}" IsHorizontalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsHorizontalScrollChainingEnabled}" IsVerticalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsVerticalScrollChainingEnabled}" IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}" IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}" TabNavigation="{TemplateBinding TabNavigation}" VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}" VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}" ZoomMode="{TemplateBinding ScrollViewer.ZoomMode}">
 4                                     <Grid>
 5                                         <Grid.RowDefinitions>
 6                                             <RowDefinition Height="auto"></RowDefinition>
 7                                             <RowDefinition Height="*"></RowDefinition>
 8                                         </Grid.RowDefinitions>
 9                                         <Grid x:Name="refresh_ring" Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center">
10                                             <Ellipse Height="30" Width="30" Fill="{Binding Source={StaticResource APPTheme},Path=APP_Color_Brush}" Opacity="0.7" StrokeThickness="0"></Ellipse>
11                                             <ProgressRing IsActive="True" Foreground="{Binding Source={StaticResource APPTheme},Path=Foreground_Color_Brush}"></ProgressRing>
12                                         </Grid>
13                                         <ItemsPresenter Grid.Row="1" FooterTransitions="{TemplateBinding FooterTransitions}" FooterTemplate="{TemplateBinding FooterTemplate}" Footer="{TemplateBinding Footer}" HeaderTemplate="{TemplateBinding HeaderTemplate}" Header="{TemplateBinding Header}" HeaderTransitions="{TemplateBinding HeaderTransitions}"/>
14                                     </Grid>
15                                 </ScrollViewer>
16                             </Border>
17                         </ControlTemplate>

2.在ListView的Loaded事件中拿到其內部的SrollViewer,以及訂閱相關事件。

1         private void ListView_Loaded(object sender, RoutedEventArgs e)
2         {
3             Get_Child((DependencyObject)sender);
4             lastest_listview_sc.ViewChanging += Lastest_listview_sc_ViewChanging;
5             lastest_listview_sc.ViewChanged += Lastest_listview_sc_ViewChanged;
6         }

其中這個Get_Child是一個實現遍歷可視化樹的方法,我們這里傳遞的參數sender就是ListView本身。Get_Child代碼如下:

 1         private void Get_Child(DependencyObject o)
 2         {
 3             try
 4             {
 5                 int count = VisualTreeHelper.GetChildrenCount(o);
 6                 if (count > 0)
 7                 {
 8                     for (int i = 0; i < count; i++)
 9                     {
10                         var child = VisualTreeHelper.GetChild(o, count - 1);
11                         if (child is ScrollViewer) 12  { 13                             lastest_listview_sc = child as ScrollViewer; 14  } 15                         else
16  { 17                             Get_Child(VisualTreeHelper.GetChild(o, count - 1), n); 18  } 19  } 20  } 21  } 22             catch (Exception ex) 23  { 24                 return; 25  } 26         }

3.這里就是接下來的重點了。相信自己動手實現過下拉刷新的同學們都能夠遇見過這個問題:如果用戶只是普通的滾動ListView查看內容,手指往下滑的時候(內容是往下)ScrollViewer會由於慣性偏移量滑到最上部,或者其他的類似操作也會滑動到最上部,但是此時用戶並不想要刷新。因此,我們就要在Lastest_listview_sc_ViewChanging方法內部實時監聽ScrollViewer的滾動狀態。如果此時ScrollViewer是由於慣性偏移量在滾動並且已經滾動超過了指定條件,我們就要讓SrollViewer自己滾回30的位置,也就是判錯。另外在觸屏設備和非觸屏設備上面,我們判錯的指定條件最好不要一樣(因為電腦用的是鼠標滾輪手機Surface之類的用的是手指,要是設定成一樣的你會發現可能你怎么樣都無法滑到最上面。。。)

因此上面提到的問題我們要在Lastest_listview_sc_ViewChanging方法中解決。其中的形參e的屬性IsInertial可以用來獲取操作是否具有慣性元素。Lastest_listview_sc_ViewChanging代碼如下:

 1         private async void Lastest_listview_sc_ViewChanging(object sender, ScrollViewerViewChangingEventArgs e) //下拉刷新判錯 自動加載
 2         {
 3             if (App.DeviceInfo.Device_type != Model.DeviceType.PC)
 4             {
 5                 if (lastest_listview_sc.VerticalOffset <= 10.0)
 6                 {
 7                     if (e.IsInertial)
 8                     {
 9                         lastest_listview_sc.ChangeView(null, 30.0, null);
10                     }
11                 }
12             }
13             else
14             {
15                 if (lastest_listview_sc.VerticalOffset <= 50.0 && lastest_listview_sc.VerticalOffset > 30.0)
16                 {
17                     if (e.IsInertial)
18                     {
19                         lastest_listview_sc.ChangeView(null, 30.0, null);
20                     }
21                 }
22             }
23         }

4.現在我們要用到Lastest_listview_sc_ViewChanged這個方法。其中的形參e的屬性IsIntermediate可以告訴我們引發ViewChanged事件的基礎操作是否完成,如果完成的話我們就可以來判斷是否需要刷新了。代碼如下:

1         private void Lastest_listview_sc_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
2         {
3             if (!e.IsIntermediate)
4             {
5                 Lastest_Refresh();
6             }
7         }

5.最后一步了,就是實現我們的Lastest_Refresh方法,在這個方法中,我們需要判斷ScrollViewer是否滾動到了最頂端,如果是的話,就可以開始執行刷新代碼。刷新結束之后再將ScrollViewer往下滾30就OK了。代碼如下:

 1         private async void Lastest_Refresh()
 2         {
 3             if (lastest_listview_sc.VerticalOffset == 0.0)
 4             {
 5                 //刷新相關代碼
 6                 await Task.Delay(1000);
 7 
 8                 //移動回頂部
 9                 lastest_listview_sc.ChangeView(null, 30, null);
10             }
11         }

至此,下拉刷新的過程到結束。當然,大家看到我的代碼還是比較復雜,也不夠簡潔,畢竟我還是個大一學生。拿出來是跟大家交流的嘛,如果有什么更好的方法或者建議歡迎評論,謝謝。感激不盡!

——2016/08/01編輯——

忘了寫一個點,就是進入頁面后我們要先把刷新部分的Header隱藏起來,要不然Header就會一直出現。。。可以在頁面的OnNavigate方法最后添加,代碼如下:

1                 await Task.Delay(500);
2                 series_listview_sc.ChangeView(null, 30, null);

第一行的意義是如果直接執行的話可能會沒有效果,所以要先Delay。

然后還要感謝網友提出一個很嚴重的BUG,就是如果ListView內部item過少,Header可能會無法被隱藏掉,這個時候這套刷新邏輯就沒有用了。。。希望大家有更好的方法的話可以分享一下,謝謝!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM