1. 需求
前天看到有人問弧形進度條怎么做,我模仿了一下,成果如下圖所示:
當時我第一反應是可以用 Microsoft.Toolkit.Uwp.UI.Controls 里的 RadialGauge 實現,雖然這是個 UWP 的控件,不過代碼沒有很復雜,應該很輕松就能移植到 WPF:
但仔細想想,我實現過很多次圓形的進度條,這種弧形的進度條則沒碰過。原型進度條基本只需要用 Ellipse 就能實現,而且只需要 Progress 一個參數,而弧形進度條則還需要 StartAngle 和 EndAngle 兩個屬性,而且計算復雜許多。於是興致來了試試用不同的方式實現弧形進度條。
這篇文章只介紹了怎么顯示弧形及怎么顯示進度,只有原理,沒有具體實現一個弧形進度條控件。
2. 使用 Path 及 ArcSegment
Path 用於繪制曲線和復雜形狀,而且 ArcSegment 用於描述 Path 中兩點之間的一條橢圓弧。通常使用以下幾個屬性控制 ArcSegment:
屬性 | 描述 |
---|---|
Point | 終點(起始點在 Path 或前一個 Segment 中描述)。 |
Size | X 軸和 Y 軸的半徑。 |
IsLargeArc | 圓弧是整個圓形中大的那部分,還是小的那部分。 |
SweepDirection | 弧線繪制的方向。 |
具體說明可以看 這個文檔。
用 Path 和 ArcSegment 可以很好地實現弧形的進度條,它的 XAML 如下:
<Path Stroke="SlateBlue"
StrokeThickness="4">
<Path.Data>
<PathGeometry>
<PathFigure IsClosed="False" StartPoint="30,170">
<ArcSegment IsLargeArc="True"
Point="170,170"
Size="96,96"
SweepDirection="Clockwise" />
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
疊加兩個不同顏色的 Path,就可以實現這種效果:
Path 和 ArcSegment 是一個很正統的方案,前面提到的 RadialGauge 就用了這個方案。不過它的計算很麻煩,三角函數我已經忘光了。
另外,請注意弧線兩端都是平平的直角,這和需求不符,所以需要設置 StrokeStartLineCap
和 StrokeEndLineCap
這兩個屬性的值為 Round
:
StrokeStartLineCap="Round" StrokeEndLineCap="Round"
它們控制線條兩端邊緣的輪廓,Round
表示一個直徑等於線條粗細的半圓形。這樣才能實現需求中的圓角:
順便一提,這兩個屬性的類型是 PenLineCap
枚舉,這個枚舉的四個值分別代表以下幾種形狀:
3. 使用 Arc
第二個方案是使用 Microsoft.Expression.Drawing
中的 Arc
形狀直接畫出一個弧形。如果安裝了舊版的 Blend(好像 2017 或以前的都可以),可以在 資產->形狀
里找到這個形狀(我裝的是英文版所以沒有中文截圖):
或者在 Nuget 上搜索 Microsoft.Expression.Drawing
找到一個符合自己項目的版本。
Arc 的用法很簡單,只需要執行 StartAngle
和 EndAngle
即可輸出一個弧形:
<ed:Arc ArcThickness="12"
ArcThicknessUnit="Pixel"
EndAngle="150"
Fill="#101a26"
StartAngle="-150"
Stretch="None"
StrokeEndLineCap="Round"
StrokeStartLineCap="Round" />
疊加兩個不同顏色的 Arc,可以實現這種效果:
可是仔細看,就算用了 StrokeStartLineCap
和 StrokeEndLineCap
兩個屬性,Arc 的兩端任然是直角,這不符合需求,所以這個方案簡單但不完美,我還要嘗試下一個方案。
4. 使用 Ellipse
這個方案還算有趣,Ellipse 明明是圓形,卻能用來畫弧形。為了用 Ellipse 顯示進度,我們會用 StrokeDashArray 控制它的邊框長度。StrokeDashArray 用於將邊框變成虛線,它的值是一個 double 類型的有序集合,集合中的值指虛線中每一段的長度,長度單位是邊框值的寬度。例如以下圓形:
<Ellipse StrokeDashArray="1,2,3"
Stroke="#FFFF0EC4"
StrokeThickness="10"
Height="200"
Width="200" />
邊框寬度為 10,虛線的第一段是長度為 10 的實線,第二段為長度為 20 的空白,第三段為長度為 30 的實線,然后如此循環直到結束。
用 StrokeDashArray 做進度提示的基本做法就是將進度(Progress)通過 Converter 轉換為分成兩段的 StrokeDashArray,第一段為實線,表示當前進度,第二段為空白。假設一個 Shape 的邊長是 100,當前進度為 50,則將 StrokeDashArray 設置成 {50,double.MaxValue} 兩段。
為了實現弧形進度條,我們還需要控制 Ellipse 旋轉的角度。具體來說我實現了一個 EllipseProgressBehavior,里面有 Progress、StartAngle 和 EndAngle 三個屬性,具體代碼在 這里。用這個 Behavior 控制 Ellipse 的邊框長度和旋轉角度,使用方式如下:
<Ellipse Margin="4"
Stroke="#7bcdd9"
StrokeThickness="4">
<interactivity:Interaction.Behaviors>
<local1:EllipseProgressBehavior EndAngle="150"
Progress="50"
StartAngle="-150" />
</interactivity:Interaction.Behaviors>
</Ellipse>
疊加兩個 Ellipse,即可實現需求中的弧形進度條。可是這時候弧形的兩端都是直角,即使設置了 StrokeStartLineCap
和 StrokeEndLineCap
兩個屬性都不起作用。對於用 StrokeDashArray 顯示的邊框,不能使用 StrokeStartLineCap
和 StrokeEndLineCap
去控制它的兩端的輪廓,而應該使用 StrokeDashCap:
StrokeDashCap="Round"
最終通過疊加兩個 Ellipse 實現了戶型進度條的需求:
5. 最后
童話和寓言都喜歡把相似的內容說上三次,例如三只小豬,三顧茅廬,弗利薩的三段變身。所以不是我在研究回字有多少種寫法,我只是遵循古法想把一種技術講透而已。