300 lines
10 KiB
C#
300 lines
10 KiB
C#
|
|
using System.Windows;
|
|||
|
|
using System.Windows.Controls;
|
|||
|
|
using System.Windows.Media;
|
|||
|
|
using System.Windows.Media.Animation;
|
|||
|
|
using System.Windows.Threading;
|
|||
|
|
using PropertyMetadata = System.Windows.PropertyMetadata;
|
|||
|
|
|
|||
|
|
namespace YY.Admin.Core.Controls
|
|||
|
|
{
|
|||
|
|
//图标官网
|
|||
|
|
public enum FontAwesomeStyle
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// 常规
|
|||
|
|
/// </summary>
|
|||
|
|
Regular,
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 实心的
|
|||
|
|
/// </summary>
|
|||
|
|
Solid,
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 品牌(Logo)
|
|||
|
|
/// </summary>
|
|||
|
|
Brands
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public class FontAwesomeIcon : TextBlock
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
#region Icon 属性
|
|||
|
|
public static readonly DependencyProperty IconProperty =
|
|||
|
|
DependencyProperty.Register(nameof(Icon), typeof(string), typeof(FontAwesomeIcon),
|
|||
|
|
new PropertyMetadata(null, (d, e) => ((FontAwesomeIcon)d).Text = e.NewValue?.ToString()));
|
|||
|
|
|
|||
|
|
public string Icon
|
|||
|
|
{
|
|||
|
|
get => (string)GetValue(IconProperty);
|
|||
|
|
set => SetValue(IconProperty, value);
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region Spin 属性
|
|||
|
|
public static readonly DependencyProperty SpinProperty =
|
|||
|
|
DependencyProperty.Register(nameof(Spin), typeof(bool), typeof(FontAwesomeIcon),
|
|||
|
|
new PropertyMetadata(false, (d, e) => ((FontAwesomeIcon)d).UpdateAnimation()));
|
|||
|
|
|
|||
|
|
public bool Spin
|
|||
|
|
{
|
|||
|
|
get => (bool)GetValue(SpinProperty);
|
|||
|
|
set => SetValue(SpinProperty, value);
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region SpinSpeed 属性
|
|||
|
|
public static readonly DependencyProperty SpinSpeedProperty =
|
|||
|
|
DependencyProperty.Register(nameof(SpinSpeed), typeof(double), typeof(FontAwesomeIcon),
|
|||
|
|
new PropertyMetadata(1.0, (d, e) =>
|
|||
|
|
{
|
|||
|
|
var fa = (FontAwesomeIcon)d;
|
|||
|
|
if (fa.Spin)
|
|||
|
|
fa.StartSpin();
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
public double SpinSpeed
|
|||
|
|
{
|
|||
|
|
get => (double)GetValue(SpinSpeedProperty);
|
|||
|
|
set => SetValue(SpinSpeedProperty, value);
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region Beat 属性
|
|||
|
|
public static readonly DependencyProperty BeatProperty =
|
|||
|
|
DependencyProperty.Register(nameof(Beat), typeof(bool), typeof(FontAwesomeIcon),
|
|||
|
|
new PropertyMetadata(false, (d, e) => ((FontAwesomeIcon)d).UpdateAnimation()));
|
|||
|
|
|
|||
|
|
public bool Beat
|
|||
|
|
{
|
|||
|
|
get => (bool)GetValue(BeatProperty);
|
|||
|
|
set => SetValue(BeatProperty, value);
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region BeatScale 属性
|
|||
|
|
public static readonly DependencyProperty BeatScaleProperty =
|
|||
|
|
DependencyProperty.Register(nameof(BeatScale), typeof(double), typeof(FontAwesomeIcon),
|
|||
|
|
new PropertyMetadata(1.3));
|
|||
|
|
|
|||
|
|
public double BeatScale
|
|||
|
|
{
|
|||
|
|
get => (double)GetValue(BeatScaleProperty);
|
|||
|
|
set => SetValue(BeatScaleProperty, value);
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region BeatDuration 属性
|
|||
|
|
public static readonly DependencyProperty BeatDurationProperty =
|
|||
|
|
DependencyProperty.Register(nameof(BeatDuration), typeof(double), typeof(FontAwesomeIcon),
|
|||
|
|
new PropertyMetadata(0.5));
|
|||
|
|
|
|||
|
|
public double BeatDuration
|
|||
|
|
{
|
|||
|
|
get => (double)GetValue(BeatDurationProperty);
|
|||
|
|
set => SetValue(BeatDurationProperty, value);
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region IconFamily 属性
|
|||
|
|
public static readonly DependencyProperty IconFamilyProperty =
|
|||
|
|
DependencyProperty.Register(nameof(IconFamily), typeof(FontAwesomeStyle), typeof(FontAwesomeIcon),
|
|||
|
|
new PropertyMetadata(FontAwesomeStyle.Regular, OnIconFamilyChanged));
|
|||
|
|
|
|||
|
|
public FontAwesomeStyle IconFamily
|
|||
|
|
{
|
|||
|
|
get => (FontAwesomeStyle)GetValue(IconFamilyProperty);
|
|||
|
|
set => SetValue(IconFamilyProperty, value);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static void OnIconFamilyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|||
|
|
{
|
|||
|
|
var fa = (FontAwesomeIcon)d;
|
|||
|
|
fa.UpdateFontFamily();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void UpdateFontFamily()
|
|||
|
|
{
|
|||
|
|
FontFamily = IconFamily switch
|
|||
|
|
{
|
|||
|
|
FontAwesomeStyle.Regular => (FontFamily)Application.Current.Resources["FontAwesomeRegular"],
|
|||
|
|
FontAwesomeStyle.Solid => (FontFamily)Application.Current.Resources["FontAwesomeSolid"],
|
|||
|
|
FontAwesomeStyle.Brands => (FontFamily)Application.Current.Resources["FontAwesomeBrands"],
|
|||
|
|
_ => (FontFamily)Application.Current.Resources["FontAwesomeRegular"]
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
// Transform components
|
|||
|
|
private RotateTransform? _rotateTransform;
|
|||
|
|
private ScaleTransform? _scaleTransform;
|
|||
|
|
private TransformGroup? _transformGroup;
|
|||
|
|
|
|||
|
|
private bool _initialized = false;
|
|||
|
|
private DoubleAnimation? _currentSpinAnimation;
|
|||
|
|
|
|||
|
|
public FontAwesomeIcon()
|
|||
|
|
{
|
|||
|
|
// 设置文本对齐方式,确保内容居中
|
|||
|
|
TextAlignment = TextAlignment.Center;
|
|||
|
|
VerticalAlignment = VerticalAlignment.Center;
|
|||
|
|
HorizontalAlignment = HorizontalAlignment.Center;
|
|||
|
|
|
|||
|
|
// 设置默认字体家庭
|
|||
|
|
UpdateFontFamily();
|
|||
|
|
|
|||
|
|
// 创建 transforms
|
|||
|
|
_rotateTransform = new RotateTransform();
|
|||
|
|
_scaleTransform = new ScaleTransform(1, 1);
|
|||
|
|
_transformGroup = new TransformGroup();
|
|||
|
|
_transformGroup.Children.Add(_scaleTransform);
|
|||
|
|
_transformGroup.Children.Add(_rotateTransform);
|
|||
|
|
|
|||
|
|
// 使用 RenderTransform 避免布局抖动
|
|||
|
|
RenderTransform = _transformGroup;
|
|||
|
|
// 关键:设置变换原点为内容中心
|
|||
|
|
RenderTransformOrigin = new Point(0.5, 0.5);
|
|||
|
|
|
|||
|
|
// 延迟初始化到 Loaded
|
|||
|
|
Loaded += OnLoadedSafe;
|
|||
|
|
Unloaded += OnUnloadedSafe;
|
|||
|
|
SizeChanged += OnSizeChangedSafe;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnLoadedSafe(object? sender, RoutedEventArgs e)
|
|||
|
|
{
|
|||
|
|
Dispatcher.BeginInvoke((Action)(() =>
|
|||
|
|
{
|
|||
|
|
if (_initialized) return;
|
|||
|
|
_initialized = true;
|
|||
|
|
|
|||
|
|
UpdateAnimation();
|
|||
|
|
}), DispatcherPriority.Loaded);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnSizeChangedSafe(object? sender, SizeChangedEventArgs e)
|
|||
|
|
{
|
|||
|
|
// 确保变换原点始终居中
|
|||
|
|
RenderTransformOrigin = new Point(0.5, 0.5);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnUnloadedSafe(object? sender, RoutedEventArgs e)
|
|||
|
|
{
|
|||
|
|
// 停止动画,避免内存泄漏
|
|||
|
|
StopSpin();
|
|||
|
|
StopBeat();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void UpdateAnimation()
|
|||
|
|
{
|
|||
|
|
if (DesignerProperties.GetIsInDesignMode(this)) return;
|
|||
|
|
|
|||
|
|
if (!_initialized)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (Spin) StartSpin(); else StopSpin();
|
|||
|
|
if (Beat) StartBeat(); else StopBeat();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void StartSpin()
|
|||
|
|
{
|
|||
|
|
if (_rotateTransform == null) return;
|
|||
|
|
|
|||
|
|
// 重新确认 RenderTransform 已正确赋值
|
|||
|
|
if (RenderTransform != _transformGroup)
|
|||
|
|
{
|
|||
|
|
RenderTransform = _transformGroup;
|
|||
|
|
RenderTransformOrigin = new Point(0.5, 0.5);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 先取消旧动画
|
|||
|
|
StopSpin();
|
|||
|
|
|
|||
|
|
// 方法1:使用 By 动画实现无缝旋转(推荐)
|
|||
|
|
var anim = new DoubleAnimation
|
|||
|
|
{
|
|||
|
|
By = 360, // 每次增加360度
|
|||
|
|
Duration = TimeSpan.FromSeconds(Math.Max(0.01, SpinSpeed)),
|
|||
|
|
RepeatBehavior = RepeatBehavior.Forever
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 动画在 WPF 内部直接使用 Freezable 缓存,提高性能
|
|||
|
|
anim.Freeze();
|
|||
|
|
|
|||
|
|
_currentSpinAnimation = anim;
|
|||
|
|
_rotateTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
|
|||
|
|
|
|||
|
|
// 方法2:使用 IsCumulative 属性(备选方案)
|
|||
|
|
/*
|
|||
|
|
var anim = new DoubleAnimation(0, 360, new Duration(TimeSpan.FromSeconds(Math.Max(0.01, SpinSpeed))))
|
|||
|
|
{
|
|||
|
|
RepeatBehavior = RepeatBehavior.Forever,
|
|||
|
|
IsCumulative = true // 累积值,避免跳回0度
|
|||
|
|
};
|
|||
|
|
*/
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void StopSpin()
|
|||
|
|
{
|
|||
|
|
_rotateTransform?.BeginAnimation(RotateTransform.AngleProperty, null);
|
|||
|
|
_currentSpinAnimation = null;
|
|||
|
|
|
|||
|
|
// 重置旋转角度
|
|||
|
|
if (_rotateTransform != null)
|
|||
|
|
_rotateTransform.Angle = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void StartBeat()
|
|||
|
|
{
|
|||
|
|
if (_scaleTransform == null)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 重新确认 RenderTransform 已正确赋值
|
|||
|
|
if (RenderTransform != _transformGroup)
|
|||
|
|
{
|
|||
|
|
RenderTransform = _transformGroup;
|
|||
|
|
RenderTransformOrigin = new Point(0.5, 0.5);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 先停止已有缩放动画
|
|||
|
|
_scaleTransform.BeginAnimation(ScaleTransform.ScaleXProperty, null);
|
|||
|
|
_scaleTransform.BeginAnimation(ScaleTransform.ScaleYProperty, null);
|
|||
|
|
|
|||
|
|
// 使用更平滑的缓动函数
|
|||
|
|
var anim = new DoubleAnimation(1.0, BeatScale, new Duration(TimeSpan.FromSeconds(Math.Max(0.01, BeatDuration))))
|
|||
|
|
{
|
|||
|
|
AutoReverse = true,
|
|||
|
|
RepeatBehavior = RepeatBehavior.Forever,
|
|||
|
|
EasingFunction = new QuadraticEase() { EasingMode = EasingMode.EaseInOut }
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
_scaleTransform.BeginAnimation(ScaleTransform.ScaleXProperty, anim);
|
|||
|
|
_scaleTransform.BeginAnimation(ScaleTransform.ScaleYProperty, anim);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void StopBeat()
|
|||
|
|
{
|
|||
|
|
if (_scaleTransform != null)
|
|||
|
|
{
|
|||
|
|
_scaleTransform.BeginAnimation(ScaleTransform.ScaleXProperty, null);
|
|||
|
|
_scaleTransform.BeginAnimation(ScaleTransform.ScaleYProperty, null);
|
|||
|
|
_scaleTransform.ScaleX = 1.0;
|
|||
|
|
_scaleTransform.ScaleY = 1.0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|