天天看点

WPF实现ScrollViewer平滑滚动效果

WPF实现滚动条只要在控件外围加上ScrollViewer即可,但是滚动的时候没有动画效果,比较生硬,在滚动的时候添加过渡动画实现平滑滚动能给我们的软件增色不少。

接下来,在上一篇博客(WPF使用FlowDocument加载文本并修改文本样式https://blog.csdn.net/dnazhd/article/details/89307386)的基础上进行修改,这里只展示垂直滚动效果,水平滚动方式同理。为了更加明显的展示效果,先将协议文本内容添加的更多些。

原始滚动效果:

WPF实现ScrollViewer平滑滚动效果

修改后效果:

WPF实现ScrollViewer平滑滚动效果

修改流程:

1.新建SmoothScroll文件夹,并新建SmoothScrollViewer.cs类,继承自ScrollViewer,注册步进长度依赖属性,代码如下:

using System.Windows;
using System.Windows.Controls;

namespace RichTextBoxDemo.SmoothScroll
{
    public class SmoothScrollViewer : ScrollViewer
    {
        /// <summary>
        /// 垂直归一化步进长度
        /// </summary>
        public double VerticalScrollRatio
        {
            get { return (double)GetValue(VerticalScrollRatioProperty); }
            set { SetValue(VerticalScrollRatioProperty, value); }
        }

        //注册VerticalScrollRatio依赖属性
        public static readonly DependencyProperty VerticalScrollRatioProperty =
            DependencyProperty.Register("VerticalScrollRatio", typeof(double), typeof(SmoothScrollViewer), new PropertyMetadata(0.0, new PropertyChangedCallback(V_ScrollRatioChangedCallBack)));

        private static void V_ScrollRatioChangedCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var scrollViewer = (ScrollViewer)(d);
            if (scrollViewer != null)
            {
                scrollViewer.ScrollToVerticalOffset((double)(e.NewValue) * scrollViewer.ScrollableHeight);
            }
        }

        /// <summary>
        /// 水平归一化步进长度
        /// </summary>
        public double HorizontalScrollRatio
        {
            get { return (double)GetValue(HorizontalScrollRatioProperty); }
            set { SetValue(HorizontalScrollRatioProperty, value); }
        }

        //注册HorizontalScrollRatio依赖属性
        public static readonly DependencyProperty HorizontalScrollRatioProperty =
            DependencyProperty.Register("HorizontalScrollRatio", typeof(double), typeof(SmoothScrollViewer), new PropertyMetadata(0.0, new PropertyChangedCallback(H_ScrollRatioChangedCallBack)));

        private static void H_ScrollRatioChangedCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var scrollViewer = (ScrollViewer)(d);
            if (scrollViewer != null)
            {
                scrollViewer.ScrollToHorizontalOffset((double)(e.NewValue) * scrollViewer.ScrollableWidth);
            }
        }
    }
}
           

2、在SmoothScroll文件夹,新建ScrollViewerExtend.cs类,实现平滑滚动功能,代码如下:

using System;
using System.Windows;
using System.Windows.Media.Animation;

namespace RichTextBoxDemo.SmoothScroll
{
    public static class ScrollViewerExtend
    {
        /// <summary>
        /// 实现ScrollViewer的平滑滚动
        /// </summary>
        /// <param name="scrollViewer"></param>
        /// <param name="ScrollStepRatio">归一化步进长度</param>
        /// <param name="ScrollPositionRatio">归一化位置</param>
        /// <param name="direction">滚动方向</param>
        public static void SmoothScroll(this SmoothScrollViewer scrollViewer, double ScrollStepRatio, double ScrollPositionRatio, ScrollDirection direction)
        {
            if (double.IsNaN(ScrollStepRatio) || double.IsNaN(ScrollPositionRatio))
                return;
            DoubleAnimation Animation = new DoubleAnimation();

            Animation.From = ScrollPositionRatio;
            if (ScrollDirection.Down == direction || ScrollDirection.Right == direction)
            {
                double To = ScrollPositionRatio + ScrollStepRatio;
                Animation.To = To > 0.95 ? 1.0 : To;//向下(右)滚动补偿
            }
            else if (ScrollDirection.Up == direction || ScrollDirection.Left == direction)
            {
                double To = ScrollPositionRatio - ScrollStepRatio;
                Animation.To = To < 0.05 ? 0.0 : To;//向上(左)滚动补偿
            }
            Animation.Duration = new Duration(TimeSpan.FromMilliseconds(500));

            Storyboard storyboard = new Storyboard();
            storyboard.Children.Add(Animation);
            Storyboard.SetTarget(Animation, scrollViewer);
            if (ScrollDirection.Down == direction || ScrollDirection.Up == direction)
                Storyboard.SetTargetProperty(Animation, new PropertyPath(SmoothScrollViewer.VerticalScrollRatioProperty));
            else if (ScrollDirection.Right == direction || ScrollDirection.Left == direction)
                Storyboard.SetTargetProperty(Animation, new PropertyPath(SmoothScrollViewer.HorizontalScrollRatioProperty));
            storyboard.Begin();
        }
    }

