From 3f343a59311cfb861a770da053a9ff61923c99be Mon Sep 17 00:00:00 2001 From: tanglong <842690096@qq.com> Date: Thu, 25 Sep 2025 12:03:23 +0800 Subject: [PATCH] =?UTF-8?q?=E7=81=AB=E6=9F=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MyUserControl/CoverFlowControl1.xaml | 22 ++-- .../MyUserControl/CoverFlowControl1.xaml.cs | 121 ++++++++++++++++-- .../MyUserControl/CoverFlowItem.cs | 36 ++++-- .../ProgressToRectangleGeometryConverter.cs | 43 ++++--- 4 files changed, 169 insertions(+), 53 deletions(-) diff --git a/Wpf_AiSportsMicrospace/MyUserControl/CoverFlowControl1.xaml b/Wpf_AiSportsMicrospace/MyUserControl/CoverFlowControl1.xaml index 4995ded..affadd0 100644 --- a/Wpf_AiSportsMicrospace/MyUserControl/CoverFlowControl1.xaml +++ b/Wpf_AiSportsMicrospace/MyUserControl/CoverFlowControl1.xaml @@ -1,16 +1,16 @@  + xmlns:local="clr-namespace:Wpf_AiSportsMicrospace.MyUserControl"> - + @@ -30,13 +30,19 @@ - - + + - - + + + + + + + + diff --git a/Wpf_AiSportsMicrospace/MyUserControl/CoverFlowControl1.xaml.cs b/Wpf_AiSportsMicrospace/MyUserControl/CoverFlowControl1.xaml.cs index 964be4c..058578d 100644 --- a/Wpf_AiSportsMicrospace/MyUserControl/CoverFlowControl1.xaml.cs +++ b/Wpf_AiSportsMicrospace/MyUserControl/CoverFlowControl1.xaml.cs @@ -24,7 +24,6 @@ namespace Wpf_AiSportsMicrospace.MyUserControl /// /// CoverFlowControl1.xaml 的交互逻辑 /// - public partial class CoverFlowControl1 : UserControl { public ObservableCollection Images { get; set; } = new ObservableCollection(); @@ -36,18 +35,21 @@ namespace Wpf_AiSportsMicrospace.MyUserControl set { if (Images.Count == 0) return; + if (value < 0) _selectedIndex = Images.Count - 1; else if (value >= Images.Count) _selectedIndex = 0; else _selectedIndex = value; for (int i = 0; i < Images.Count; i++) - { - Images[i].IsSelected = (i == _selectedIndex); - if (i == _selectedIndex) Images[i].Progress = 0; - } + Images[i].IsSelected = i == _selectedIndex; UpdateLayoutWithAnimation(); + + // 启动进度条动画 + var current = Images[_selectedIndex]; + StartProgress(current); } + } public CoverFlowControl1() @@ -56,15 +58,95 @@ namespace Wpf_AiSportsMicrospace.MyUserControl DataContext = this; Loaded += (s, e) => UpdateLayoutWithAnimation(true); - var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(30) }; - timer.Tick += (s, e) => + //var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(5) }; + //timer.Tick += (s, e) => + //{ + // if (Images.Count == 0) return; + // var current = Images[_selectedIndex]; + // if (current.Progress < 1) + // current.Progress += 0.01; // 调整速度 + //}; + //timer.Start(); + } + private EventHandler _renderHandler; + + private CoverFlowItem _currentItem; + + private void StartProgress(CoverFlowItem item) + { + StopProgress(); + + _currentItem = item; + item.Progress = 0; + + var container = ItemsHost.ItemContainerGenerator.ContainerFromItem(item) as ContentPresenter; + if (container == null) return; + + var canvas = FindVisualChild(container); + var spark = FindVisualChild(canvas); + if (spark != null) + spark.Visibility = Visibility.Visible; + + _renderHandler = (s, e) => { - if (Images.Count == 0) return; - var current = Images[_selectedIndex]; - if (current.Progress < 1) - current.Progress += 0.01; // 调整速度 + if (_currentItem.Progress < 1) + { + _currentItem.Progress += 0.002; // 控制速度 + if (spark != null) + { + var pos = GetSparkPosition(_currentItem.Progress, 150, 200); + Canvas.SetLeft(spark, pos.X - spark.Width / 2); + Canvas.SetTop(spark, pos.Y - spark.Height / 2); + } + } + else + { + // 停止动画 + StopProgress(); + } }; - timer.Start(); + + CompositionTarget.Rendering += _renderHandler; + } + + private void StopProgress() + { + if (_renderHandler != null) + { + CompositionTarget.Rendering -= _renderHandler; + _renderHandler = null; + } + + if (_currentItem != null) + { + _currentItem.Progress = 0; + _currentItem.IsSelected = false; + + var container = ItemsHost.ItemContainerGenerator.ContainerFromItem(_currentItem) as ContentPresenter; + if (container != null) + { + var canvas = FindVisualChild(container); + var spark = FindVisualChild(canvas); + if (spark != null) + spark.Visibility = Visibility.Collapsed; + } + + _currentItem = null; + } + } + + private Point GetSparkPosition(double progress, double width, double height) + { + double perimeter = 2 * (width + height); + double len = progress * perimeter; + + if (len <= width) return new Point(len, 0); // 上边 + len -= width; + if (len <= height) return new Point(width, len); // 右边 + len -= height; + if (len <= width) return new Point(width - len, height); // 下边 + len -= width; + return new Point(0, height - len); // 左边 } private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) @@ -82,6 +164,7 @@ namespace Wpf_AiSportsMicrospace.MyUserControl private void UpdateLayoutWithAnimation(bool instant = false) { + double centerX = ActualWidth / 2; double spacing = 180; double sideScale = 0.8; @@ -145,6 +228,19 @@ namespace Wpf_AiSportsMicrospace.MyUserControl border.BeginAnimation(Border.OpacityProperty, new DoubleAnimation(targetOpacity, TimeSpan.FromMilliseconds(400))); } + + var item = Images[i]; + + if (i == SelectedIndex) + { + item.IsSelected = true; + StartProgress(item); // 开始绘制进度条 + 火花 + } + else + { + item.IsSelected = false; + StopProgress(); // 停止并重置进度 + } } } @@ -163,5 +259,4 @@ namespace Wpf_AiSportsMicrospace.MyUserControl public void SlideLeft() => SelectedIndex++; public void SlideRight() => SelectedIndex--; } - } diff --git a/Wpf_AiSportsMicrospace/MyUserControl/CoverFlowItem.cs b/Wpf_AiSportsMicrospace/MyUserControl/CoverFlowItem.cs index 34cfac5..15a85c0 100644 --- a/Wpf_AiSportsMicrospace/MyUserControl/CoverFlowItem.cs +++ b/Wpf_AiSportsMicrospace/MyUserControl/CoverFlowItem.cs @@ -10,25 +10,37 @@ namespace Wpf_AiSportsMicrospace.MyUserControl { public class CoverFlowItem : INotifyPropertyChanged { - private bool _isSelected; - private double _progress; // 0~1 - public Uri ImageUri { get; set; } - public bool IsSelected - { - get => _isSelected; - set { _isSelected = value; OnPropertyChanged(nameof(IsSelected)); } - } - + private double _progress; public double Progress { get => _progress; - set { _progress = value; OnPropertyChanged(nameof(Progress)); } + set + { + if (_progress != value) + { + _progress = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Progress))); + } + } + } + + private bool _isSelected; + public bool IsSelected + { + get => _isSelected; + set + { + if (_isSelected != value) + { + _isSelected = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSelected))); + } + } } public event PropertyChangedEventHandler PropertyChanged; - protected void OnPropertyChanged(string name) => - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } + } diff --git a/Wpf_AiSportsMicrospace/MyUserControl/ProgressToRectangleGeometryConverter.cs b/Wpf_AiSportsMicrospace/MyUserControl/ProgressToRectangleGeometryConverter.cs index ebbaf0e..1314f39 100644 --- a/Wpf_AiSportsMicrospace/MyUserControl/ProgressToRectangleGeometryConverter.cs +++ b/Wpf_AiSportsMicrospace/MyUserControl/ProgressToRectangleGeometryConverter.cs @@ -12,57 +12,60 @@ namespace Wpf_AiSportsMicrospace.MyUserControl { public class ProgressToRectangleGeometryConverter : IValueConverter { - // parameter: "width,height" + // parameter: "width,height" (例如 "150,200") public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - double progress = (double)value; - string[] sizes = parameter.ToString().Split(','); - double width = double.Parse(sizes[0]); - double height = double.Parse(sizes[1]); + if (!(value is double progress)) return Geometry.Empty; + if (parameter == null) return Geometry.Empty; + + var parts = parameter.ToString().Split(','); + if (parts.Length != 2) return Geometry.Empty; + if (!double.TryParse(parts[0], out double width)) return Geometry.Empty; + if (!double.TryParse(parts[1], out double height)) return Geometry.Empty; + + double perimeter = 2 * (width + height); + double len = Math.Max(0, Math.Min(perimeter, progress * perimeter)); var geo = new StreamGeometry(); using (var ctx = geo.Open()) { ctx.BeginFigure(new Point(0, 0), false, false); - double total = 2 * (width + height); - double len = progress * total; - - // 上边 + // top edge if (len <= width) { - ctx.LineTo(new Point(len, 0), true, true); + ctx.LineTo(new Point(len, 0), true, false); return geo; } - ctx.LineTo(new Point(width, 0), true, true); + ctx.LineTo(new Point(width, 0), true, false); len -= width; - // 右边 + // right edge if (len <= height) { - ctx.LineTo(new Point(width, len), true, true); + ctx.LineTo(new Point(width, len), true, false); return geo; } - ctx.LineTo(new Point(width, height), true, true); + ctx.LineTo(new Point(width, height), true, false); len -= height; - // 下边 + // bottom edge if (len <= width) { - ctx.LineTo(new Point(width - len, height), true, true); + ctx.LineTo(new Point(width - len, height), true, false); return geo; } - ctx.LineTo(new Point(0, height), true, true); + ctx.LineTo(new Point(0, height), true, false); len -= width; - // 左边 + // left edge if (len <= height) { - ctx.LineTo(new Point(0, height - len), true, true); + ctx.LineTo(new Point(0, height - len), true, false); } else { - ctx.LineTo(new Point(0, 0), true, true); + ctx.LineTo(new Point(0, 0), true, false); } } geo.Freeze();