列表控件是應用程序中常見的控件之一,對其做一些絢麗的視覺特效,可以讓軟件增色不少。
本人網上看過一個視頻,是windows phone 7系統上的一個App的列表滾動效果,效果非常炫
現在在WPF上用ListBox重現此效果
首先我們來分析一下,這種實時滾動的效果是如何實現的,有哪些步驟
1.獲取ListBox模板內部的ScrollViewer和ItemsPanel
2.監聽ScrollViewer的滾動事件ScrollChange, 獲取ItemsPanel的布局方向
3.在滾動事件發生時計算當前可視化區域中的第一項和最后一項,這是此滑動效果的核心算法所在,算法的效率決定了滑動效果的流暢性
4.根據滾動的方向和布局的方向依次對指定的Item做動畫效果。
?
重寫ListBoxItem
public class PowerListBoxItem : ListBoxItem
聲明構造函數并賦初始值
static PowerListBoxItem(){DefaultStyleKeyProperty.OverrideMetadata(typeof(PowerListBoxItem), new FrameworkPropertyMetadata(typeof(PowerListBoxItem)));}public PowerListBoxItem(){ItemStatus = ItemStatusEnum.Out; //默認Item狀態為"退出"duration = new TimeSpan(0, 0, 0, 0, 300);//easingFunction = new PowerEase() { EasingMode = EasingMode.EaseIn, Power = 4 };easingFunction = new CircleEase() { EasingMode = EasingMode.EaseInOut };}
定義PowerListBoxItem的成員屬性
/// <summary>/// PowerListBoxItem模板中的內容控件/// </summary>private FrameworkElement contentControl;/// <summary>/// 動畫間隔時間/// </summary>private TimeSpan duration;private IEasingFunction easingFunction; //動畫緩動函數private IList<AnimationModel> DownInAnimationList; //定義Item從下往上運動的動畫內容集合private IList<AnimationModel> UpInAnimationList; //定義Item從上往下運動的動畫內容集合/// <summary>/// 項枚舉狀態,指明Item運動的方向/// </summary>internal enum ItemStatusEnum{UpIn, DownIn, RightIn, LeftIn, Out}private ItemStatusEnum _itemStatus;internal ItemStatusEnum ItemStatus{get { return _itemStatus; }set{if (_itemStatus == value) //狀態相同時不再刷新狀態return;_itemStatus = value;PlayAnimation(); //執行動畫}}
重寫ListBox?
[StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(PowerListBoxItem))]public class PowerListBox : ListBox{static PowerListBox(){DefaultStyleKeyProperty.OverrideMetadata(typeof(PowerListBox), new FrameworkPropertyMetadata(typeof(PowerListBox)));}public PowerListBox(){DefaultStyleKey = typeof(PowerListBox);}}protected override DependencyObject GetContainerForItemOverride(){return new PowerListBoxItem(); //指定PowerListBox的項為PowerListBoxItem}protected override bool IsItemItsOwnContainerOverride(object item){return item is PowerListBoxItem;}
定義PowerList的成員屬性
/// <summary>/// ListBox內部的滾動試圖/// </summary>private ScrollViewer _scrollView;/// <summary>/// 容器的布局方向/// </summary>private Orientation _panelOrientation;/// <summary>/// 當前可視化視圖的第一項/// </summary>private int firstVisibleIndex;/// <summary>/// 當前可視化視圖的最后一項/// </summary>private int lastVisibleIndex;/// <summary>/// 上次滾動時可視化視圖的第一項/// </summary>private int oldFirstVisibleIndex;/// <summary>/// 上次滾動時可視化視圖的最后一項/// </summary>private int oldLastVisibleIndex;/// <summary>/// 標識,是否已找到第一項/// </summary>private bool isFindFirst;/// <summary>/// 當前累計已遍歷過的Item高度或寬度的值,用于尋找第一項和最后一項/// </summary>private double cumulativeNum;
獲取PowerListBox內部的ScrollViewer和ItemsPanel,并監聽滾動事件
public override void OnApplyTemplate(){_scrollView = VisualHelper.FindFirstVisualChild<ScrollViewer>(this);if (_scrollView == null)return;_scrollView.CanContentScroll = false; //不按Item為步長滾動_scrollView.PanningMode = PanningMode.Both;_scrollView.ScrollChanged += _scrollView_ScrollChanged; //監聽滾動事件var panel = this.ItemsPanel.LoadContent(); //讀取布局容器if (panel is StackPanel)_panelOrientation = (panel as StackPanel).Orientation;else if (panel is VirtualizingPanel)_panelOrientation = (panel as VirtualizingStackPanel).Orientation;base.OnApplyTemplate();}private void _scrollView_ScrollChanged(object sender, ScrollChangedEventArgs e){//Console.WriteLine("itemCount:{0} VerticalOffset:{1} ViewportHeight:{2} ContentVerticalOffset:{3}",//Items.Count, _scrollView.VerticalOffset, _scrollView.ViewportHeight, _scrollView.ContentVerticalOffset);
//每次滾動時都計算當前可視化區域的首尾項calculationIndex(); refreshItemStatus(); //刷新Item狀態}
計算可視化區域的第一項和最后一項
private void calculationIndex(){oldFirstVisibleIndex = firstVisibleIndex;oldLastVisibleIndex = lastVisibleIndex;isFindFirst = false;if (_panelOrientation == Orientation.Vertical){cumulativeNum = 0.0;for (int i = 0; i < Items.Count; i++){var _item = this.ItemContainerGenerator.ContainerFromIndex(i) as PowerListBoxItem;cumulativeNum += _item.ActualHeight + _item.Margin.Top + _item.Margin.Bottom;//遍歷Items, 累計Item高度,第一個超過滾動條垂直偏移量的Item就是當前可視化區域中的第一項if (!isFindFirst && cumulativeNum >= _scrollView.VerticalOffset){firstVisibleIndex = i;isFindFirst = true;}//累計Item高度超過滾動條垂直偏移量和滾動區顯示高度的和,就是當前可視化區域的最后一項if (cumulativeNum >= (_scrollView.VerticalOffset + _scrollView.ViewportHeight)){lastVisibleIndex = i;break;}}}}
確定當前可視化區域的首尾項之后,刷新Item的狀態
private void refreshItemStatus(){Console.WriteLine("firstIndex: {0} lastIndex: {1} oldFirstIndex: {2} oldLastIndex: {3} {4}",firstVisibleIndex, lastVisibleIndex, oldFirstVisibleIndex, oldLastVisibleIndex, firstVisibleIndex > oldFirstVisibleIndex ? "Down In" : firstVisibleIndex < oldFirstVisibleIndex ? "UpIn" : "normal");if ((firstVisibleIndex == oldFirstVisibleIndex && lastVisibleIndex == oldLastVisibleIndex) || oldFirstVisibleIndex == 0 && oldLastVisibleIndex == 0)return;//Console.WriteLine("firstVisibleIndex:{0} oldFirstVisibleIndex:{1}", firstVisibleIndex, oldFirstVisibleIndex);//判斷滾動方向if (firstVisibleIndex > oldFirstVisibleIndex){//垂直 滾動條往下,內容網上//水平 滾動條往右,內容往左for (var i = oldLastVisibleIndex; i <= lastVisibleIndex; i++){var _item = this.ItemContainerGenerator.ContainerFromIndex(i) as PowerListBoxItem;_item.ItemStatus = _panelOrientation == Orientation.Vertical ? PowerListBoxItem.ItemStatusEnum.DownIn : PowerListBoxItem.ItemStatusEnum.RightIn;//Console.WriteLine("DownIn {0}", i);}}else if (lastVisibleIndex < oldLastVisibleIndex){//垂直 滾動條往上,內容網下//水平 滾動條往左,內容往右for (var i = oldFirstVisibleIndex; i >= firstVisibleIndex; i--){var _item = this.ItemContainerGenerator.ContainerFromIndex(i) as PowerListBoxItem;_item.ItemStatus = _panelOrientation == Orientation.Vertical ? PowerListBoxItem.ItemStatusEnum.UpIn : PowerListBoxItem.ItemStatusEnum.LeftIn;//Console.WriteLine("UpIn {0}", i);}}}
定義PowerListBox的默認外觀
<Style TargetType="{x:Type local:PowerListBox}"><Setter Property="Background" Value="Transparent"/><Setter Property="BorderThickness" Value="0"/><Setter Property="BorderBrush" Value="Transparent"/><Setter Property="Padding" Value="0"/><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="{x:Type local:PowerListBox}"><ScrollViewer x:Name="ScrollViewer" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Foreground="{TemplateBinding Foreground}" Padding="{TemplateBinding Padding}"><ItemsPresenter/></ScrollViewer></ControlTemplate></Setter.Value></Setter></Style><Style TargetType="{x:Type local:PowerListBoxItem}"><Setter Property="Background" Value="Transparent"/><Setter Property="BorderThickness" Value="0"/><Setter Property="BorderBrush" Value="Transparent"/><Setter Property="Padding" Value="0"/><Setter Property="HorizontalContentAlignment" Value="Stretch"/><Setter Property="VerticalContentAlignment" Value="Stretch"/><Setter Property="Margin" Value="0,8"/><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="{x:Type local:PowerListBoxItem}"><Border x:Name="LayoutRoot" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}"><ContentControl x:Name="ContentContainer" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Foreground="{TemplateBinding Foreground}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" RenderTransformOrigin="0.5,0.5"><ContentControl.RenderTransform><TransformGroup><TranslateTransform/></TransformGroup></ContentControl.RenderTransform></ContentControl></Border></ControlTemplate></Setter.Value></Setter></Style>
調用 PowerListBox
<local:PowerListBox ItemsSource="{Binding TestModelList}" ><local:PowerListBox.ItemTemplate><DataTemplate><Grid><Grid.ColumnDefinitions><ColumnDefinition Width="150"/><ColumnDefinition/></Grid.ColumnDefinitions><TextBlock Text="{Binding Name}" VerticalAlignment="Center" FontSize="20"/><Border Width="100" Height="120" Background="#FF4949D3" Grid.Column="1" HorizontalAlignment="Left"><TextBlock Text="{Binding Id}" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="40" Foreground="Black"/></Border></Grid></DataTemplate></local:PowerListBox.ItemTemplate>
</local:PowerListBox>
效果圖
?
?由于gif錄制幀數的原因,效果圖不是很流暢,但實際運行情況動畫效果是非常流暢的