看了看原生UWP的ScrollViewer,滑動很流暢(例如 開始菜單),可是WPF自帶的ScrollViewer滾動十分生硬..ide
突發奇想,今天來實現一個流暢滾動的ScrollViewer.函數
查閱網上的實現方法,要麼直接重寫控件,要麼一堆Storyboard..非常無奈,還有些許bug.性能
主要目標以下:優化
1.可以流暢地滾動ScrollViewer 要求:有慣性的物理滾動 而不是 生硬的 內容滾動.動畫
2.在使用動畫的過程當中,避免多個begin致使卡頓編碼
3.好用(? )spa
實現以上目標須要到的幾件東西: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); }
//繼承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