• 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录制帧数的原因,效果图不是很流畅,但实际运行情况动画效果是非常流畅的

  • 相关阅读:
    如何发布自定义的UI 组件库到 npmjs.com 并且编写 UI组件说明文档
    Go 包导入备忘
    Incorrect column count: expected 1, actual 5,JdbcTemplate queryForList 出错
    SpringJdbc持久层封装,Spring jdbcTemplate封装,springJdbc泛型Dao,Spring baseDao封装
    Java最快的maven仓库地址,国内Maven地址,超快的Maven地址
    SQLyog-12.4.2版下载,SQLyog最新版下载,SQLyog官网下载,SQLyog Download
    easyui datebox定位到某一个日期, easyui datebox直接定位到具体的日期, easyui datebox MoveTo方法使用
    Echarts调整图表上下左右的间距,Echarts调整柱状图左右的间距
    Spring整合Quartz定时任务执行2次,Spring定时任务执行2次
    Linux关闭Tomcat为什么要用Kill,而不是shutdown.sh
  • 原文地址:https://www.cnblogs.com/ShenNan/p/4993374.html
Copyright © 2020-2023  润新知