博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
《ListBox》———何如实现ListBox下拉刷新和到底部自动加载
阅读量:6289 次
发布时间:2019-06-22

本文共 17574 字,大约阅读时间需要 58 分钟。

一、下拉刷新

下拉刷新实现思路:
1、定义一个PullDownToRefreshPanel容器控件。为它添加3种状态模板,分别是PullingDownTemplate,ReadyToReleaseTemplate
     和RefreshingTemplate,顾名思义分别是显示下拉状态模板,显示松开刷新状态模板和正在刷新中的状态模板。
2、定义自己的ListBox让它继承系统的ListBox,并重写它的Style,把ScrollViewer的ManipulationMode属性设为Conrtrol(必需),
     只有这样才能和我们的定义的PullDownToRefreshPanel兼容。ManipulationMode属性系统默认是System;区别就是,System的
     滑动效果更好。这里的ListBox的Style定义可以参考安装的SDK目录里面的系统定义,路径大致是:c->Program Files(x86)->
     Microsoft SDKs->Windows Phone->v7.1->Design->System.Windows.xaml.

 

View Code
1  76  77  78  79 

 

3、编写PullDownToRefreshPanel控件,主要是把PullDownToRefreshPanel和ScrollViewer联系起来,通过PullDwonReflesh距离滚动条的位置

     来实现下拉刷新功能. 具体的实现,代码里有很好的注释。相信难不到你

