看了看原生UWP的ScrollViewer,滑動很流暢(例如 開始菜單),但是WPF自帶的ScrollViewer滾動十分生硬.. 突發奇想,今天來實現一個流暢滾動的ScrollViewer. 一、目標 查閱網上的實現方法,要麼直接重寫控制項,要麼一堆Storyboard..很是無奈,還有些許bug. ...
看了看原生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