WPF 控件庫——仿制Windows10的進度條


WPF 控件庫系列博文地址:

WPF 控件庫——仿制Chrome的ColorPicker

WPF 控件庫——仿制Windows10的進度條

WPF 控件庫——輪播控件

WPF 控件庫——帶有慣性的ScrollViewer

WPF 控件庫——可拖動選項卡的TabControl

 

一、其實有現成的

  先來看看Windows10進度條的兩種模式:

 

 

  網上有不少介紹仿制Windows10進度條的文章,也都實現了不錯的效果。而我再開一文的原因是覺得如果在這基礎上添加一些功能,比如圓點的數量,圓點的大小等等,效果可能會更好一些。接觸過UWP的朋友應該知道,其框架中自帶了進度條控件,以 ProgressRing 為例,通過Blend,我們可以獲取到控件的XAML,以下是部分截圖:

 

  粗略一看,只要稍作修改便能用到WPF中——我們幾乎可以什么都不做!

 

二、添加功能

  如果要更改圓點的數量,圓點的大小或者圓點的移動速度,我們該如何實現呢?繼承章節一中的XAML,並根據所需調整模板就顯得太麻煩了,這會讓我們的樣式文件顯得臃腫不堪,所以采用純粹的C#代碼來實現它或許比較明智。不過之前的XAML也不是一無是處,至少它給出了環形進度條的關鍵幀動畫的構成,這些信息對我們來說很重要,免去了我們自己去分析的步驟。

  現在我們的主要工作就是讓寫死的關鍵幀能夠通過屬性靈活配置,所以我們可能需要先編碼一份進度條的基類( LoadingBase ),以提取兩種類型進度條的共性。基類中定義8個屬性,分別是 IsRunning 、 DotCount 、 DotInterval 、 DotBorderBrush 、 DotBorderThickness 、 DotDiameter 、 DotSpeed 、 DotDelayTime ,它們的含義已經是自注釋的,不必贅述。而在環形進度條中,還有另外兩個屬性: DotOffSet 和 NeedHidden ,分別表示圓點整體的位置偏移和在運動中是否需要隱藏圓點。

 

三、關鍵幀動畫

  最后一步就是用C#代碼實現關鍵幀動畫,不過得先有米才能做飯,故而需要先創建圓點:

 1 protected Ellipse CreateEllipse(int index)
 2         {
 3             var ellipse = new Ellipse();
 4             ellipse.SetBinding(WidthProperty, new Binding("DotDiameter") {Source = this});
 5             ellipse.SetBinding(HeightProperty, new Binding("DotDiameter") {Source = this});
 6             ellipse.SetBinding(Shape.FillProperty, new Binding("Foreground") {Source = this});
 7             ellipse.SetBinding(Shape.StrokeThicknessProperty, new Binding("DotBorderThickness") {Source = this});
 8             ellipse.SetBinding(Shape.StrokeProperty, new Binding("DotBorderBrush") {Source = this});
 9             return ellipse;
10         }

  上面的方法在進度條基類中實現,僅僅是用相關的屬性初始化了我們的原材料:圓點。由於環形進度條在X、Y軸方向都有移動,所以為了方便,我們可以考慮在圓點外面再包一層 Border 作為看不見的殼,我們將圓點與殼底部對齊,現在只要讓殼繞中心旋轉就基本實現了目標,下面是環形進度條1個點到5個點帶殼的示意圖:

  想一想,如果沒有這層殼,我們又有什么替代方法,這些方法是否都是極為方便的?可能沒有這層殼,就需要去琢磨怎么改變圓點的 RenderTransformOrigin ,好讓它們看起來都是圍繞一個點旋轉的,即使改變了進度條整體的尺寸。套殼的代碼如下:

 1 private Border CreateBorder(int index)
 2         {
 3             var ellipse = CreateEllipse(index);
 4             ellipse.HorizontalAlignment = HorizontalAlignment.Center;
 5             ellipse.VerticalAlignment = VerticalAlignment.Bottom;
 6             var rt = new RotateTransform
 7             {
 8                 Angle = -DotInterval * index
 9             };
10             var myTransGroup = new TransformGroup();
11             myTransGroup.Children.Add(rt);
12             var border = new Border
13             {
14                 RenderTransformOrigin = new Point(0.5, 0.5),
15                 RenderTransform = myTransGroup,
16                 Child = ellipse,
17                 Visibility = NeedHidden ? Visibility.Collapsed : Visibility.Visible
18             };
19             border.SetBinding(WidthProperty, new Binding("Width") { Source = this }); 20             border.SetBinding(HeightProperty, new Binding("Height") { Source = this }); 21 
22             return border;
23         }

  套殼代碼除了套殼和相關的初始化,最重要的是19和20行的寬高綁定,這是讓圓點旋轉中心始終唯一的關鍵。有了以上的准備,我們終於可以開始for循環了:

 1 //定義動畫
 2 Storyboard = new Storyboard
 3 {
 4     RepeatBehavior = RepeatBehavior.Forever
 5 };
 6 
 7 for (var i = 0; i < DotCount; i++)
 8 {
 9     //在這里創建圓點  
10 }

  下面就是最核心的關鍵幀動畫,通過之前用Blend提取出來的XAML,我們可以看到它使用了 SplineDoubleKeyFrame ,這會涉及三次貝塞爾曲線的控制點,考慮到易用性,我們會用 LinearDoubleKeyFrame 和 EasingDoubleKeyFrame 代替。在XAML中我們最關心的關鍵字應該是角度,在時間片的哪部分,圓點應該在哪兒,而又在什么時候,圓點應該會消失,我們只要隨意截取兩個點的關鍵幀就能獲得以上所有信息:

 

  上面兩張分別是圓點1和2透明度和位置的關鍵幀截圖,通過兩個點我們完全可以推斷所有點。出於個人喜好,我將透明度替換成了 Visibility 的切換,所以還會引入 DiscreteObjectKeyFrame 。篇幅原因,我們直接總結分析結果:

  • 一開始所有點都是顯示的,但是位置不同,從點1的-110度開始,角度逐個減6;
  • 點1開始運動后,0.167秒(1/6秒)后點2開始運動,所以各點動畫延遲時間為1/6秒(這里不太能確定是否和圓點數量有關);
  • 以點1為例,旋轉角度隨時間變化圖如下:

  從上面7張圖中可以看出,在一次循環中點1是這樣運動的:減速、勻速、加速、減速、勻速、加速,而且與之對應的角度位置也給出了,最后水到渠成,環形進度條就完成了。

 

四、截圖

  通過設置不同的屬性,可以實現不同的效果:

  

 

五、源碼

  本文所討論的進度條源碼已經在github開源:https://github.com/NaBian/HandyControl


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM