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 { /// /// 常规 /// Regular, /// /// 实心的 /// Solid, /// /// 品牌(Logo) /// 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; } } } }