WPF 控件库系列博文地址:html
WPF 控件库——仿制Chrome的ColorPickergit
WPF 控件库——仿制Windows10的进度条github
WPF 控件库——可拖动选项卡的TabControlthis
1、其实有现成的编码
先来看看Windows10进度条的两种模式:spa
网上有很多介绍仿制Windows10进度条的文章,也都实现了不错的效果。而我再开一文的缘由是以为若是在这基础上添加一些功能,好比圆点的数量,圆点的大小等等,效果可能会更好一些。接触过UWP的朋友应该知道,其框架中自带了进度条控件,以 ProgressRing 为例,经过Blend,咱们能够获取到控件的XAML,如下是部分截图:3d
粗略一看,只要稍做修改便能用到WPF中——咱们几乎能够什么都不作!code
2、添加功能
若是要更改圆点的数量,圆点的大小或者圆点的移动速度,咱们该如何实现呢?继承章节一中的XAML,并根据所需调整模板就显得太麻烦了,这会让咱们的样式文件显得臃肿不堪,因此采用纯粹的C#代码来实现它或许比较明智。不过以前的XAML也不是一无可取,至少它给出了环形进度条的关键帧动画的构成,这些信息对咱们来讲很重要,免去了咱们本身去分析的步骤。
如今咱们的主要工做就是让写死的关键帧可以经过属性灵活配置,因此咱们可能须要先编码一份进度条的基类( LoadingBase ),以提取两种类型进度条的共性。基类中定义8个属性,分别是 IsRunning 、 DotCount 、 DotInterval 、 DotBorderBrush 、 DotBorderThickness 、 DotDiameter 、 DotSpeed 、 DotDelayTime ,它们的含义已是自注释的,没必要赘述。而在环形进度条中,还有另外两个属性: DotOffSet 和 NeedHidden ,分别表示圆点总体的位置偏移和在运动中是否须要隐藏圆点。
3、关键帧动画
最后一步就是用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 。篇幅缘由,咱们直接总结分析结果:
从上面7张图中能够看出,在一次循环中点1是这样运动的:减速、匀速、加速、减速、匀速、加速,并且与之对应的角度位置也给出了,最后水到渠成,环形进度条就完成了。
4、截图
经过设置不一样的属性,能够实现不一样的效果:
5、源码
本文所讨论的进度条源码已经在github开源:https://github.com/NaBian/HandyControl