你的位置:首页 > ASP.net教程

[ASP.net教程]WPF自定义控件之列表滑动特效 PowerListBox


列表控件是应用程序中常见的控件之一,对其做一些绚丽的视觉特效,可以让软件增色不少。

本人网上看过一个视频,是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录制帧数的原因,效果图不是很流畅,但实际运行情况动画效果是非常流畅的