diff --git a/HandyControl/Controls/Loading/LoadingBase.cs b/HandyControl/Controls/Loading/LoadingBase.cs new file mode 100644 index 00000000..12abb57c --- /dev/null +++ b/HandyControl/Controls/Loading/LoadingBase.cs @@ -0,0 +1,135 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Shapes; + +// ReSharper disable once CheckNamespace +namespace HandyControl.Controls +{ + public abstract class LoadingBase : ContentControl + { + protected Storyboard Storyboard; + + public static readonly DependencyProperty IsRunningProperty = DependencyProperty.Register( + "IsRunning", typeof(bool), typeof(LoadingBase), new PropertyMetadata(true, (o, args) => + { + var ctl = (LoadingBase) o; + var v = (bool) args.NewValue; + if (v) + { + ctl.Storyboard?.Resume(); + } + else + { + ctl.Storyboard?.Pause(); + } + })); + + public bool IsRunning + { + get { return (bool) GetValue(IsRunningProperty); } + set { SetValue(IsRunningProperty, value); } + } + + public static readonly DependencyProperty DotCountProperty = DependencyProperty.Register( + "DotCount", typeof(int), typeof(LoadingBase), + new FrameworkPropertyMetadata(5, FrameworkPropertyMetadataOptions.AffectsRender)); + + public static readonly DependencyProperty DotIntervalProperty = DependencyProperty.Register( + "DotInterval", typeof(double), typeof(LoadingBase), + new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsRender)); + + public static readonly DependencyProperty DotBorderBrushProperty = DependencyProperty.Register( + "DotBorderBrush", typeof(Brush), typeof(LoadingBase), + new FrameworkPropertyMetadata(default(Brush), FrameworkPropertyMetadataOptions.AffectsRender)); + + public static readonly DependencyProperty DotBorderThicknessProperty = DependencyProperty.Register( + "DotBorderThickness", typeof(double), typeof(LoadingBase), + new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.AffectsRender)); + + public static readonly DependencyProperty DotDiameterProperty = DependencyProperty.Register( + "DotDiameter", typeof(double), typeof(LoadingBase), + new FrameworkPropertyMetadata(6.0, FrameworkPropertyMetadataOptions.AffectsRender)); + + public static readonly DependencyProperty DotSpeedProperty = DependencyProperty.Register( + "DotSpeed", typeof(double), typeof(LoadingBase), + new FrameworkPropertyMetadata(4.0, FrameworkPropertyMetadataOptions.AffectsRender)); + + public static readonly DependencyProperty DotDelayTimeProperty = DependencyProperty.Register( + "DotDelayTime", typeof(double), typeof(LoadingBase), + new FrameworkPropertyMetadata(80.0, FrameworkPropertyMetadataOptions.AffectsRender)); + + protected readonly Canvas PrivateCanvas = new Canvas + { + ClipToBounds = true + }; + + protected LoadingBase() + { + Content = PrivateCanvas; + } + + public int DotCount + { + get => (int) GetValue(DotCountProperty); + set => SetValue(DotCountProperty, value); + } + + public double DotInterval + { + get => (double) GetValue(DotIntervalProperty); + set => SetValue(DotIntervalProperty, value); + } + + public Brush DotBorderBrush + { + get => (Brush) GetValue(DotBorderBrushProperty); + set => SetValue(DotBorderBrushProperty, value); + } + + public double DotBorderThickness + { + get => (double) GetValue(DotBorderThicknessProperty); + set => SetValue(DotBorderThicknessProperty, value); + } + + public double DotDiameter + { + get => (double) GetValue(DotDiameterProperty); + set => SetValue(DotDiameterProperty, value); + } + + public double DotSpeed + { + get => (double) GetValue(DotSpeedProperty); + set => SetValue(DotSpeedProperty, value); + } + + public double DotDelayTime + { + get => (double) GetValue(DotDelayTimeProperty); + set => SetValue(DotDelayTimeProperty, value); + } + + protected abstract void UpdateDots(); + + protected override void OnRender(DrawingContext drawingContext) + { + base.OnRender(drawingContext); + UpdateDots(); + } + + protected Ellipse CreateEllipse(int index) + { + var ellipse = new Ellipse(); + ellipse.SetBinding(WidthProperty, new Binding("DotDiameter") {Source = this}); + ellipse.SetBinding(HeightProperty, new Binding("DotDiameter") {Source = this}); + ellipse.SetBinding(Shape.FillProperty, new Binding("Foreground") {Source = this}); + ellipse.SetBinding(Shape.StrokeThicknessProperty, new Binding("DotBorderThickness") {Source = this}); + ellipse.SetBinding(Shape.StrokeProperty, new Binding("DotBorderBrush") {Source = this}); + return ellipse; + } + } +} \ No newline at end of file diff --git a/HandyControl/Controls/Loading/LoadingCircle.cs b/HandyControl/Controls/Loading/LoadingCircle.cs new file mode 100644 index 00000000..1f373fe7 --- /dev/null +++ b/HandyControl/Controls/Loading/LoadingCircle.cs @@ -0,0 +1,202 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Media; +using System.Windows.Media.Animation; + +// ReSharper disable once CheckNamespace +namespace HandyControl.Controls +{ + public class LoadingCircle : LoadingBase + { + public static readonly DependencyProperty DotOffSetProperty = DependencyProperty.Register( + "DotOffSet", typeof(double), typeof(LoadingCircle), + new FrameworkPropertyMetadata(20.0, FrameworkPropertyMetadataOptions.AffectsRender)); + + public double DotOffSet + { + get => (double) GetValue(DotOffSetProperty); + set => SetValue(DotOffSetProperty, value); + } + + public static readonly DependencyProperty NeedHiddenProperty = DependencyProperty.Register( + "NeedHidden", typeof(bool), typeof(LoadingCircle), + new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsRender)); + + public bool NeedHidden + { + get => (bool) GetValue(NeedHiddenProperty); + set => SetValue(NeedHiddenProperty, value); + } + + static LoadingCircle() + { + DotSpeedProperty.OverrideMetadata(typeof(LoadingCircle), + new FrameworkPropertyMetadata(6.0, FrameworkPropertyMetadataOptions.AffectsRender)); + DotDelayTimeProperty.OverrideMetadata(typeof(LoadingCircle), + new FrameworkPropertyMetadata(220.0, FrameworkPropertyMetadataOptions.AffectsRender)); + } + + protected sealed override void UpdateDots() + { + if (DotCount < 1) return; + PrivateCanvas.Children.Clear(); + + //定义动画 + Storyboard = new Storyboard + { + RepeatBehavior = RepeatBehavior.Forever + }; + + //创建圆点 + for (var i = 0; i < DotCount; i++) + { + var border = CreateBorder(i); + + var framesMove = new DoubleAnimationUsingKeyFrames + { + BeginTime = TimeSpan.FromMilliseconds(DotDelayTime * i) + }; + + var subAngle = -DotInterval * i; + + //开始位置 + var frame0 = new LinearDoubleKeyFrame + { + Value = 0 + subAngle, + KeyTime = KeyTime.FromTimeSpan(TimeSpan.Zero) + }; + + //开始位置到第一次匀速开始 + var frame1 = new EasingDoubleKeyFrame + { + EasingFunction = new PowerEase + { + EasingMode = EasingMode.EaseOut + }, + Value = 180 + subAngle, + KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed * (0.75 / 7))) + }; + + //第一次匀速开始到第一次匀速结束 + var frame2 = new LinearDoubleKeyFrame + { + Value = 180 + DotOffSet + subAngle, + KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed * (2.75 / 7))) + }; + + //第一次匀速结束到匀加速结束 + var frame3 = new EasingDoubleKeyFrame + { + EasingFunction = new PowerEase + { + EasingMode = EasingMode.EaseIn + }, + Value = 360 + subAngle, + KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed * (3.5 / 7))) + }; + + //匀加速结束到匀减速结束 + var frame4 = new EasingDoubleKeyFrame + { + EasingFunction = new PowerEase + { + EasingMode = EasingMode.EaseOut + }, + Value = 540 + subAngle, + KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed * (4.25 / 7))) + }; + + //匀减速结束到第二次匀速结束 + var frame5 = new LinearDoubleKeyFrame + { + Value = 540 + DotOffSet + subAngle, + KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed * (6.25 / 7))) + }; + + //第二次匀速结束到匀加速结束 + var frame6 = new EasingDoubleKeyFrame + { + EasingFunction = new PowerEase + { + EasingMode = EasingMode.EaseIn + }, + Value = 720 + subAngle, + KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed)) + }; + + framesMove.KeyFrames.Add(frame0); + framesMove.KeyFrames.Add(frame1); + framesMove.KeyFrames.Add(frame2); + framesMove.KeyFrames.Add(frame3); + framesMove.KeyFrames.Add(frame4); + framesMove.KeyFrames.Add(frame5); + framesMove.KeyFrames.Add(frame6); + + Storyboard.SetTarget(framesMove, border); + Storyboard.SetTargetProperty(framesMove, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[0].(RotateTransform.Angle)")); + Storyboard.Children.Add(framesMove); + + if (NeedHidden) + { + //隐藏一段时间 + var frame7 = new DiscreteObjectKeyFrame + { + Value = Visibility.Collapsed, + KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed)) + }; + + var frame8 = new DiscreteObjectKeyFrame + { + Value = Visibility.Collapsed, + KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed + 0.4)) + }; + + var frame9 = new DiscreteObjectKeyFrame + { + Value = Visibility.Visible, + KeyTime = KeyTime.FromTimeSpan(TimeSpan.Zero) + }; + var framesVisibility = new ObjectAnimationUsingKeyFrames + { + BeginTime = TimeSpan.FromMilliseconds(DotDelayTime * i) + }; + framesVisibility.KeyFrames.Add(frame9); + framesVisibility.KeyFrames.Add(frame7); + framesVisibility.KeyFrames.Add(frame8); + Storyboard.SetTarget(framesVisibility, border); + Storyboard.SetTargetProperty(framesVisibility, new PropertyPath("(UIElement.Visibility)")); + Storyboard.Children.Add(framesVisibility); + } + PrivateCanvas.Children.Add(border); + } + + Storyboard.Begin(); + } + + private Border CreateBorder(int index) + { + var ellipse = CreateEllipse(index); + ellipse.HorizontalAlignment = HorizontalAlignment.Center; + ellipse.VerticalAlignment = VerticalAlignment.Bottom; + var rt = new RotateTransform + { + Angle = -DotInterval * index + }; + var myTransGroup = new TransformGroup(); + myTransGroup.Children.Add(rt); + var border = new Border + { + RenderTransformOrigin = new Point(0.5, 0.5), + RenderTransform = myTransGroup, + Child = ellipse, + Visibility = NeedHidden ? Visibility.Collapsed : Visibility.Visible + }; + border.SetBinding(WidthProperty, new Binding("Width") { Source = this }); + border.SetBinding(HeightProperty, new Binding("Height") { Source = this }); + + return border; + } + } +} \ No newline at end of file diff --git a/HandyControl/Controls/Loading/LoadingLine.cs b/HandyControl/Controls/Loading/LoadingLine.cs new file mode 100644 index 00000000..9f98a7c6 --- /dev/null +++ b/HandyControl/Controls/Loading/LoadingLine.cs @@ -0,0 +1,109 @@ +using System; +using System.Windows; +using System.Windows.Data; +using System.Windows.Media.Animation; +using System.Windows.Shapes; + +// ReSharper disable once CheckNamespace +namespace HandyControl.Controls +{ + /// + /// LoadingLine.xaml 的交互逻辑 + /// + public class LoadingLine : LoadingBase + { + private const double MoveLength = 80; + + private const double UniformScale = .6; + + public LoadingLine() + { + SetBinding(HeightProperty, new Binding("DotDiameter") { Source = this }); + } + + protected sealed override void UpdateDots() + { + if (DotCount < 1) return; + PrivateCanvas.Children.Clear(); + + //计算相关尺寸 + var centerWidth = DotDiameter * DotCount + DotInterval * (DotCount - 1) + MoveLength; + var speedDownLength = (ActualWidth - MoveLength) / 2; + var speedUniformLength = centerWidth / 2; + + //定义动画 + Storyboard = new Storyboard + { + RepeatBehavior = RepeatBehavior.Forever + }; + + //创建圆点 + for (var i = 0; i < DotCount; i++) + { + var ellipse = CreateEllipse(i); + + var frames = new ThicknessAnimationUsingKeyFrames + { + BeginTime = TimeSpan.FromMilliseconds(DotDelayTime * i) + }; + //开始位置 + var frame0 = new LinearThicknessKeyFrame + { + Value = new Thickness(ellipse.Margin.Left, 0, 0, 0), + KeyTime = KeyTime.FromTimeSpan(TimeSpan.Zero) + }; + + //开始位置到匀速开始 + var frame1 = new EasingThicknessKeyFrame + { + EasingFunction = new PowerEase + { + EasingMode = EasingMode.EaseOut + }, + Value = new Thickness(speedDownLength + ellipse.Margin.Left, 0, 0, 0), + KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed * (1 - UniformScale)/2)) + }; + + //匀速开始到匀速结束 + var frame2 = new LinearThicknessKeyFrame + { + Value = new Thickness(speedDownLength + speedUniformLength + ellipse.Margin.Left, 0, 0, 0), + KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed * (1 + UniformScale) / 2)) + }; + + //匀速结束到匀加速结束 + var frame3 = new EasingThicknessKeyFrame + { + EasingFunction = new PowerEase + { + EasingMode = EasingMode.EaseIn + }, + Value = new Thickness(ActualWidth + ellipse.Margin.Left + speedUniformLength, 0, 0, 0), + KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed)) + }; + + frames.KeyFrames.Add(frame0); + frames.KeyFrames.Add(frame1); + frames.KeyFrames.Add(frame2); + frames.KeyFrames.Add(frame3); + + Storyboard.SetTarget(frames, ellipse); + Storyboard.SetTargetProperty(frames, new PropertyPath(MarginProperty)); + Storyboard.Children.Add(frames); + + PrivateCanvas.Children.Add(ellipse); + } + + Storyboard.Begin(); + } + + private new Ellipse CreateEllipse(int index) + { + var ellipse = base.CreateEllipse(index); + ellipse.HorizontalAlignment = HorizontalAlignment.Left; + ellipse.VerticalAlignment = VerticalAlignment.Top; + ellipse.Margin = new Thickness(-(DotInterval + DotDiameter) * index, 0, 0, 0); + return ellipse; + } + } +} \ No newline at end of file diff --git a/HandyControl/HandyControl.csproj b/HandyControl/HandyControl.csproj index 6d854d8e..d8a19cdc 100644 --- a/HandyControl/HandyControl.csproj +++ b/HandyControl/HandyControl.csproj @@ -42,6 +42,9 @@ InfoTextBox.xaml + + + @@ -159,6 +162,18 @@ Designer MSBuild:Compile + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + Designer MSBuild:Compile @@ -171,6 +186,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/HandyControl/Themes/Styles/Base/LoadingBaseStyle.xaml b/HandyControl/Themes/Styles/Base/LoadingBaseStyle.xaml new file mode 100644 index 00000000..5aa742fd --- /dev/null +++ b/HandyControl/Themes/Styles/Base/LoadingBaseStyle.xaml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/HandyControl/Themes/Styles/Base/LoadingCircleBaseStyle.xaml b/HandyControl/Themes/Styles/Base/LoadingCircleBaseStyle.xaml new file mode 100644 index 00000000..cca16378 --- /dev/null +++ b/HandyControl/Themes/Styles/Base/LoadingCircleBaseStyle.xaml @@ -0,0 +1,19 @@ + + + + + + + + + \ No newline at end of file diff --git a/HandyControl/Themes/Styles/Base/LoadingLineBaseStyle.xaml b/HandyControl/Themes/Styles/Base/LoadingLineBaseStyle.xaml new file mode 100644 index 00000000..4efa6878 --- /dev/null +++ b/HandyControl/Themes/Styles/Base/LoadingLineBaseStyle.xaml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/HandyControl/Themes/Styles/Loading.xaml b/HandyControl/Themes/Styles/Loading.xaml new file mode 100644 index 00000000..8e5d4010 --- /dev/null +++ b/HandyControl/Themes/Styles/Loading.xaml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/HandyControl/Themes/Styles/Style.xaml b/HandyControl/Themes/Styles/Style.xaml index 2973af17..42d4f76e 100644 --- a/HandyControl/Themes/Styles/Style.xaml +++ b/HandyControl/Themes/Styles/Style.xaml @@ -22,10 +22,10 @@ - --> + \ No newline at end of file diff --git a/HandyControlDemo/MainWindow.xaml b/HandyControlDemo/MainWindow.xaml index 278c487e..8da76a79 100644 --- a/HandyControlDemo/MainWindow.xaml +++ b/HandyControlDemo/MainWindow.xaml @@ -3,9 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:controls="clr-namespace:HandyControl.Controls;assembly=HandyControl" - xmlns:model="clr-namespace:HandyControlDemo.Data.Model" mc:Ignorable="d" UseLayoutRounding="True" Style="{StaticResource WindowDefaultStyle}" @@ -282,7 +280,7 @@ --> - - --> +