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;
|
||
}
|
||
}
|
||
}
|
||
} |