列表控件是應用程序中常見的控件之一,對其作一些絢麗的視覺特效,能夠讓軟件增色很多。php
本人網上看過一個視頻,是windows phone 7系統上的一個App的列表滾動效果,效果很是炫html
如今在WPF上用ListBox重現此效果算法
首先咱們來分析一下,這種實時滾動的效果是如何實現的,有哪些步驟windows
1.獲取ListBox模板內部的ScrollViewer和ItemsPanelapp
2.監聽ScrollViewer的滾動事件ScrollChange, 獲取ItemsPanel的佈局方向ide
3.在滾動事件發生時計算當前可視化區域中的第一項和最後一項,這是此滑動效果的核心算法所在,算法的效率決定了滑動效果的流暢性函數
4.根據滾動的方向和佈局的方向依次對指定的Item作動畫效果。佈局
重寫ListBoxItem動畫
public class PowerListBoxItem : ListBoxItem
聲明構造函數並賦初始值this
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錄製幀數的緣由,效果圖不是很流暢,但實際運行狀況動畫效果是很是流暢的