《ListBox》———何如实现ListBox下拉刷新和到底部自动加载
一、下拉刷新
下拉刷新实现思路:
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.
1 <Style TargetType="rlb:CustListBox"> 2 3 <Setter Property="Background" Value="Transparent" /> 4 5 <Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}" /> 6 7 <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" /> 8 9 <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" /> 10 11 <Setter Property="BorderThickness" Value="0" /> 12 13 <Setter Property="BorderBrush" Value="Transparent" /> 14 15 <Setter Property="Padding" Value="0" /> 16 17 <Setter Property="Template"> 18 19 <Setter.Value> 20 21 <ControlTemplate TargetType="rlb:CustListBox"> 22 23 <Grid> 24 25 <Grid.ColumnDefinitions> 26 27 <ColumnDefinition Width="*" /> 28 29 <ColumnDefinition Width="Auto" /> 30 31 </Grid.ColumnDefinitions> 32 33 <!-- ScrollViewer的ManipulationMode属性必须设为Conrtrol才能和PullDownToRefreshPanel兼容 --> 34 35 <!-- ScrollViewer的ManipulationMode属性默认是System它能提供更好的滚动效果 --> 36 37 <ScrollViewer x:Name="ScrollViewer" Grid.ColumnSpan="2" ManipulationMode="Control" Foreground="{TemplateBinding Foreground}" Background="{TemplateBinding Background}" 38 39 BorderBrush="Transparent" BorderThickness="0" Padding="{TemplateBinding Padding}"> 40 41 <ItemsPresenter /> 42 43 </ScrollViewer> 44 45 <!-- The DragInterceptor sits on top of the item DragHandles and intercepts drag events 46 47 so that the capture is not lost when the item container is removed from the panel. 48 49 Its width must be equal to the width of the item DragHandles. --> 50 51 <Canvas x:Name="DragInterceptor" Grid.Column="1" Margin="{TemplateBinding Padding}" Background="Transparent" VerticalAlignment="Stretch" Width="52"> 52 53 <Image x:Name="DragIndicator" Visibility="Collapsed"> 54 55 <Image.RenderTransform> 56 57 <TranslateTransform /> 58 59 </Image.RenderTransform> 60 61 </Image> 62 63 </Canvas> 64 65 <Canvas x:Name="RearrangeCanvas" Grid.ColumnSpan="2" Margin="{TemplateBinding Padding}" Background="Transparent" Visibility="Collapsed" /> 66 67 </Grid> 68 69 </ControlTemplate> 70 71 </Setter.Value> 72 73 </Setter> 74 75 </Style> 76 77 78 79 <Style TargetType="rlb:PullDownToRefreshPanel"> 80 81 <Setter Property="Background" Value="Transparent" /> 82 83 <Setter Property="PullingDownTemplate"> 84 85 <Setter.Value> 86 87 <DataTemplate> 88 89 <TextBlock Margin="0,16,0,0" Style="{StaticResource PhoneTextGroupHeaderStyle}" TextAlignment="Center" FontStyle="Italic" Text="Pull down to refresh." /> 90 91 </DataTemplate> 92 93 </Setter.Value> 94 95 </Setter> 96 97 <Setter Property="ReadyToReleaseTemplate"> 98 99 <Setter.Value> 100 101 <DataTemplate> 102 103 <TextBlock Margin="0,16,0,0" Style="{StaticResource PhoneTextGroupHeaderStyle}" TextAlignment="Center" FontWeight="Bold" Text="Release to refresh!" /> 104 105 </DataTemplate> 106 107 </Setter.Value> 108 109 </Setter> 110 111 <Setter Property="RefreshingTemplate"> 112 113 <Setter.Value> 114 115 <DataTemplate> 116 117 <ProgressBar Margin="0,4,0,4" IsIndeterminate="True" /> 118 119 </DataTemplate> 120 121 </Setter.Value> 122 123 </Setter> 124 125 <Setter Property="Template"> 126 127 <Setter.Value> 128 129 <ControlTemplate TargetType="rlb:PullDownToRefreshPanel"> 130 131 <StackPanel x:Name="PullDownContainer" HorizontalAlignment="Stretch"> 132 133 <StackPanel.Resources> 134 135 <rlb:NegativeValueConverter x:Key="NegativeValueConverter" /> 136 137 </StackPanel.Resources> 138 139 <StackPanel x:Name="PullingDownPanel" Background="{TemplateBinding Background}" Height="{TemplateBinding PullDistance}" Opacity="{TemplateBinding PullFraction}" 140 141 Margin="{Binding PullDistance, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource NegativeValueConverter}, ConverterParameter=Bottom}" Visibility="Collapsed"> 142 143 <ContentPresenter ContentTemplate="{TemplateBinding PullingDownTemplate}" /> 144 145 </StackPanel> 146 147 <StackPanel x:Name="ReadyToReleasePanel" Background="{TemplateBinding Background}" Height="{TemplateBinding PullDistance}" 148 149 Margin="{Binding PullDistance, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource NegativeValueConverter}, ConverterParameter=Bottom}" Visibility="Collapsed"> 150 151 <ContentPresenter ContentTemplate="{TemplateBinding ReadyToReleaseTemplate}" /> 152 153 </StackPanel> 154 155 <StackPanel x:Name="RefreshingPanel" Background="{TemplateBinding Background}" Visibility="Collapsed"> 156 157 <ContentPresenter ContentTemplate="{TemplateBinding RefreshingTemplate}" /> 158 159 </StackPanel> 160 161 <VisualStateManager.VisualStateGroups> 162 163 <VisualStateGroup x:Name="ActivityStates"> 164 165 <VisualState x:Name="Inactive" /> 166 167 <VisualState x:Name="PullingDown"> 168 169 <Storyboard> 170 171 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PullingDownPanel" Storyboard.TargetProperty="Visibility"> 172 173 <DiscreteObjectKeyFrame KeyTime="0" Value="Visible" /> 174 175 </ObjectAnimationUsingKeyFrames> 176 177 </Storyboard> 178 179 </VisualState> 180 181 <VisualState x:Name="ReadyToRelease"> 182 183 <Storyboard> 184 185 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ReadyToReleasePanel" Storyboard.TargetProperty="Visibility"> 186 187 <DiscreteObjectKeyFrame KeyTime="0" Value="Visible" /> 188 189 </ObjectAnimationUsingKeyFrames> 190 191 </Storyboard> 192 193 </VisualState> 194 195 <VisualState x:Name="Refreshing"> 196 197 <Storyboard> 198 199 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RefreshingPanel" Storyboard.TargetProperty="Visibility"> 200 201 <DiscreteObjectKeyFrame KeyTime="0" Value="Visible" /> 202 203 </ObjectAnimationUsingKeyFrames> 204 205 </Storyboard> 206 207 </VisualState> 208 209 </VisualStateGroup> 210 211 </VisualStateManager.VisualStateGroups> 212 213 </StackPanel> 214 215 </ControlTemplate> 216 217 </Setter.Value> 218 219 </Setter> 220 221 </Style>
3、编写PullDownToRefreshPanel控件,主要是把PullDownToRefreshPanel和ScrollViewer联系起来,通过PullDwonReflesh距离滚动条的位置
来实现下拉刷新功能. 具体的实现,代码里有很好的注释。相信难不到你
1 /// <summary> 2 3 /// 给滚得条添加下拉刷新机制 4 5 /// </summary> 6 7 /// <remarks> 8 9 /// 使用PullDwonReflesh距离滚动条的位置来实现下拉刷新功能. 10 11 /// 包含PullDwonReflesh的容器,必须直接或间接的包含滚动条。 12 13 /// 例如:一个StackPanel包含一个PullDownToRefreshPanel 和一个ListBox, 14 15 /// 而ListBox内部包含一个ScrollViewer来实现子项的显示。 16 17 /// </remarks> 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 /// <summary> 60 61 /// 用于绑定下拉文字显示,松手刷新的滚动条 62 63 /// </summary> 64 65 private ScrollViewer targetScrollViewer; 66 67 68 69 /// <summary> 70 71 /// 构造函数 72 73 /// </summary> 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 /// <summary> 88 89 /// 当滚动条下拉到指定程度(PullThreshold)时引发这个事件,并显示松手刷新 90 91 /// 如果开始刷新的时候事件要把IsRefreshing设置为true 92 93 /// 当刷新完成时把IsRefreshing设回false. 94 95 /// </summary> 96 97 public event EventHandler RefreshRequested; 98 99 100 101 #region IsRefreshing DependencyProperty 是否正在刷新 102 103 104 105 public static readonly DependencyProperty IsRefreshingProperty = DependencyProperty.Register 106 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 IsRefreshing 124 125 { 126 127 get 128 129 { 130 131 return (bool)this.GetValue(PullDownToRefreshPanel.IsRefreshingProperty); 132 133 } 134 135 set 136 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 #endregion 160 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 /// <summary> 174 175 /// 得到或设置一个滚动条从顶部下拉的最小距离,当达到这个距离松手时会触发刷新事件 176 177 /// 默认距离是100. 推荐最大值不要超过125. 178 179 /// </summary> 180 181 public double PullThreshold 182 183 { 184 185 get 186 187 { 188 189 return (double)this.GetValue(PullDownToRefreshPanel.PullThresholdProperty); 190 191 } 192 193 set 194 195 { 196 197 this.SetValue(PullDownToRefreshPanel.PullThresholdProperty, value); 198 199 } 200 201 } 202 203 204 205 #endregion 206 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 /// <summary> 220 221 /// 得到滚动条已经拉下的距离。 222 223 /// 用它绑定图形距离 224 225 /// </summary> 226 227 public double PullDistance 228 229 { 230 231 get 232 233 { 234 235 return (double)this.GetValue(PullDownToRefreshPanel.PullDistanceProperty); 236 237 } 238 239 private set 240 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 /// <summary> 266 267 /// 得到滚动条从顶部拉下的距离分数。即拉下PullThreshold的几分之几,取值范围(0.0 - 1.0) 268 269 /// </summary> 270 271 public double PullFraction 272 273 { 274 275 get 276 277 { 278 279 return (double)this.GetValue(PullDownToRefreshPanel.PullFractionProperty); 280 281 } 282 283 private set 284 285 { 286 287 this.SetValue(PullDownToRefreshPanel.PullFractionProperty, value); 288 289 } 290 291 } 292 293 294 295 #endregion 296 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 /// <summary> 310 311 /// Gets or sets a template that is progressively revealed has the ScrollViewer is pulled down. 312 313 /// </summary> 314 315 public DataTemplate PullingDownTemplate 316 317 { 318 319 get 320 321 { 322 323 return (DataTemplate)this.GetValue(PullDownToRefreshPanel.PullingDownTemplateProperty); 324 325 } 326 327 set 328 329 { 330 331 this.SetValue(PullDownToRefreshPanel.PullingDownTemplateProperty, value); 332 333 } 334 335 } 336 337 338 339 #endregion 340 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 /// <summary> 354 355 /// Gets or sets the template that is displayed after the ScrollViewer is pulled down past 356 357 /// the PullThreshold. 358 359 /// </summary> 360 361 public DataTemplate ReadyToReleaseTemplate 362 363 { 364 365 get 366 367 { 368 369 return (DataTemplate)this.GetValue(PullDownToRefreshPanel.ReadyToReleaseTemplateProperty); 370 371 } 372 373 set 374 375 { 376 377 this.SetValue(PullDownToRefreshPanel.ReadyToReleaseTemplateProperty, value); 378 379 } 380 381 } 382 383 384 385 #endregion 386 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 /// <summary> 400 401 /// Gets or sets the template that is displayed while the ScrollViewer is refreshing. 402 403 /// </summary> 404 405 public DataTemplate RefreshingTemplate 406 407 { 408 409 get 410 411 { 412 413 return (DataTemplate)this.GetValue(PullDownToRefreshPanel.RefreshingTemplateProperty); 414 415 } 416 417 set 418 419 { 420 421 this.SetValue(PullDownToRefreshPanel.RefreshingTemplateProperty, value); 422 423 } 424 425 } 426 427 428 429 #endregion 430 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<ScrollViewer>(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 #endregion 482 483 484 485 #region Pull-down detection 下拉检测 486 487 488 489 /// <summary> 490 491 /// 当滚动条被拖拽时,显示动画和对应的控件模型 492 493 /// </summary> 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 else 536 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 /// <summary> 556 557 /// 当松手时检查是否要刷新 558 559 /// </summary> 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 #endregion 604 605 606 607 #region Utility methods 查找第一个符合要求的元素方法 608 609 610 611 /// <summary> 612 613 /// 查找容器内所有元素,并返回第一个是T类型的元素 614 615 /// </summary> 616 617 /// <typeparam name="T">要搜索的元素类型</typeparam> 618 619 /// <param name="initial">要搜索的容器</param> 620 621 /// <returns>返回要找的元素,没找到返回null</returns> 622 623 private static T FindVisualElement<T>(DependencyObject container) where T : DependencyObject 624 625 { 626 627 Queue<DependencyObject> childQueue = new Queue<DependencyObject>(); 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 #endregion 678 679 }
4、实现自定义ListBox->CustListBox,代码很简单直接上。
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 DependencyProperty 22 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 AutoScrollMargin 32 33 { 34 35 get 36 37 { 38 39 return (int)this.GetValue(CustListBox.AutoScrollMarginProperty); 40 41 } 42 43 set 44 45 { 46 47 this.SetValue(CustListBox.AutoScrollMarginProperty, value); 48 49 } 50 51 } 52 53 54 55 #endregion 56 57 }
5、使用要求
包含PullDwonReflesh的容器,必须直接或间接的包含滚动条。
例如:一个StackPanel包含一个PullDownToRefreshPanel 和一个ListBox,而ListBox内部包含一个ScrollViewer来实现子项的显示。
6、为了使用起来更方便,我把CustListBox和PullDownToRefreshPanel 又封装了一层
xaml
1 <UserControl x:Class="PullDwonReflesh.Themes.CustListbox" 2 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 5 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 6 7 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 8 9 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 10 11 mc:Ignorable="d" 12 13 FontFamily="{StaticResource PhoneFontFamilyNormal}" 14 15 FontSize="{StaticResource PhoneFontSizeNormal}" 16 17 Foreground="{StaticResource PhoneForegroundBrush}" 18 19 d:DesignHeight="480" d:DesignWidth="480" xmlns:my="clr-namespace:PullDwonReflesh" 20 21 22 23 xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit" 24 25 x:Name="this"> 26 27 28 29 <Grid x:Name="LayoutRoot"> 30 31 <Grid.RowDefinitions> 32 33 <RowDefinition Height="Auto" /> 34 35 <RowDefinition Height="*" /> 36 37 </Grid.RowDefinitions> 38 39 <my:PullDownToRefreshPanel x:Name="refreshPanel" RefreshRequested="refreshPanel_RefreshRequested" Grid.Row="0" /> 40 41 <my:CustListBox x:Name="custListBox" Grid.Row="1" Margin="12,0,12,12" toolkit:TiltEffect.IsTiltEnabled="True" 42 43 ItemsSource="{Binding ElementName=this, Path=ItemsSource}" 44 45 ItemTemplate="{Binding ElementName=this,Path=ItemTemplate}" 46 47 SelectionChanged="CustListBox_SelectionChanged" > 48 49 <!--<ListBox.ItemsPanel> 50 51 <ItemsPanelTemplate> 52 53 <toolkit:WrapPanel/> 54 55 </ItemsPanelTemplate> 56 57 </ListBox.ItemsPanel> 58 59 <ListBox.ItemContainerStyle> 60 61 <Style TargetType="ListBoxItem"> 62 63 <Setter Property="HorizontalContentAlignment" Value="Stretch"/> 64 65 </Style> 66 67 </ListBox.ItemContainerStyle>--> 68 69 </my:CustListBox> 70 71 </Grid> 72 73 </UserControl>
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 get 102 103 { 104 105 return (bool)this.GetValue(CustListbox.IsRefreshingProperty); 106 107 } 108 109 set 110 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保存起来,并在这里作为目标滚动条。
然后编写获得状态函数,如下
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<VisualStateChangedEventArgs>(visualStateGroup_CurrentStateChanged); 26 27 } 28 29 } 30 31 }
本文参考:http://www.hugwp.com/thread-2058-1.html
和Jason Ginchereau的博客
源码:猛击下载
本文版权归作者和卤面网所有,
转载请标明原始出处