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

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

WPF自定义控件之列表滑动特效 PowerListBox的更多相关文章

  1. uwp ListView列表滑动特效

    在看过一篇文章 WPF自定义控件之列表滑动特效 PowerListBox  http://www.cnblogs.com/ShenNan/p/4993374.html#3619585 实现了滑动的特效 ...

  2. WPF自定义控件与样式(7)-列表控件DataGrid与ListView自定义样式

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要内容: Dat ...

  3. WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要针对WPF项目 ...

  4. 【转】WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要针对WPF项目 ...

  5. 【转】WPF自定义控件与样式(7)-列表控件DataGrid与ListView自定义样式

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等. 本文主要内容: DataGrid自定义样式: ListView自定义样式: 二.Dat ...

  6. WPF自定义控件与样式(6)-ScrollViewer与ListBox自定义样式

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要内容: Scr ...

  7. 【转】WPF自定义控件与样式(6)-ScrollViewer与ListBox自定义样式

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等. 本文主要内容: ScrollViewer的样式拆解及基本样式定义: ListBox集合 ...

  8. WPF自定义控件与样式(1)-矢量字体图标(iconfont)

    一.图标字体 图标字体在网页开发上运用非常广泛,具体可以网络搜索了解,网页上的运用有很多例子,如Bootstrap.但在C/S程序中使用还不多,字体图标其实就是把矢量图形打包到字体文件里,就像使用一般 ...

  9. WPF自定义控件与样式(2)-自定义按钮FButton

    一.前言.效果图 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 还是先看看效果 ...

随机推荐

  1. PyDev for eclipse 插件下载地址

    PyDev for eclipse 插件下载地址http://sourceforge.net/projects/pydev/files/pydev/python解释器以及python类库下载地址htt ...

  2. windows解压缩版MySQL5.6.40的安装

    windows解压缩版MySQL5.6.40的安装 安装步骤: 1.下载mysql-5.6.40-winx64.zip https://cdn.mysql.com//Downloads/MySQL-5 ...

  3. 值得推荐的开源C/C++框架和库

    值得学习的C语言开源项目   - 1. Webbench Webbench是一个在linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工 ...

  4. 迷你MVVM框架 avalonjs 学习教程3、绑定属性与扫描机制

    在MVVM框架中,你都会看到页面定了许多奇怪的属性,比如knockout的data-☆,angular的ng-☆,avalon的ms-☆,此外还有一些只写文本节点上的双花括号,它们统称为指令.ms-☆ ...

  5. Maven编译并打包Mahout CDH版源码

    目录 1. 问题描述 最近在使用Mahout里的推荐算法进行实验,由于业务需求,需要修改Mahout源码,将原本输出到HDFS上的结果输出到HBase中.由于Mahout发布的源码都是Maven项目, ...

  6. clr相关名词

    程序集:一个或多个类型定义文件和资源文件的集合 Native Code(本机代码): 已被编译为特定于处理器的机器码的代码. 本地代码(native code)是计算机编程(代码),编译用来运行一个特 ...

  7. 【转载】Javascript里面的线程和异步

    JavaScript引擎是单线程运行的,浏览器无论在什么时候都只且只有一个线程在运行JavaScript程序. 参考这篇文章 http://www.ruanyifeng.com/blog/2012/1 ...

  8. requests.session之set trust_env to disable environment searches for proxies

    import requests s = requests.Session() s.trust_env = False This will prevent requests getting any in ...

  9. python的return

    关于python的return用法,在stackoverflow里的问题: Python — return, return None, and no return at all Consider th ...

  10. mysql 截取字符串

    #select SUBSTRING_INDEX('1,2', ',', 1); SELECT * FROM dis_disease WHERE id = ( SELECT SUBSTRING_INDE ...