原文:
UWP 下拉刷新控件(PullToRefreshControl)
最近项目里面有下拉刷新的需求,自己做了一个,效果还不错。
<Style TargetType="local:PullToRefreshControl"> <Setter Property="HeaderTemplate"> <Setter.Value> <DataTemplate> <Grid> <StackPanel VerticalAlignment="Center" Orientation="Horizontal" Visibility="{Binding IsReachThreshold,Converter={StaticResource InversedBooleanToVisibilityConverter}}"> <FontIcon FontSize="30" FontFamily="Segoe UI Emoji" Glyph="↓" IsHitTestVisible="False" VerticalAlignment="Bottom"/> <TextBlock Margin="5,0,5,0" Text="下拉刷新" VerticalAlignment="Bottom"/> </StackPanel> <StackPanel VerticalAlignment="Center" Orientation="Horizontal" Visibility="{Binding IsReachThreshold, Converter={StaticResource BooleanToVisibilityConverter}}"> <FontIcon FontSize="30" FontFamily="Segoe UI Emoji" Glyph="↑" IsHitTestVisible="False" VerticalAlignment="Bottom"/> <TextBlock Margin="5,0,5,0" Text="释放立即刷新" VerticalAlignment="Bottom"/> </StackPanel> </Grid> </DataTemplate> </Setter.Value> </Setter> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:PullToRefreshControl"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Margin="{TemplateBinding Margin}"> <ScrollViewer x:Name="ScrollViewer" VerticalScrollBarVisibility="Hidden"> <StackPanel> <ContentControl x:Name="PanelHeader" ContentTemplate="{TemplateBinding HeaderTemplate}" HorizontalContentAlignment="Center" VerticalContentAlignment="Bottom" /> <ContentPresenter x:Name="PanelContent" ContentTemplate="{TemplateBinding ContentTemplate}" ContentTransitions="{TemplateBinding ContentTransitions}" Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> </StackPanel> </ScrollViewer> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>
[TemplatePart(Name = PanelHeader, Type = typeof(ContentControl))] [TemplatePart(Name = PanelContent, Type = typeof(ContentPresenter))] [TemplatePart(Name = ScrollViewer, Type = typeof(ScrollViewer))] public class PullToRefreshControl:ContentControl { #region Fields private const string PanelHeader = "PanelHeader"; private const string PanelContent = "PanelContent"; private const string ScrollViewer = "ScrollViewer"; private ContentControl _panelHeader; private ContentPresenter _panelContent; private ScrollViewer _scrollViewer; #endregion #region Property /// <summary> /// The threshold for release to refresh,defautl value is 2/5 of PullToRefreshPanel's height. /// </summary> public double RefreshThreshold { get { return (double)GetValue(RefreshThresholdProperty); } set { SetValue(RefreshThresholdProperty, value); } } // Using a DependencyProperty as the backing store for RefreshThreshold. This enables animation, styling, binding, etc... public static readonly DependencyProperty RefreshThresholdProperty = DependencyProperty.Register("RefreshThreshold", typeof(double), typeof(PullToRefreshControl), new PropertyMetadata(0.0,new PropertyChangedCallback(OnRefreshThresholdChanged))); private static void OnRefreshThresholdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var pullToRefreshControl = d as PullToRefreshControl; pullToRefreshControl.UpdateContentGrid(); } /// <summary> /// occur when reach threshold. /// </summary> public event EventHandler PullToRefresh; public DataTemplate HeaderTemplate { get { return (DataTemplate)GetValue(HeaderTemplateProperty); } set { SetValue(HeaderTemplateProperty, value); } } // Using a DependencyProperty as the backing store for HeaderTemplate. This enables animation, styling, binding, etc... public static readonly DependencyProperty HeaderTemplateProperty = DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(PullToRefreshControl), new PropertyMetadata(null)); public bool IsReachThreshold { get { return (bool)GetValue(IsReachThresholdProperty); } set { SetValue(IsReachThresholdProperty, value); } } // Using a DependencyProperty as the backing store for IsReachThreshold. This enables animation, styling, binding, etc... public static readonly DependencyProperty IsReachThresholdProperty = DependencyProperty.Register("IsReachThreshold", typeof(bool), typeof(PullToRefreshControl), new PropertyMetadata(false)); #endregion protected override void OnApplyTemplate() { _panelHeader = GetTemplateChild(PanelHeader) as ContentControl; _panelHeader.DataContext = this; _panelContent = GetTemplateChild(PanelContent) as ContentPresenter; _scrollViewer = GetTemplateChild(ScrollViewer) as ScrollViewer; _scrollViewer.ViewChanged += _scrollViewer_ViewChanged; base.OnApplyTemplate(); } private void _scrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e) { //Sometime we can't make it to 0.0. IsReachThreshold = _scrollViewer.VerticalOffset <= 5.0; if (e.IsIntermediate) { return; } if (IsReachThreshold) { if (PullToRefresh!=null) { PullToRefresh(this, null); } } _panelHeader.Height = RefreshThreshold > _panelHeader.ActualHeight ? RefreshThreshold : _panelHeader.ActualHeight; this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { _scrollViewer.ChangeView(null, _panelHeader.Height, null); }); } public PullToRefreshControl() { this.DefaultStyleKey = typeof(PullToRefreshControl); this.Loaded +=(s,e)=> { if (RefreshThreshold == 0.0) { RefreshThreshold = this.ActualHeight * 2 / 5.0; } UpdateContentGrid(); }; this.SizeChanged += (s, e) => { if (RefreshThreshold==0.0) { RefreshThreshold = this.ActualHeight *2 / 5.0; } UpdateContentGrid(); }; } #region Method private void UpdateContentGrid() { if (_scrollViewer != null && _panelContent!=null && _panelHeader !=null) { _panelHeader.Height = RefreshThreshold > _panelHeader.ActualHeight? RefreshThreshold: _panelHeader.ActualHeight; this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { _scrollViewer.ChangeView(null, _panelHeader.Height, null, true); }); _panelContent.Width = this.ActualWidth; _panelContent.Height = this.ActualHeight; } } #endregion }