    public enum ScrollDirection
    {
        Up, Down, Left, Right
    }
}
           

3、主界面:

<Window x:Class="RichTextBoxDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:RichTextBoxDemo.SmoothScroll"
        mc:Ignorable="d"
        Title="RichTextBox" Height="600" Width="800" Loaded="Window_Loaded">
    <Grid>
        <local:SmoothScrollViewer x:Name="scrollviewer" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled"
                                  ScrollChanged="scrollviewer_ScrollChanged">
            <RichTextBox x:Name="showInfo" Margin="30" Background="Transparent" IsReadOnly="True" Focusable="False" Cursor="Arrow"></RichTextBox>
        </local:SmoothScrollViewer>
    </Grid>
</Window>
           

4、交互逻辑:

using RichTextBoxDemo.SmoothScroll;
using System;
using System.IO;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;

namespace RichTextBoxDemo
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        double m_ScrollStepRatio = 0.0;     //滚动条的归一化步进长度
        double m_ScrollPositionRatio = 0.0; //滚动条的归一化位置

        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            LoadDocument();
        }

        /// <summary>
        /// 加载txt文本
        /// </summary>
        private void LoadDocument()
        {
            string filePath = AppDomain.CurrentDomain.BaseDirectory + @"Disclaimer\Disclaimer.txt";//文件路径
            if(File.Exists(filePath))
            {
                FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);//以只读方式打开源文件
                try
                {
                    using (StreamReader sr = new StreamReader(fs))
                    {
                        FlowDocument document = new FlowDocument();//承载文件内容
                        string content = sr.ReadToEnd();
                        string[] split = content.Split(new char[2] { '\r', '\n' });
                        foreach (string para in split)
                        {
                            if(para != "")
                            {
                                Paragraph paragraph = new Paragraph(new Run(para));
                                paragraph.FontFamily = new FontFamily("微软雅黑");//修改样式
                                paragraph.Foreground = new SolidColorBrush(Colors.Black);
                                if (para == "免责声明")
                                {
                                    paragraph.FontSize = 26;
                                    paragraph.TextAlignment = TextAlignment.Center;
                                }
                                else
                                    paragraph.FontSize = 20;
                                document.Blocks.Add(paragraph);
                            }
                        }
                        this.showInfo.Document = document;
                    }
                }
                catch (Exception ex)
                {

                }
                finally
                {
                    fs.Close();
                }
            }
        }

        private void scrollviewer_ScrollChanged(object sender, System.Windows.Controls.ScrollChangedEventArgs e)
        {
            m_ScrollStepRatio = scrollviewer.ViewportHeight / (scrollviewer.ExtentHeight - scrollviewer.ViewportHeight);
            m_ScrollPositionRatio = scrollviewer.ContentVerticalOffset / scrollviewer.ScrollableHeight;
            if (e.VerticalChange < 0)
                scrollviewer.SmoothScroll(m_ScrollStepRatio, m_ScrollPositionRatio, ScrollDirection.Up);
            else if (e.VerticalChange > 0)
                scrollviewer.SmoothScroll(m_ScrollStepRatio, m_ScrollPositionRatio, ScrollDirection.Down);
        }
    }
}