大佬教程收集整理的这篇文章主要介绍了【UWP】实现一个波浪进度条,大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。
好久没写 blog 了,一个是忙,另外一个是觉得没啥好写。废话不多说,直接上效果图:
可能看到这波浪线你觉得会很难,但看完这篇 blog 后应该你也会像我一样恍然大悟。图上的图形,我们可以考虑是由 3 条直线和 1 条曲线组成。直线没什么难的,难的是曲线。在曲线这一领域,我们有一种特殊的曲线,叫贝塞尔曲线。
在上面这曲线,我们可以对应到的是三次方贝塞尔曲线,它由 4 个点控制,起点、终点和两个控制点。这里我找了一个在线的 demo:https://www.bezier-curve.com/
调整控制点 1(红色)和控制点 2(蓝色)的位置我们可以得到像最开始的图那样的波浪线了。
另外,我们也可以注意到一个性质,假如起点、终点、控制点 1 和控制点 2 都在同一条直线上的话,那么我们这条贝塞尔曲线就是一条直线。
按最开始的图的动画,我们最终状态是一条直线,显然就是需要这 4 个点都在同一直线上,然而在动画过程中,我们需要的是一条曲线,也就是说动画过程中它们不会在同一直线上了。我们也可以注意到,在波浪往上涨的时候,左边部分是凸起来的,而右半部分是凹进去的。这对应了控制点 1 是在直线以上,而控制点 2 在直线以下。那么如何在动画里做到呢,很简单,使用缓动函数就行了,让控制点 1 的值更快到达最终目标值,让控制点 2 的值更慢到达最终目标值即可。(当然,单纯使用时间控制也行,在这里我还是用缓动函数)
理论已经有了,现在让我们开始编码。
新建一个 UWP 项目,然后我们创建一个模板控件,叫 WaveProgressBar。之所以不继承自 ProgressBar,是因为 ProgressBar 上有一个 IsIndeterminate 属性,代表不确定状态,我们的 WaveProgressBar 并不需要,简单起见,我们还是从模板控件开始。
接下来我们修改 Generic.xaml 中的控件模板代码
@H_450_43@<Style TargetType@H_450_43@="local:WaveProgressBar"@H_450_43@> @H_450_43@<Setter Property@H_450_43@="BACkground" Value@H_450_43@="LightBlue" @H_450_43@/> @H_450_43@<Setter Property@H_450_43@="Template"@H_450_43@> @H_450_43@<Setter.Value@H_450_43@> @H_450_43@<ControlTemplate TargetType@H_450_43@="local:WaveProgressBar"@H_450_43@> @H_450_43@<Viewbox Stretch@H_450_43@="Fill"@H_450_43@> @H_450_43@<Path Width@H_450_43@="100" Height@H_450_43@="100" Fill@H_450_43@="{TemplateBinding BACkgrounD}@H_450_43@"@H_450_43@> @H_450_43@<Path.Data@H_450_43@> @H_450_43@<PathGeometry@H_450_43@> @H_450_43@<PathGeometry.Figures@H_450_43@> @H_450_43@<PathFigure StartPoint@H_450_43@="0,100"@H_450_43@> @H_450_43@<PathFigure.Segments@H_450_43@> @H_450_43@<Linesegment x:Name@H_450_43@="PART_Linesegment" Point@H_450_43@="0,50" @H_450_43@/> @H_450_43@<BezierSegment x:Name@H_450_43@="PART_BezierSegment" Point1@H_450_43@="35,25" Point2@H_450_43@="65,75" Point3@H_450_43@="100,50" @H_450_43@/> @H_450_43@<Linesegment Point@H_450_43@="100,100" @H_450_43@/> @H_450_43@</PathFigure.Segments@H_450_43@> @H_450_43@</PathFigure@H_450_43@> @H_450_43@</PathGeometry.Figures@H_450_43@> @H_450_43@</PathGeometry@H_450_43@> @H_450_43@</Path.Data@H_450_43@> @H_450_43@</Path@H_450_43@> @H_450_43@</Viewbox@H_450_43@> @H_450_43@</ControlTemplate@H_450_43@> @H_450_43@</Setter.Value@H_450_43@> @H_450_43@</Setter@H_450_43@> @H_450_43@</Style@H_450_43@>
这里我使用了一个 Viewbox 以适应 Path 缩放。Path 大小我们定义为 100x100,然后先从左下角 0,100 开始绘制,绘制一条直线到 0,50,接下来绘制我们的贝塞尔曲线,Point3 是终点 100,50,最后我们绘制了一条直线从 100,50 到 100,100。另外因为 PathFigure 默认就是会自动封闭的,所以我们不需要画 100,100 到 0,100 的这一条直线。当然以上这些点的坐标都会在运行期间发生变化是了,这里这些坐标仅仅只是先看看效果。
加入如下代码到我们的页面:
运行程序应该会看到如下效果:
接下来我们就可以考虑我们的进度 Progress 属性了,这里我定义 0 代表 0%,1 代表 100%,那 0.5 就是 50% 了。定义如下依赖属性:
@H_450_43@ public @H_450_43@class WaveProgressBar : Control { @H_450_43@public @H_450_43@static @H_450_43@readonly DependencyProperty ProgressProperty = DependencyProperty.Register( nameof(Progress), @H_450_43@typeof(@H_450_43@double), @H_450_43@typeof(WaveProgressBar), @H_450_43@new Propertymetadata(0d, OnProgressChanged)); @H_450_43@public WaveProgressBar() { DefaultStyleKey = @H_450_43@typeof(WaveProgressBar); } @H_450_43@public @H_450_43@double Progress { @H_450_43@get => (@H_450_43@double)GetValue(ProgressProperty); @H_450_43@set => SETVALue(ProgressProperty, @R_450_7548@; } @H_450_43@private @H_450_43@static @H_450_43@void OnProgressChanged(DependencyObject d, DependencyPropertyChangedEventArgs E) { @H_450_43@throw @H_450_43@new NotImplementedException(); } }
由于我们要对 Path 里面的点进行动画,所以先在 OnApplyTemplate 方法中把它们拿出来。
[TemplatePart(Name = LinesegmentTemplatename, Type = @H_450_43@typeof(Linesegment))] [TemplatePart(Name = BezierSegmentTemplatename, Type = @H_450_43@typeof(BezierSegment))] @H_450_43@public @H_450_43@class WaveProgressBar : Control { @H_450_43@public @H_450_43@static @H_450_43@readonly DependencyProperty ProgressProperty = DependencyProperty.Register( nameof(Progress), @H_450_43@typeof(@H_450_43@double), @H_450_43@typeof(WaveProgressBar), @H_450_43@new Propertymetadata(0d, OnProgressChanged)); @H_450_43@private @H_450_43@const @H_450_43@String BezierSegmentTemplatename = "PART_BezierSegment"; @H_450_43@private @H_450_43@const @H_450_43@String LinesegmentTemplatename = "PART_Linesegment"; @H_450_43@private BezierSegment _bezierSegment; @H_450_43@private Linesegment _linesegment; @H_450_43@public WaveProgressBar() { DefaultStyleKey = @H_450_43@typeof(WaveProgressBar); } @H_450_43@public @H_450_43@double Progress { @H_450_43@get => (@H_450_43@double)GetValue(ProgressProperty); @H_450_43@set => SETVALue(ProgressProperty, @R_450_7548@; } @H_450_43@protected @H_450_43@override @H_450_43@void OnApplyTemplate() { _linesegment = (Linesegment)GetTemplateChild(LinesegmentTemplateName); _bezierSegment = (BezierSegment)GetTemplateChild(BezierSegmentTemplateName); } @H_450_43@private @H_450_43@static @H_450_43@void OnProgressChanged(DependencyObject d, DependencyPropertyChangedEventArgs E) { @H_450_43@throw @H_450_43@new NotImplementedException(); } }
接着我们可以考虑动画部分了,这里应该有两个地方会调用到动画,一个是 OnProgressChanged,Progress 值变动需要触发动画。另一个地方是 OnApplyTemplate,因为控件第一次出现时需要将 Progress 的值立刻同步上去(不然 Progress 跟看上去的不一样),所以这个是瞬时的动画。
配合最开始的理论,我们大致可以编写出如下的动画代码:
@H_450_43@ private @H_450_43@void PlayAnimation(@H_450_43@bool isInit) { @H_450_43@if (_linesegment == @H_450_43@null || _bezierSegment == @H_450_43@null) { @H_450_43@return; } @H_450_43@var targetY = 100 * (1 - Progress); @H_450_43@var duration = @H_450_43@new Duration(TimeSpan.FromSeconds(isInit ? 0 : 0.7)); @H_450_43@var storyboard = @H_450_43@new Storyboard(); @H_450_43@var point1Animation = @H_450_43@new PointAnimation { EnableDependentAnimation = @H_450_43@true, Duration = duration, To = @H_450_43@new Point(0, targetY), EasingFunction = @H_450_43@new BACkEase { Amplitude = 0.5 } }; Storyboard.SetTarget(point1Animation, _linesegment); Storyboard.SetTargetProperty(point1Animation, nameof(_linesegment.Point)); storyboard.Children.Add(point1Animation); @H_450_43@var point2Animation = @H_450_43@new PointAnimation { EnableDependentAnimation = @H_450_43@true, Duration = duration, To = @H_450_43@new Point(35, targetY), EasingFunction = @H_450_43@new BACkEase { Amplitude = 1.5 } }; Storyboard.SetTarget(point2Animation, _bezierSegment); Storyboard.SetTargetProperty(point2Animation, nameof(_bezierSegment.Point1)); storyboard.Children.Add(point2Animation); @H_450_43@var point3Animation = @H_450_43@new PointAnimation { EnableDependentAnimation = @H_450_43@true, Duration = duration, To = @H_450_43@new Point(65, targetY), EasingFunction = @H_450_43@new BACkEase { Amplitude = 0.1 } }; Storyboard.SetTarget(point3Animation, _bezierSegment); Storyboard.SetTargetProperty(point3Animation, nameof(_bezierSegment.Point2)); storyboard.Children.Add(point3Animation); @H_450_43@var point4Animation = @H_450_43@new PointAnimation { EnableDependentAnimation = @H_450_43@true, Duration = duration, To = @H_450_43@new Point(100, targetY), EasingFunction = @H_450_43@new BACkEase { Amplitude = 0.5 } }; Storyboard.SetTarget(point4Animation, _bezierSegment); Storyboard.SetTargetProperty(point4Animation, nameof(_bezierSegment.Point3)); storyboard.Children.Add(point4Animation); storyboard.begin(); }
对于 OnApplyTemplate 的瞬时动画,我们直接设置 Duration 为 0。
接下来 4 个点的控制,我们通过使用 BACkEase 缓动函数,配上不同的强度(Amplitude)来实现控制点 1 先到达目标,然后是起点和终点同时到达目标,最后控制点 2 到达目标。
最后 WaveProgressBar 的完整代码应该是这样的:
[TemplatePart(Name = LinesegmentTemplatename, Type = @H_450_43@typeof(Linesegment))] [TemplatePart(Name = BezierSegmentTemplatename, Type = @H_450_43@typeof(BezierSegment))] @H_450_43@public @H_450_43@class WaveProgressBar : Control { @H_450_43@public @H_450_43@static @H_450_43@readonly DependencyProperty ProgressProperty = DependencyProperty.Register( nameof(Progress), @H_450_43@typeof(@H_450_43@double), @H_450_43@typeof(WaveProgressBar), @H_450_43@new Propertymetadata(0d, OnProgressChanged)); @H_450_43@private @H_450_43@const @H_450_43@String BezierSegmentTemplatename = "PART_BezierSegment"; @H_450_43@private @H_450_43@const @H_450_43@String LinesegmentTemplatename = "PART_Linesegment"; @H_450_43@private BezierSegment _bezierSegment; @H_450_43@private Linesegment _linesegment; @H_450_43@public WaveProgressBar() { DefaultStyleKey = @H_450_43@typeof(WaveProgressBar); } @H_450_43@public @H_450_43@double Progress { @H_450_43@get => (@H_450_43@double)GetValue(ProgressProperty); @H_450_43@set => SETVALue(ProgressProperty, @R_450_7548@; } @H_450_43@protected @H_450_43@override @H_450_43@void OnApplyTemplate() { _linesegment = (Linesegment)GetTemplateChild(LinesegmentTemplateName); _bezierSegment = (BezierSegment)GetTemplateChild(BezierSegmentTemplateName); PlayAnimation(@H_450_43@true); } @H_450_43@private @H_450_43@static @H_450_43@void OnProgressChanged(DependencyObject d, DependencyPropertyChangedEventArgs E) { @H_450_43@var obj = (WaveProgressBar)d; obj.PlayAnimation(@H_450_43@false); } @H_450_43@private @H_450_43@void PlayAnimation(@H_450_43@bool isInit) { @H_450_43@if (_linesegment == @H_450_43@null || _bezierSegment == @H_450_43@null) { @H_450_43@return; } @H_450_43@var targetY = 100 * (1 - Progress); @H_450_43@var duration = @H_450_43@new Duration(TimeSpan.FromSeconds(isInit ? 0 : 0.7)); @H_450_43@var storyboard = @H_450_43@new Storyboard(); @H_450_43@var point1Animation = @H_450_43@new PointAnimation { EnableDependentAnimation = @H_450_43@true, Duration = duration, To = @H_450_43@new Point(0, targetY), EasingFunction = @H_450_43@new BACkEase { Amplitude = 0.5 } }; Storyboard.SetTarget(point1Animation, _linesegment); Storyboard.SetTargetProperty(point1Animation, nameof(_linesegment.Point)); storyboard.Children.Add(point1Animation); @H_450_43@var point2Animation = @H_450_43@new PointAnimation { EnableDependentAnimation = @H_450_43@true, Duration = duration, To = @H_450_43@new Point(35, targetY), EasingFunction = @H_450_43@new BACkEase { Amplitude = 1.5 } }; Storyboard.SetTarget(point2Animation, _bezierSegment); Storyboard.SetTargetProperty(point2Animation, nameof(_bezierSegment.Point1)); storyboard.Children.Add(point2Animation); @H_450_43@var point3Animation = @H_450_43@new PointAnimation { EnableDependentAnimation = @H_450_43@true, Duration = duration, To = @H_450_43@new Point(65, targetY), EasingFunction = @H_450_43@new BACkEase { Amplitude = 0.1 } }; Storyboard.SetTarget(point3Animation, _bezierSegment); Storyboard.SetTargetProperty(point3Animation, nameof(_bezierSegment.Point2)); storyboard.Children.Add(point3Animation); @H_450_43@var point4Animation = @H_450_43@new PointAnimation { EnableDependentAnimation = @H_450_43@true, Duration = duration, To = @H_450_43@new Point(100, targetY), EasingFunction = @H_450_43@new BACkEase { Amplitude = 0.5 } }; Storyboard.SetTarget(point4Animation, _bezierSegment); Storyboard.SetTargetProperty(point4Animation, nameof(_bezierSegment.Point3)); storyboard.Children.Add(point4Animation); storyboard.begin(); } }
修改项目主页面如下:
@H_450_43@<Grid@H_450_43@> @H_450_43@<local:WaveProgressBar Width@H_450_43@="200" Height@H_450_43@="300" Progress@H_450_43@="{Binding ElementName=Slider, Path=value}@H_450_43@" @H_450_43@/> @H_450_43@<Slider x:Name@H_450_43@="Slider" Width@H_450_43@="200" Margin@H_450_43@="0,0,0,20" HorizontalAlignment@H_450_43@="Center" VerticalAlignment@H_450_43@="Bottom" Maximum@H_450_43@="1" Minimum@H_450_43@="0" StepFrequency@H_450_43@="0.01" @H_450_43@/> @H_450_43@</Grid@H_450_43@>
此时再运行的话,你就会看到如本文开头中动图的效果了。
本文的代码也可以在这里找到:https://github.com/h82258652/UWPWaveProgressBar
以上是大佬教程为你收集整理的【UWP】实现一个波浪进度条全部内容,希望文章能够帮你解决【UWP】实现一个波浪进度条所遇到的程序开发问题。
如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。