1. 程式人生 > WINDOWS開發 >WPF 如何流暢地滾動ScrollViewer 簡單實現下

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

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

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

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

一、目標

查閱網上的實現方法,要麼直接重寫控制元件,要麼一堆Storyboard..很是無奈,還有些許bug.

主要目標如下:

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

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

3.好用(? )

二、準備工作

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

1.對於Listbox之類的控制元件需要先講滾動方式變為 畫素滾動 (若沒有的話就可以不設定)

<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); }
技術分享圖片

三、編碼

技術分享圖片
    //繼承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即可.

四、關於效能

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

以下有幾點可以改進的措施:

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

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

你可以嘗試以下方法:

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

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

RenderOptions.SetBitmapScalingMode(控制元件物件,BitmapScalingMode.LowQuality);

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

4).如果能找到更好的緩動函式

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