一、其實有現成的 先來看看Windows10進度條的兩種模式: 網上有不少介紹仿製Windows10進度條的文章,也都實現了不錯的效果。而我再開一文的原因是覺得如果在這基礎上添加一些功能,比如圓點的數量,圓點的大小等等,效果可能會更好一些。接觸過UWP的朋友應該知道,其框架中自帶了進度條控制項,以 P ...
一、其實有現成的
先來看看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