View Code
1 ///   2   3 /// 给滚得条添加下拉刷新机制  4   5 ///   6   7 /// 
8 9 /// 使用PullDwonReflesh距离滚动条的位置来实现下拉刷新功能. 10 11 /// 包含PullDwonReflesh的容器,必须直接或间接的包含滚动条。 12 13 /// 例如:一个StackPanel包含一个PullDownToRefreshPanel 和一个ListBox, 14 15 /// 而ListBox内部包含一个ScrollViewer来实现子项的显示。 16 17 ///
18 19 [TemplateVisualState(Name = PullDownToRefreshPanel.InactiveVisualState, GroupName = PullDownToRefreshPanel.ActivityVisualStateGroup)] 20 21 [TemplateVisualState(Name = PullDownToRefreshPanel.PullingDownVisualState, GroupName = PullDownToRefreshPanel.ActivityVisualStateGroup)] 22 23 [TemplateVisualState(Name = PullDownToRefreshPanel.ReadyToReleaseVisualState, GroupName = PullDownToRefreshPanel.ActivityVisualStateGroup)] 24 25 [TemplateVisualState(Name = PullDownToRefreshPanel.RefreshingVisualState, GroupName = PullDownToRefreshPanel.ActivityVisualStateGroup)] 26 27 public class PullDownToRefreshPanel : Control 28 29 { 30 31 #region Visual state name constants 32 33 //滚动中 34 35 private const string ActivityVisualStateGroup = "ActivityStates"; 36 37 //无操作 38 39 private const string InactiveVisualState = "Inactive"; 40 41 //下拉 42 43 private const string PullingDownVisualState = "PullingDown"; 44 45 //松手刷新 46 47 private const string ReadyToReleaseVisualState = "ReadyToRelease"; 48 49 //刷新中 50 51 private const string RefreshingVisualState = "Refreshing"; 52 53 54 55 #endregion 56 57 58 59 /// 60 61 /// 用于绑定下拉文字显示,松手刷新的滚动条 62 63 /// 64 65 private ScrollViewer targetScrollViewer; 66 67 68 69 /// 70 71 /// 构造函数 72 73 /// 74 75 public PullDownToRefreshPanel() 76 77 { 78 79 this.DefaultStyleKey = typeof(PullDownToRefreshPanel); 80 81 this.LayoutUpdated += this.PullDownToRefreshPanel_LayoutUpdated; 82 83 } 84 85 86 87 /// 88 89 /// 当滚动条下拉到指定程度(PullThreshold)时引发这个事件,并显示松手刷新 90 91 /// 如果开始刷新的时候事件要把IsRefreshing设置为true 92 93 /// 当刷新完成时把IsRefreshing设回false. 94 95 /// 96 97 public event EventHandler RefreshRequested; 98 99 100 101 #region IsRefreshing DependencyProperty 是否正在刷新102 103 104 105 public static readonly DependencyProperty IsRefreshingProperty = DependencyProperty.Register106 107 (108 109 "IsRefreshing", typeof(bool), typeof(PullDownToRefreshPanel),110 111 new PropertyMetadata(false, (d, e) => 112 113 {114 115 ((PullDownToRefreshPanel)d).OnIsRefreshingChanged(e); 116 117 })118 119 );120 121 122 123 public bool IsRefreshing124 125 {126 127 get128 129 {130 131 return (bool)this.GetValue(PullDownToRefreshPanel.IsRefreshingProperty);132 133 }134 135 set136 137 {138 139 this.SetValue(PullDownToRefreshPanel.IsRefreshingProperty, value);140 141 }142 143 }144 145 146 147 protected void OnIsRefreshingChanged(DependencyPropertyChangedEventArgs e)148 149 {150 151 string activityState = (bool)e.NewValue ? PullDownToRefreshPanel.RefreshingVisualState : PullDownToRefreshPanel.InactiveVisualState;152 153 VisualStateManager.GoToState(this, activityState, false);154 155 }156 157 158 159 #endregion160 161 162 163 #region PullThreshold DependencyProperty 设置下拉距离164 165 166 167 public static readonly DependencyProperty PullThresholdProperty = DependencyProperty.Register(168 169 "PullThreshold", typeof(double), typeof(PullDownToRefreshPanel), new PropertyMetadata(100.0));170 171 172 173 /// 174 175 /// 得到或设置一个滚动条从顶部下拉的最小距离,当达到这个距离松手时会触发刷新事件176 177 /// 默认距离是100. 推荐最大值不要超过125.178 179 /// 180 181 public double PullThreshold182 183 {184 185 get186 187 {188 189 return (double)this.GetValue(PullDownToRefreshPanel.PullThresholdProperty);190 191 }192 193 set194 195 {196 197 this.SetValue(PullDownToRefreshPanel.PullThresholdProperty, value);198 199 }200 201 }202 203 204 205 #endregion206 207 208 209 #region PullDistance DependencyProperty 滚动条拉下的距离210 211 212 213 public static readonly DependencyProperty PullDistanceProperty = DependencyProperty.Register(214 215 "PullDistance", typeof(double), typeof(PullDownToRefreshPanel), new PropertyMetadata(0.0));216 217 218 219 /// 220 221 /// 得到滚动条已经拉下的距离。222 223 /// 用它绑定图形距离224 225 /// 226 227 public double PullDistance228 229 {230 231 get232 233 {234 235 return (double)this.GetValue(PullDownToRefreshPanel.PullDistanceProperty);236 237 }238 239 private set240 241 {242 243 this.SetValue(PullDownToRefreshPanel.PullDistanceProperty, value);244 245 }246 247 }248 249 250 251 #endregion 252 253 254 255 #region PullFraction DependencyProperty 相对PullDistance拉下距离分值256 257 258 259 public static readonly DependencyProperty PullFractionProperty = DependencyProperty.Register(260 261 "PullFraction", typeof(double), typeof(PullDownToRefreshPanel), new PropertyMetadata(0.0));262 263 264 265 /// 266 267 /// 得到滚动条从顶部拉下的距离分数。即拉下PullThreshold的几分之几,取值范围(0.0 - 1.0)268 269 /// 270 271 public double PullFraction272 273 {274 275 get276 277 {278 279 return (double)this.GetValue(PullDownToRefreshPanel.PullFractionProperty);280 281 }282 283 private set284 285 {286 287 this.SetValue(PullDownToRefreshPanel.PullFractionProperty, value);288 289 }290 291 }292 293 294 295 #endregion296 297 298 299 #region PullingDownTemplate DependencyProperty 提示下拉刷新的模板300 301 302 303 public static readonly DependencyProperty PullingDownTemplateProperty = DependencyProperty.Register(304 305 "PullingDownTemplate", typeof(DataTemplate), typeof(PullDownToRefreshPanel), null);306 307 308 309 /// 310 311 /// Gets or sets a template that is progressively revealed has the ScrollViewer is pulled down.312 313 /// 314 315 public DataTemplate PullingDownTemplate316 317 {318 319 get320 321 {322 323 return (DataTemplate)this.GetValue(PullDownToRefreshPanel.PullingDownTemplateProperty);324 325 }326 327 set328 329 {330 331 this.SetValue(PullDownToRefreshPanel.PullingDownTemplateProperty, value);332 333 }334 335 }336 337 338 339 #endregion340 341 342 343 #region ReadyToReleaseTemplate DependencyProperty 提示松开刷新的模板344 345 346 347 public static readonly DependencyProperty ReadyToReleaseTemplateProperty = DependencyProperty.Register(348 349 "ReadyToReleaseTemplate", typeof(DataTemplate), typeof(PullDownToRefreshPanel), null);350 351 352 353 /// 354 355 /// Gets or sets the template that is displayed after the ScrollViewer is pulled down past356 357 /// the PullThreshold.358 359 /// 360 361 public DataTemplate ReadyToReleaseTemplate362 363 {364 365 get366 367 {368 369 return (DataTemplate)this.GetValue(PullDownToRefreshPanel.ReadyToReleaseTemplateProperty);370 371 }372 373 set374 375 {376 377 this.SetValue(PullDownToRefreshPanel.ReadyToReleaseTemplateProperty, value);378 379 }380 381 }382 383 384 385 #endregion386 387 388 389 #region RefreshingTemplate DependencyProperty 提示正在刷新的模板390 391 392 393 public static readonly DependencyProperty RefreshingTemplateProperty = DependencyProperty.Register(394 395 "RefreshingTemplate", typeof(DataTemplate), typeof(PullDownToRefreshPanel), null);396 397 398 399 /// 400 401 /// Gets or sets the template that is displayed while the ScrollViewer is refreshing.402 403 /// 404 405 public DataTemplate RefreshingTemplate406 407 {408 409 get410 411 {412 413 return (DataTemplate)this.GetValue(PullDownToRefreshPanel.RefreshingTemplateProperty);414 415 }416 417 set418 419 {420 421 this.SetValue(PullDownToRefreshPanel.RefreshingTemplateProperty, value);422 423 }424 425 }426 427 428 429 #endregion430 431 432 433 #region Initial setup 初始化设置434 435 436 437 private void PullDownToRefreshPanel_LayoutUpdated(object sender, EventArgs e)438 439 {440 441 if (this.targetScrollViewer == null)442 443 {444 445 //找到要绑定的目标滚动条446 447 this.targetScrollViewer = FindVisualElement
(VisualTreeHelper.GetParent(this));448 449 App._ScrollViewer = targetScrollViewer;450 451 if (this.targetScrollViewer != null)452 453 {454 455 this.targetScrollViewer.MouseMove += this.targetScrollViewer_MouseMove;456 457 this.targetScrollViewer.MouseLeftButtonUp += this.targetScrollViewer_MouseLeftButtonUp;458 459 460 461 //滚得条的类型必须是控件类型462 463 if (this.targetScrollViewer.ManipulationMode != ManipulationMode.Control)464 465 {466 467 throw new InvalidOperationException("PullDownToRefreshPanel requires the ScrollViewer " +468 469 "to have ManipulationMode=Control. (ListBoxes may require re-templating.");470 471 }472 473 }474 475 }476 477 }478 479 480 481 #endregion482 483 484 485 #region Pull-down detection 下拉检测486 487 488 489 ///
490 491 /// 当滚动条被拖拽时,显示动画和对应的控件模型492 493 /// 494 495 private void targetScrollViewer_MouseMove(object sender, MouseEventArgs e)496 497 {498 499 UIElement scrollContent = (UIElement)this.targetScrollViewer.Content;500 501 CompositeTransform ct = scrollContent.RenderTransform as CompositeTransform;502 503 if (ct != null && !this.IsRefreshing)504 505 {506 507 string activityState;508 509 if (ct.TranslateY > this.PullThreshold)510 511 {512 513 this.PullDistance = ct.TranslateY;514 515 this.PullFraction = 1.0;516 517 activityState = PullDownToRefreshPanel.ReadyToReleaseVisualState;518 519 }520 521 else if (ct.TranslateY > 0)522 523 {524 525 this.PullDistance = ct.TranslateY;526 527 double threshold = this.PullThreshold;528 529 this.PullFraction = threshold == 0.0 ? 1.0 : Math.Min(1.0, ct.TranslateY / threshold);530 531 activityState = PullDownToRefreshPanel.PullingDownVisualState;532 533 }534 535 else536 537 {538 539 this.PullDistance = 0;540 541 this.PullFraction = 0;542 543 activityState = PullDownToRefreshPanel.InactiveVisualState;544 545 }546 547 VisualStateManager.GoToState(this, activityState, false);548 549 }550 551 }552 553 554 555 ///
556 557 /// 当松手时检查是否要刷新558 559 /// 560 561 private void targetScrollViewer_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)562 563 {564 565 UIElement scrollContent = (UIElement)this.targetScrollViewer.Content;566 567 CompositeTransform ct = scrollContent.RenderTransform as CompositeTransform;568 569 if (ct != null && !this.IsRefreshing)570 571 {572 573 VisualStateManager.GoToState(this, PullDownToRefreshPanel.InactiveVisualState, false);574 575 this.PullDistance = 0;576 577 this.PullFraction = 0;578 579 580 581 if (ct.TranslateY >= this.PullThreshold)582 583 {584 585 EventHandler handler = this.RefreshRequested;586 587 if (handler != null)588 589 {590 591 handler(this, EventArgs.Empty);592 593 }594 595 }596 597 }598 599 }600 601 602 603 #endregion604 605 606 607 #region Utility methods 查找第一个符合要求的元素方法608 609 610 611 ///
612 613 /// 查找容器内所有元素,并返回第一个是T类型的元素614 615 /// 616 617 ///
要搜索的元素类型
618 619 ///
要搜索的容器620 621 ///
返回要找的元素,没找到返回null
622 623 private static T FindVisualElement
(DependencyObject container) where T : DependencyObject624 625 {626 627 Queue
childQueue = new Queue
();628 629 childQueue.Enqueue(container);630 631 632 633 while (childQueue.Count > 0)634 635 {636 637 DependencyObject current = childQueue.Dequeue();638 639 640 641 System.Diagnostics.Debug.WriteLine(current.GetType().ToString());642 643 644 645 T result = current as T;646 647 if (result != null && result != container)648 649 {650 651 return result;652 653 }654 655 656 657 int childCount = VisualTreeHelper.GetChildrenCount(current);658 659 for (int childIndex = 0; childIndex < childCount; childIndex++)660 661 {662 663 childQueue.Enqueue(VisualTreeHelper.GetChild(current, childIndex));664 665 }666 667 }668 669 670 671 return null;672 673 }674 675 676 677 #endregion678 679 }

4、实现自定义ListBox->CustListBox,代码很简单直接上。

View Code
1 [TemplatePart(Name = CustListBox.ScrollViewerPart, Type = typeof(ScrollViewer))] 2  3 public class CustListBox : ListBox 4  5 { 6  7 public const string ScrollViewerPart = "ScrollViewer"; 8  9 10 11 public CustListBox()12 13 {14 15 this.DefaultStyleKey = typeof(CustListBox);16 17 }18 19 20 21 #region AutoScrollMargin DependencyProperty22 23 24 25 public static readonly DependencyProperty AutoScrollMarginProperty = DependencyProperty.Register(26 27 "AutoScrollMargin", typeof(int), typeof(CustListBox), new PropertyMetadata(32));28 29 30 31 public double AutoScrollMargin32 33 {34 35 get36 37 {38 39 return (int)this.GetValue(CustListBox.AutoScrollMarginProperty);40 41 }42 43 set44 45 {46 47 this.SetValue(CustListBox.AutoScrollMarginProperty, value);48 49 }50 51 }52 53 54 55 #endregion56 57 }

5、使用要求   

     包含PullDwonReflesh的容器,必须直接或间接的包含滚动条。
     例如:一个StackPanel包含一个PullDownToRefreshPanel 和一个ListBox,而ListBox内部包含一个ScrollViewer来实现子项的显示。
6、为了使用起来更方便,我把CustListBox和PullDownToRefreshPanel 又封装了一层
     xaml

View Code
1 
26 27 28 29
30 31
32 33
34 35
36 37
38 39
40 41
48 49
68 69
70 71
72 73
View Code
1 using System;  2   3 using System.Collections.Generic;  4   5 using System.Linq;  6   7 using System.Net;  8   9 using System.Windows; 10  11 using System.Windows.Controls; 12  13 using System.Windows.Documents; 14  15 using System.Windows.Input; 16  17 using System.Windows.Media; 18  19 using System.Windows.Media.Animation; 20  21 using System.Windows.Shapes; 22  23 using System.Collections; 24  25  26  27 namespace PullDwonReflesh.Themes 28  29 { 30  31 public partial class CustListbox : UserControl 32  33 { 34  35 public event SelectionChangedEventHandler SelectionChanged; 36  37 public event EventHandler RefreshRequested; 38  39  40  41 public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(CustListbox), new PropertyMetadata("")); 42  43 public IEnumerable ItemsSource 44  45 { 46  47 get 48  49 { 50  51 return (IEnumerable)base.GetValue(ItemsSourceProperty); 52  53 } 54  55 set 56  57 { 58  59 base.SetValue(ItemsSourceProperty, value); 60  61 } 62  63 } 64  65  66  67 private DataTemplate _ItemTemplate = null; 68  69 public DataTemplate ItemTemplate 70  71 { 72  73 get 74  75 { 76  77 return _ItemTemplate; 78  79 } 80  81 set 82  83 { 84  85 _ItemTemplate = value; 86  87 custListBox.ItemTemplate = value; 88  89 } 90  91 } 92  93  94  95 public static readonly DependencyProperty IsRefreshingProperty = DependencyProperty.Register("IsRefreshing", typeof(bool), typeof(CustListbox), new PropertyMetadata(false, (d, e) => ((CustListbox)d).OnIsRefreshingChanged(e))); 96  97 public bool IsRefreshing 98  99 {100 101 get102 103 {104 105 return (bool)this.GetValue(CustListbox.IsRefreshingProperty);106 107 }108 109 set110 111 {112 113 this.SetValue(CustListbox.IsRefreshingProperty, value);114 115 }116 117 }118 119 120 121 protected void OnIsRefreshingChanged(DependencyPropertyChangedEventArgs e)122 123 {124 125 this.refreshPanel.IsRefreshing = (bool)e.NewValue;126 127 }128 129 130 131 public CustListbox()132 133 {134 135 InitializeComponent();136 137 }138 139 140 141 private void CustListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)142 143 {144 145 if (SelectionChanged != null)146 147 {148 149 SelectionChanged(sender, e);150 151 }152 153 }154 155 156 157 private void refreshPanel_RefreshRequested(object sender, EventArgs e)158 159 {160 161 if (RefreshRequested != null)162 163 {164 165 RefreshRequested(sender, e);166 167 }168 169 }170 171 }172 173 }

到此,下拉刷新已经完成,可以拿出来单独使用。

二、ListBox滚动到底部自动加载
这个实现起来就更简单,思路
1、检测ListBox中的ScrollViewer控件状态
2、若状态不为滚动中:根据ScrollViewer的ExtentHeight与VerticalOffset,判断是否到底,并执行请求加载数据。
首先获取ScrollViewer,这个上面实现下拉的中已经得到了,这里在App定义一个变量把下拉时获得的ScrollViewer保存起来,并在这里作为目标滚动条。
然后编写获得状态函数,如下

View Code
1 private VisualStateGroup FindVisualState(FrameworkElement element, string name) 2  3 { 4  5 if (element == null) 6  7 return null; 8  9 10 11 IList groups = VisualStateManager.GetVisualStateGroups(element);12 13 foreach (VisualStateGroup group in groups)14 15 {16 17 if (group.Name == name)18 19 return group;20 21 }22 23 24 25 return null;26 27 }

 

接下来根据状态的改变做相应的加载功能,代码如下

1 void visualStateGroup_CurrentStateChanged(object sender, VisualStateChangedEventArgs e) 2  3 { 4  5 var visualState = e.NewState.Name; 6  7 if (visualState == "NotScrolling") 8  9 {10 11 var v1 = _ScrollViewer.ExtentHeight - _ScrollViewer.VerticalOffset;12 13 var v2 = _ScrollViewer.ViewportHeight * 1.5;14 15 16 17 if (v1 <= v2 && !custListBox.IsRefreshing)18 19 {20 21 AddString(index, 20);22 23 visualState += "_End";24 25 }26 27 }28 29 }

最后,在页面的loaded里把这些代码串联起来就OK了。如下:

1 private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e) 2  3 { 4  5 if (_IsHookedScrollEvent) 6  7 return; 8  9 _ScrollViewer = App._ScrollViewer;10 11 if (_ScrollViewer != null)12 13 {14 15 _IsHookedScrollEvent = true;16 17 FrameworkElement element = VisualTreeHelper.GetChild(_ScrollViewer, 0) as FrameworkElement;18 19 if (element != null)20 21 {22 23 VisualStateGroup visualStateGroup = FindVisualState(element, "ScrollStates");24 25 visualStateGroup.CurrentStateChanged += new EventHandler
(visualStateGroup_CurrentStateChanged);26 27 }28 29 }30 31 }

本文参考:

和Jason Ginchereau的博客

源码:

本文版权归作者和卤面网所有,

转载请标明原始出处

 

 

转载于:https://www.cnblogs.com/qq278360339/archive/2012/06/01/2530532.html

你可能感兴趣的文章