前端代码(主要是样式)
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:AvaloniaProgressRing"
x:CompileBindings="True">
<Style Selector="Ellipse.ProgressRingEllipseStyle">
<Setter Property="Opacity" Value="0" />
<Setter Property="IsVisible" Value="{Binding $parent[local:ProgressRing].IsActive}" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Top" />
</Style>
<Style Selector="local|ProgressRing">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="{TemplateBinding Foreground}" />
<Setter Property="IsHitTestVisible" Value="False" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="MinHeight" Value="20" />
<Setter Property="MinWidth" Value="20" />
<Setter Property="ClipToBounds" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border
x:Name="Ring"
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="0"
Padding="{TemplateBinding Padding}"
MaxWidth="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=MaxSideLength}"
MaxHeight="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=MaxSideLength}"
IsVisible="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsActive}">
<Border.RenderTransform>
<TransformGroup>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</Border.RenderTransform>
<Grid>
<Canvas Name="E1R">
<Ellipse x:Name="E1"
Classes="ProgressRingEllipseStyle"
Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=EllipseDiameter}"
Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=EllipseDiameter}"
Margin="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=EllipseOffset}"
Fill="{TemplateBinding Foreground}" />
</Canvas>
<Canvas Name="E2R">
<Ellipse x:Name="E2"
Classes="ProgressRingEllipseStyle"
Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=EllipseDiameter}"
Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=EllipseDiameter}"
Margin="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=EllipseOffset}"
Fill="{TemplateBinding Foreground}" />
</Canvas>
<Canvas Name="E3R">
<Ellipse x:Name="E3"
Classes="ProgressRingEllipseStyle"
Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=EllipseDiameter}"
Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=EllipseDiameter}"
Margin="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=EllipseOffset}"
Fill="{TemplateBinding Foreground}" />
</Canvas>
<Canvas Name="E4R">
<Ellipse x:Name="E4"
Classes="ProgressRingEllipseStyle"
Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=EllipseDiameter}"
Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=EllipseDiameter}"
Margin="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=EllipseOffset}"
Fill="{TemplateBinding Foreground}" />
</Canvas>
<Canvas Name="E5R">
<Ellipse x:Name="E5"
Classes="ProgressRingEllipseStyle"
Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=EllipseDiameter}"
Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=EllipseDiameter}"
Margin="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=EllipseOffset}"
Fill="{TemplateBinding Foreground}" />
</Canvas>
<Canvas x:Name="E6R">
<Ellipse x:Name="E6"
Classes="ProgressRingEllipseStyle"
Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=EllipseDiameter}"
Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=EllipseDiameter}"
Margin="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=EllipseOffset}"
Fill="{TemplateBinding Foreground}" />
</Canvas>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style Selector="local|ProgressRing:active /template/ Ellipse#E1" >
<Style.Animations>
<Animation Duration="0:0:4.4" IterationCount="Infinite" Delay="0" FillMode="None">
<KeyFrame KeyTime="0:0:0.001">
<Setter Property="Opacity" Value="1"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.21">
<Setter Property="Opacity" Value="1"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.22">
<Setter Property="Opacity" Value="0"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.47">
<Setter Property="Opacity" Value="0"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="local|ProgressRing:active /template/ Ellipse#E2">
<Style.Animations>
<Animation Duration="0:0:4.4" IterationCount="Infinite" Delay="0:0:0.167" FillMode="None">
<KeyFrame KeyTime="0:0:0.001">
<Setter Property="Opacity" Value="1"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.21">
<Setter Property="Opacity" Value="1"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.22">
<Setter Property="Opacity" Value="0"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.47">
<Setter Property="Opacity" Value="0"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="local|ProgressRing:active /template/ Ellipse#E3">
<Style.Animations>
<Animation Duration="0:0:4.4" IterationCount="Infinite" Delay="0:0:0.334" FillMode="None">
<KeyFrame KeyTime="0:0:0.001">
<Setter Property="Opacity" Value="1"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.21">
<Setter Property="Opacity" Value="1"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.22">
<Setter Property="Opacity" Value="0"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.47">
<Setter Property="Opacity" Value="0"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="local|ProgressRing:active /template/ Ellipse#E4">
<Style.Animations>
<Animation Duration="0:0:4.4" IterationCount="Infinite" Delay="0:0:0.501" FillMode="None">
<KeyFrame KeyTime="0:0:0.001">
<Setter Property="Opacity" Value="1"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.21">
<Setter Property="Opacity" Value="1"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.22">
<Setter Property="Opacity" Value="0"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.47">
<Setter Property="Opacity" Value="0"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="local|ProgressRing:active /template/ Ellipse#E5">
<Style.Animations>
<Animation Duration="0:0:4.4" IterationCount="Infinite" Delay="0:0:0.668" FillMode="None">
<KeyFrame KeyTime="0:0:0.001">
<Setter Property="Opacity" Value="1"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.21">
<Setter Property="Opacity" Value="1"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.22">
<Setter Property="Opacity" Value="0"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.47">
<Setter Property="Opacity" Value="0"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="local|ProgressRing:active /template/ Ellipse#E6">
<Style.Animations>
<Animation Duration="0:0:4.4" IterationCount="Infinite" Delay="0:0:0.835" FillMode="None">
<KeyFrame KeyTime="0:0:0.001">
<Setter Property="Opacity" Value="1"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.21">
<Setter Property="Opacity" Value="1"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.22">
<Setter Property="Opacity" Value="0"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.47">
<Setter Property="Opacity" Value="0"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="local|ProgressRing:active /template/ Canvas#E1R">
<!--<Style.Setters>
<Setter Property="Background" Value="LightBlue"/>
</Style.Setters>-->
<Style.Animations>
<Animation Duration="0:0:4.4" IterationCount="Infinite" Delay="0:0:0.0"
FillMode="None">
<KeyFrame KeyTime="0:0:0.001" KeySpline="0.13,0.21,0.1,0.7">
<Setter Property="RotateTransform.Angle" Value="-110"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:0.433" KeySpline="0.02,0.33,0.38,0.77">
<Setter Property="RotateTransform.Angle" Value="10"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:1.2">
<Setter Property="RotateTransform.Angle" Value="93"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:1.617" KeySpline="0.57,0.17,0.95,0.75" >
<Setter Property="RotateTransform.Angle" Value="205"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:2.017" KeySpline="0,0.19,0.07,0.72">
<Setter Property="RotateTransform.Angle" Value="357"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:2.783">
<Setter Property="RotateTransform.Angle" Value="439"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.217" KeySpline="0,0,0.95,0.37">
<Setter Property="RotateTransform.Angle" Value="585"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.47">
<Setter Property="RotateTransform.Angle" Value="610"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="local|ProgressRing:active /template/ Canvas#E2R">
<Style.Animations>
<Animation Duration="0:0:4.4" IterationCount="Infinite" Delay="0:0:0.167"
FillMode="None">
<KeyFrame KeyTime="0:0:0.001" KeySpline="0.13,0.21,0.1,0.7">
<Setter Property="RotateTransform.Angle" Value="-116"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:0.433" KeySpline="0.02,0.33,0.38,0.77">
<Setter Property="RotateTransform.Angle" Value="4"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:1.2">
<Setter Property="RotateTransform.Angle" Value="87"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:1.617" KeySpline="0.57,0.17,0.95,0.75" >
<Setter Property="RotateTransform.Angle" Value="199"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:2.017" KeySpline="0,0.19,0.07,0.72">
<Setter Property="RotateTransform.Angle" Value="351"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:2.783">
<Setter Property="RotateTransform.Angle" Value="433"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.217" KeySpline="0,0,0.95,0.37">
<Setter Property="RotateTransform.Angle" Value="579"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.47">
<Setter Property="RotateTransform.Angle" Value="604"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="local|ProgressRing:active /template/ Canvas#E3R">
<Style.Setters>
<Setter Property="IsVisible" Value="True"/>
</Style.Setters>
<Style.Animations>
<Animation Duration="0:0:4.4" IterationCount="Infinite" Delay="0:0:0.334"
FillMode="None">
<KeyFrame KeyTime="0:0:0.001" KeySpline="0.13,0.21,0.1,0.7">
<Setter Property="RotateTransform.Angle" Value="-122"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:0.433" KeySpline="0.02,0.33,0.38,0.77">
<Setter Property="RotateTransform.Angle" Value="-2"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:1.2">
<Setter Property="RotateTransform.Angle" Value="81"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:1.617" KeySpline="0.57,0.17,0.95,0.75" >
<Setter Property="RotateTransform.Angle" Value="193"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:2.017" KeySpline="0,0.19,0.07,0.72">
<Setter Property="RotateTransform.Angle" Value="345"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:2.783">
<Setter Property="RotateTransform.Angle" Value="427"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.217" KeySpline="0,0,0.95,0.37">
<Setter Property="RotateTransform.Angle" Value="567"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.47">
<Setter Property="RotateTransform.Angle" Value="598"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="local|ProgressRing:active /template/ Canvas#E4R">
<Style.Setters>
<Setter Property="IsVisible" Value="True"/>
</Style.Setters>
<Style.Animations>
<Animation Duration="0:0:4.4" IterationCount="Infinite" Delay="0:0:0.501"
FillMode="None">
<KeyFrame KeyTime="0:0:0.001" KeySpline="0.13,0.21,0.1,0.7">
<Setter Property="RotateTransform.Angle" Value="-128"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:0.433" KeySpline="0.02,0.33,0.38,0.77">
<Setter Property="RotateTransform.Angle" Value="-8"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:1.2">
<Setter Property="RotateTransform.Angle" Value="75"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:1.617" KeySpline="0.57,0.17,0.95,0.75" >
<Setter Property="RotateTransform.Angle" Value="187"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:2.017" KeySpline="0,0.19,0.07,0.72">
<Setter Property="RotateTransform.Angle" Value="339"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:2.783">
<Setter Property="RotateTransform.Angle" Value="421"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.217" KeySpline="0,0,0.95,0.37">
<Setter Property="RotateTransform.Angle" Value="567"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.47">
<Setter Property="RotateTransform.Angle" Value="592"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="local|ProgressRing:active /template/ Canvas#E5R">
<Style.Setters>
<Setter Property="IsVisible" Value="True"/>
</Style.Setters>
<Style.Animations>
<Animation Duration="0:0:4.4" IterationCount="Infinite" Delay="0:0:0.668"
FillMode="None">
<KeyFrame KeyTime="0:0:0.001" KeySpline="0.13,0.21,0.1,0.7">
<Setter Property="RotateTransform.Angle" Value="-134"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:0.433" KeySpline="0.02,0.33,0.38,0.77">
<Setter Property="RotateTransform.Angle" Value="-14"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:1.2">
<Setter Property="RotateTransform.Angle" Value="69"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:1.617" KeySpline="0.57,0.17,0.95,0.75" >
<Setter Property="RotateTransform.Angle" Value="181"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:2.017" KeySpline="0,0.19,0.07,0.72">
<Setter Property="RotateTransform.Angle" Value="331"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:2.783">
<Setter Property="RotateTransform.Angle" Value="415"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.217" KeySpline="0,0,0.95,0.37">
<Setter Property="RotateTransform.Angle" Value="561"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.47">
<Setter Property="RotateTransform.Angle" Value="586"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="local|ProgressRing:active /template/ Canvas#E6R">
<Style.Setters>
<Setter Property="IsVisible" Value="True"/>
</Style.Setters>
<Style.Animations>
<Animation Duration="0:0:4.4" IterationCount="Infinite" Delay="0:0:0.835"
FillMode="None">
<KeyFrame KeyTime="0:0:0.001" KeySpline="0.13,0.21,0.1,0.7">
<Setter Property="RotateTransform.Angle" Value="-140"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:0.433" KeySpline="0.02,0.33,0.38,0.77">
<Setter Property="RotateTransform.Angle" Value="-20"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:1.2">
<Setter Property="RotateTransform.Angle" Value="63"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:1.617" KeySpline="0.57,0.17,0.95,0.75" >
<Setter Property="RotateTransform.Angle" Value="175"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:2.017" KeySpline="0,0.19,0.07,0.72">
<Setter Property="RotateTransform.Angle" Value="325"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:2.783">
<Setter Property="RotateTransform.Angle" Value="409"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.217" KeySpline="0,0,0.95,0.37">
<Setter Property="RotateTransform.Angle" Value="555"/>
</KeyFrame>
<KeyFrame KeyTime="0:0:3.47">
<Setter Property="RotateTransform.Angle" Value="580"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="local|ProgressRing:small /template/ Canvas#E6R">
<Style.Setters>
<Setter Property="IsVisible" Value="False"/>
</Style.Setters>
</Style>
<Style Selector="local|ProgressRing:large /template/ Canvas#E6R">
<Style.Setters>
<Setter Property="IsVisible" Value="True"/>
</Style.Setters>
</Style>
</Styles>
后台代码
using Avalonia;
using Avalonia.Controls.Primitives;
using Avalonia.Media;
using System;
using System.Collections.Generic;
using System.Text;
namespace AvaloniaProgressRing
{
public class ProgressRing : TemplatedControl
{
private const string LargeState = ":large";
private const string SmallState = ":small";
private const string InactiveState = ":inactive";
private const string ActiveState = ":active";
private double _maxSideLength = 10;
private double _ellipseDiameter = 10;
private Thickness _ellipseOffset = new Thickness(2);
static ProgressRing()
{
//DefaultStyleKeyProperty.OverrideMetadata(typeof(ProgressRing),
// new FrameworkPropertyMetadata(typeof(ProgressRing)));
}
public ProgressRing()
{
}
#region IsActive
public bool IsActive
{
get => (bool)GetValue(IsActiveProperty);
set => SetValue(IsActiveProperty, value);
}
public static readonly StyledProperty<bool> IsActiveProperty =
AvaloniaProperty.Register<ProgressRing, bool>(
nameof(IsActive),
defaultValue: true);
private static void OnIsActiveChanged(AvaloniaObject obj, bool arg2)
{
((ProgressRing)obj).UpdateVisualStates();
}
public static readonly DirectProperty<ProgressRing, double> MaxSideLengthProperty =
AvaloniaProperty.RegisterDirect<ProgressRing, double>(
nameof(MaxSideLength),
o => o.MaxSideLength);
public double MaxSideLength
{
get { return _maxSideLength; }
private set { SetAndRaise(MaxSideLengthProperty, ref _maxSideLength, value); }
}
public static readonly DirectProperty<ProgressRing, double> EllipseDiameterProperty =
AvaloniaProperty.RegisterDirect<ProgressRing, double>(
nameof(EllipseDiameter),
o => o.EllipseDiameter);
public double EllipseDiameter
{
get { return _ellipseDiameter; }
private set { SetAndRaise(EllipseDiameterProperty, ref _ellipseDiameter, value); }
}
public static readonly DirectProperty<ProgressRing, Thickness> EllipseOffsetProperty =
AvaloniaProperty.RegisterDirect<ProgressRing, Thickness>(
nameof(EllipseOffset),
o => o.EllipseOffset);
public Thickness EllipseOffset
{
get { return _ellipseOffset; }
private set { SetAndRaise(EllipseOffsetProperty, ref _ellipseOffset, value); }
}
#endregion
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
double maxSideLength = Math.Min(this.Width, this.Height);
double ellipseDiameter = 0.1 * maxSideLength;
if (maxSideLength <= 40)
{
ellipseDiameter += 1;
}
EllipseDiameter = ellipseDiameter;
MaxSideLength = maxSideLength;
EllipseOffset = new Thickness(0, maxSideLength / 2 - ellipseDiameter, 0, 0);
UpdateVisualStates();
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == IsActiveProperty)
{
UpdateVisualStates();
}
}
private void UpdateVisualStates()
{
PseudoClasses.Remove(ActiveState);
PseudoClasses.Remove(InactiveState);
PseudoClasses.Remove(SmallState);
PseudoClasses.Remove(LargeState);
PseudoClasses.Add(IsActive ? ActiveState : InactiveState);
PseudoClasses.Add(_maxSideLength < 60 ? SmallState : LargeState);
}
}
}
运行效果