WPF 如何流暢地滾動ScrollViewer 簡單實現下

看了看原生UWP的ScrollViewer,滑動很流暢(例如 開始菜單),可是WPF自帶的ScrollViewer滾動十分生硬..ide

突發奇想,今天來實現一個流暢滾動的ScrollViewer.函數

1、目標

查閱網上的實現方法,要麼直接重寫控件,要麼一堆Storyboard..非常無奈,還有些許bug.性能

主要目標以下:優化

1.可以流暢地滾動ScrollViewer   要求:有慣性的物理滾動 而不是 生硬的 內容滾動.動畫

2.在使用動畫的過程當中,避免多個begin致使卡頓編碼

3.好用(? )spa

2、準備工做

實現以上目標須要到的幾件東西:code

1.對於Listbox之類的控件須要先講滾動方式變爲 像素滾動  (若沒有的話就能夠不設置)orm

<ListBox
VirtualizingPanel.ScrollUnit = "Pixel" 
VirtualizingPanel.VirtualizationMode="Recycling"/>

 

2.選一種動畫使用的 緩動函數 這裏推薦使用 CubicEase的EaseOut函數 (優勢:接近於慣性滾動  對於CPU比較友好)對象

 

3.要對ScrollViewer啓用滾動動畫 須要本身寫依賴屬性:

public static class ScrollViewerBehavior
{
      public static readonly DependencyProperty VerticalOffsetProperty = DependencyProperty.RegisterAttached("VerticalOffset", typeof(double), typeof(ScrollViewerBehavior), new UIPropertyMetadata(0.0, OnVerticalOffsetChanged));
      public static void SetVerticalOffset(FrameworkElement target, double value) => target.SetValue(VerticalOffsetProperty, value);
      public static double GetVerticalOffset(FrameworkElement target) => (double)target.GetValue(VerticalOffsetProperty);
      private static void OnVerticalOffsetChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) => (target as ScrollViewer)?.ScrollToVerticalOffset((double)e.NewValue);
}

 

3、編碼

    //繼承ScrollViewer 經過截獲MouseWheel事件控制滾動
    public class MyScrollViewer : ScrollViewer
    {
        //記錄上一次的滾動位置
        private double LastLocation = 0;
        //重寫鼠標滾動事件
        protected override void OnMouseWheel(MouseWheelEventArgs e)
        {
            double WheelChange = e.Delta;
            //能夠更改一次滾動的距離倍數 (WheelChange可能爲正負數!)
            double newOffset = LastLocation - (WheelChange * 2);
            //Animation並不會改變真正的VerticalOffset(只是它的依賴屬性) 因此將VOffset設置到上一次的滾動位置 (至關於銜接上一個動畫)
            ScrollToVerticalOffset(LastLocation);
            //碰到底部和頂部時的處理
            if (newOffset < 0)
                newOffset = 0;
            if (newOffset > ScrollableHeight)
                newOffset = ScrollableHeight;

            AnimateScroll(newOffset);
            LastLocation = newOffset;
            //告訴ScrollViewer咱們已經完成了滾動
            e.Handled = true;
        }
        private void AnimateScroll(double ToValue)
        {
            //爲了不重複,先結束掉上一個動畫
            BeginAnimation(ScrollViewerBehavior.VerticalOffsetProperty, null);
            DoubleAnimation Animation = new DoubleAnimation();
            Animation.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut };
            Animation.From = VerticalOffset;
            Animation.To = ToValue;
            //動畫速度
            Animation.Duration = TimeSpan.FromMilliseconds(800);
            //考慮到性能,能夠下降動畫幀數
            //Timeline.SetDesiredFrameRate(Animation, 40);
            BeginAnimation(ScrollViewerBehavior.VerticalOffsetProperty, Animation);
        }
    }

使用方法:直接建立 <MyScrollViewer/>

若是是在ListBox中,能夠經過修改模板的方式,把<ScrollViewer/>換成MyScrollViewer便可.

4、關於性能

按照上述方法實現的功能,CPU佔用率基本穩定在10% ,可是要瘋狂地上下滑動滾輪的話,25%+   可是動畫仍是很流暢的

如下有幾點能夠改進的措施:

滾動的CPU佔用不僅是對偏移量的一個計算,與你滾動的UI畫面內容具備很大的關係

例如大量的圖片Image   3D對象    複雜的自定義控件 等等 都須要在滾動時計算並繪製。

你能夠嘗試如下方法:

     1).啓用ListBox的虛擬化技術

     2).對於圖像的繪製,能夠經過下降其渲染度(可能會損壞圖片質量 特別是低清小圖 ),對你的圖片對象使用:

RenderOptions.SetBitmapScalingMode(控件對象,BitmapScalingMode.LowQuality);

     3).就像上文中所說,能夠經過下降渲染幀數來控制CPU佔用率(可能會有失流暢度)

     4).若是能找到更好的緩動函數

     5).參閱: 優化控件性能 -WPF |Microsoft Docs

相關文章
相關標籤/搜索