WPF 控件庫系列博文地址:html
WPF 控件庫——仿製Chrome的ColorPickergit
WPF 控件庫——仿製Windows10的進度條github
1、先看看效果spa
2、原理code
雖然效果很簡單,可是網上的一些資料涉及的代碼量很是可觀,並且效果也不是很理想,滾動的時候沒有一個順滑感。我這裏提供的源碼一共120多行,就能實現上圖的效果。
htm
本質上咱們只要接管ScrollViewer的滾動邏輯,而且把這個邏輯替換成帶有慣性的便可,那麼如何去接管呢?這裏的關鍵是先屏蔽ScrollViewer的鼠標滾輪事件:blog
1 protected override void OnMouseWheel(MouseWheelEventArgs e) 2 { 3 e.Handled = true; 4 }
這樣一來,ScrollViewer就不會響應滾輪事件了,咱們就在這裏作文章。首先咱們給這個ScrollViewer添加一個屬性 IsEnableInertia ,用來控制是否使用慣性,由於蘿蔔青菜各有所愛,不要想着強制全部人使用慣性,因此滾輪響應方法變爲:
1 protected override void OnMouseWheel(MouseWheelEventArgs e) 2 { 3 if (!IsEnableInertia) 4 { 5 base.OnMouseWheel(e); 6 return; 7 } 8 e.Handled = true; 9 }
控制ScrollViewer的垂直滾動可使用 ScrollViewer.ScrollToVerticalOffset ,橫向也同樣。爲何不能用 VerticalOffset ?由於 VerticalOffset 在註冊的時候就說明了是隻讀的:
1 private static readonly DependencyPropertyKey VerticalOffsetPropertyKey = DependencyProperty.RegisterReadOnly(nameof (VerticalOffset), typeof (double), typeof (ScrollViewer), (PropertyMetadata) new FrameworkPropertyMetadata((object) 0.0)); 2 3 public static readonly DependencyProperty VerticalOffsetProperty = ScrollViewer.VerticalOffsetPropertyKey.DependencyProperty;
好了,接下來就是怎麼在滾輪響應方法中實現慣性運動了,也就是一種減速運動。想到這兒,熟悉動畫的博友很快就知道要用WPF的動畫來實現了,默認的動畫都是一次線性的,要有慣性效果就得用緩動函數,WPF的緩動函數有不少,而 CubicEase 很是適合用來作慣性,它的描述圖以下:
圖中,橫軸表示時間,縱軸表示運動距離。很明顯,中間的 EaseOut 模式就是咱們想要的。到了這裏思路就清晰了,咱們能夠定義一個屬性 CurrentVerticalOffset ,咱們會在它上面實現動畫,在它的值回調函數中調用 ScrollViewer.ScrollToVerticalOffset 來更新ScrollViewer的滾動位置。固然咱們還須要一個私有字段 _totalVerticalOffset ,這個是用來存放ScrollViewer滾動目標位置的,滾輪向下滾動一個單位咱們就給它減去一次 e.Delta ,這裏的e是滾輪響應方法傳進來的參數,每次給它賦值以後,就能夠在 CurrentVerticalOffset 上執行動畫了: BeginAnimation(CurrentVerticalOffsetProperty, animation) ,須要特別注意的是,當一個依賴屬性用了動畫改變後,再對其賦值則不會生效,緣由是在一個動畫到達活動期的終點後,時間線默認會保持其進度,直到其父級的活動期和保持期結束爲止。若是想在動畫結束後還能夠手動更改依賴屬性的值,則須要把 FillBehavior 設置爲Stop。不過這樣又會出現一個問題,一旦動畫結束,這個依賴屬性又會恢復初始值,因此還要給這個動畫訂閱一個 Completed 事件,在事件響應方法中爲 CurrentVerticalOffset 給定目標值,也就是 _totalVerticalOffset 。
最後還有一個衝突問題,當手動拖動滑塊或者當用上下文菜單改變滾動條位置時是不能用動畫的,由於這時候沒有觸發 OnMouseWheel ,不要緊,這正是咱們想要的,可是若是再次觸發 OnMouseWheel 就有問題了,由於手動觸發滾動的時候咱們沒有給 CurrentVerticalOffset 和 _totalVerticalOffset 賦值( CurrentVerticalOffset 和 _totalVerticalOffset 只在 OnMouseWheel 中賦值),因此在用動畫執行滾動操做前要先判斷一下是否須要先更新一下它們倆,如何判斷?咱們能夠用一個私有字段 _isRunning 來維護狀態,每當動畫開始就給它賦值true,結束則賦值false。這樣一來,當 _isRunning = false 時,說明在調用 OnMouseWheel 前,動畫已經結束,用戶可能已經手動改變了滾動條位置(也可能沒有,但這並不影響),因此就要給以前倆兄弟更新一下值了。
由於常見的慣性滾動以垂直方向居多,因此我沒有寫水平方向的邏輯,但也很容易擴展,有興趣的博友能夠下載源代碼本身研究。
3、源碼
本文所討論的控件源碼已經在github開源:https://github.com/NaBian/HandyControl