using System; using System.Collections.ObjectModel; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Threading; namespace Wpf_AiSportsMicrospace.MyUserControl { /// /// CoverFlowControl.xaml 的交互逻辑 /// public partial class CoverFlowControl : UserControl {// 新增事件:进度条完成 public event Action? ProgressCompleted; public ObservableCollection Images { get; set; } = new ObservableCollection(); private int _selectedIndex = 0; private bool _isAnimating; private int _pendingSlideSteps; private const double CardWidth = 346; private const double CardHeight = 534; private const double CardGap = 30; // 卡片之间水平间距 private const double SlideDurationMs = 420; // 动画时长 public int SelectedIndex { get => _selectedIndex; set { if (Images.Count == 0) return; if (value < 0) _selectedIndex = Images.Count - 1; else if (value >= Images.Count) _selectedIndex = 0; else _selectedIndex = value; UpdateSelectionFlags(); RefreshCards(instant: true); // 启动进度条动画 //var current = Images[_selectedIndex]; //StartProgress(current); } } public CoverFlowControl() { InitializeComponent(); DataContext = this; Loaded += CoverFlowControl_Loaded; //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 CoverFlowControl_Loaded(object sender, RoutedEventArgs e) { if (Images.Count > 0) { UpdateSelectionFlags(); RefreshCards(instant: true); } CarouselCanvas.Focus(); Keyboard.Focus(CarouselCanvas); } /// /// 针对当前选中项启动进度条动画 /// public void StartSelectedProgress() { if (SelectedIndex >= 0 && SelectedIndex < Images.Count) { var current = Images[SelectedIndex]; if (current.Progress >= 1) return; // 已完成,直接返回,不再启动 StartProgress(current); } } public void CancelSelectedProgress() { if (SelectedIndex >= 0 && SelectedIndex < Images.Count) { var current = Images[SelectedIndex]; // 停止动画/计时 StopProgress(); // 清零当前的进度值 current.Progress = 0; } } public void StartProgress(CoverFlowItem item) { StopProgress(); _currentItem = item; item.Progress = 0; _renderHandler = (s, e) => { if (_currentItem.Progress < 1) { _currentItem.Progress += 0.01; // 控制速度 } else { ProgressCompleted?.Invoke(_currentItem); // 停止动画 StopProgress(); } }; CompositionTarget.Rendering += _renderHandler; } private void StopProgress() { if (_renderHandler != null) { CompositionTarget.Rendering -= _renderHandler; _renderHandler = null; } if (_currentItem != null) { _currentItem.Progress = 0; _currentItem = null; } } private void UpdateSelectionFlags() { for (int i = 0; i < Images.Count; i++) Images[i].IsSelected = i == _selectedIndex; } private int WrapIndex(int index) { if (Images.Count == 0) return 0; var m = index % Images.Count; if (m < 0) m += Images.Count; return m; } private double SlotDistance => CardWidth + CardGap; private double XFarLeft => 0; private double XLeft => SlotDistance; private double XCenter => SlotDistance * 2; private double XRight => SlotDistance * 3; private double XFarRight => SlotDistance * 4; private double XOffLeft => -SlotDistance; private double XOffRight => SlotDistance * 5; private void SetCard(ContentPresenter card, CoverFlowItem? item, double left, double opacity = 1) { card.Content = item; Canvas.SetLeft(card, left); Canvas.SetTop(card, 0); card.Opacity = opacity; } private void RefreshCards(bool instant) { if (Images.Count == 0) return; var farLeft = Images[WrapIndex(_selectedIndex - 2)]; var left = Images[WrapIndex(_selectedIndex - 1)]; var center = Images[WrapIndex(_selectedIndex)]; var right = Images[WrapIndex(_selectedIndex + 1)]; var farRight = Images[WrapIndex(_selectedIndex + 2)]; SetCard(CardFarLeft, farLeft, XFarLeft, 1); SetCard(CardLeft, left, XLeft, 1); SetCard(CardCenter, center, XCenter, 1); SetCard(CardRight, right, XRight, 1); SetCard(CardFarRight, farRight, XFarRight, 1); // spare stays hidden offscreen SetCard(CardSpare, null, XOffRight, 0); if (!instant) return; } private void Carousel_PreviewMouseWheel(object sender, MouseWheelEventArgs e) { if (e.Delta < 0) SlideLeft(); else if (e.Delta > 0) SlideRight(); e.Handled = true; } private void Carousel_PreviewKeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Right || e.Key == Key.Down) { SlideLeft(); e.Handled = true; } else if (e.Key == Key.Left || e.Key == Key.Up) { SlideRight(); e.Handled = true; } } private void CardLeft_Click(object sender, MouseButtonEventArgs e) { RequestSlide(-1); } private void CardRight_Click(object sender, MouseButtonEventArgs e) { RequestSlide(+1); } private void CardFarLeft_Click(object sender, MouseButtonEventArgs e) { RequestSlide(-2); } private void CardFarRight_Click(object sender, MouseButtonEventArgs e) { RequestSlide(+2); } private void CardCenter_Click(object sender, MouseButtonEventArgs e) { // center click: treat as selecting current (already selected) } private void RequestSlide(int steps) { if (Images.Count == 0) return; if (steps == 0) return; _pendingSlideSteps += steps; TryRunNextSlide(); } private void TryRunNextSlide() { if (_isAnimating) return; if (_pendingSlideSteps == 0) return; if (_pendingSlideSteps > 0) { _pendingSlideSteps--; AnimateLeftOne(); } else { _pendingSlideSteps++; AnimateRightOne(); } } private void AnimateLeftOne() { if (_isAnimating) return; if (Images.Count == 0) return; _isAnimating = true; StopProgress(); var newSelected = WrapIndex(_selectedIndex + 1); var incomingFarRight = Images[WrapIndex(newSelected + 2)]; // Prepare spare entering from the right (to far-right slot) SetCard(CardSpare, incomingFarRight, XOffRight, 1); var sb = new Storyboard { FillBehavior = FillBehavior.Stop }; var ease = new CubicEase { EasingMode = EasingMode.EaseOut }; var dur = TimeSpan.FromMilliseconds(SlideDurationMs); void AddMove(FrameworkElement fe, double toX) { var anim = new DoubleAnimation { To = toX, Duration = new Duration(dur), EasingFunction = ease }; Storyboard.SetTarget(anim, fe); Storyboard.SetTargetProperty(anim, new PropertyPath("(Canvas.Left)")); sb.Children.Add(anim); } void AddFade(FrameworkElement fe, double toOpacity) { var anim = new DoubleAnimation { To = toOpacity, Duration = new Duration(dur), EasingFunction = ease }; Storyboard.SetTarget(anim, fe); Storyboard.SetTargetProperty(anim, new PropertyPath("Opacity")); sb.Children.Add(anim); } // Far-left card slides out to left and fades AddMove(CardFarLeft, XOffLeft); AddFade(CardFarLeft, 0); // Shift others left by one slot, spare enters to far-right AddMove(CardLeft, XFarLeft); AddMove(CardCenter, XLeft); AddMove(CardRight, XCenter); AddMove(CardFarRight, XRight); AddMove(CardSpare, XFarRight); sb.Completed += (_, _) => { _selectedIndex = newSelected; UpdateSelectionFlags(); // After shift left: // new FarLeft = old Left // new Left = old Center // new Center = old Right // new Right = old FarRight // new FarRight = old Spare var oldFarLeftContent = (CoverFlowItem?)CardFarLeft.Content; var oldLeftContent = (CoverFlowItem?)CardLeft.Content; var oldCenterContent = (CoverFlowItem?)CardCenter.Content; var oldRightContent = (CoverFlowItem?)CardRight.Content; var oldFarRightContent = (CoverFlowItem?)CardFarRight.Content; var oldSpareContent = (CoverFlowItem?)CardSpare.Content; SetCard(CardFarLeft, oldLeftContent, XFarLeft, 1); SetCard(CardLeft, oldCenterContent, XLeft, 1); SetCard(CardCenter, oldRightContent, XCenter, 1); SetCard(CardRight, oldFarRightContent, XRight, 1); SetCard(CardFarRight, oldSpareContent, XFarRight, 1); SetCard(CardSpare, null, XOffRight, 0); _isAnimating = false; TryRunNextSlide(); }; sb.Begin(this, true); } private void AnimateRightOne() { if (_isAnimating) return; if (Images.Count == 0) return; _isAnimating = true; StopProgress(); var newSelected = WrapIndex(_selectedIndex - 1); var incomingFarLeft = Images[WrapIndex(newSelected - 2)]; // Prepare spare entering from the left (to far-left slot) SetCard(CardSpare, incomingFarLeft, XOffLeft, 1); var sb = new Storyboard { FillBehavior = FillBehavior.Stop }; var ease = new CubicEase { EasingMode = EasingMode.EaseOut }; var dur = TimeSpan.FromMilliseconds(SlideDurationMs); void AddMove(FrameworkElement fe, double toX) { var anim = new DoubleAnimation { To = toX, Duration = new Duration(dur), EasingFunction = ease }; Storyboard.SetTarget(anim, fe); Storyboard.SetTargetProperty(anim, new PropertyPath("(Canvas.Left)")); sb.Children.Add(anim); } void AddFade(FrameworkElement fe, double toOpacity) { var anim = new DoubleAnimation { To = toOpacity, Duration = new Duration(dur), EasingFunction = ease }; Storyboard.SetTarget(anim, fe); Storyboard.SetTargetProperty(anim, new PropertyPath("Opacity")); sb.Children.Add(anim); } // Far-right card slides out to right and fades AddMove(CardFarRight, XOffRight); AddFade(CardFarRight, 0); // Shift others right by one slot, spare enters to far-left AddMove(CardRight, XFarRight); AddMove(CardCenter, XRight); AddMove(CardLeft, XCenter); AddMove(CardFarLeft, XLeft); AddMove(CardSpare, XFarLeft); sb.Completed += (_, _) => { _selectedIndex = newSelected; UpdateSelectionFlags(); var oldFarLeftContent = (CoverFlowItem?)CardFarLeft.Content; var oldLeftContent = (CoverFlowItem?)CardLeft.Content; var oldCenterContent = (CoverFlowItem?)CardCenter.Content; var oldRightContent = (CoverFlowItem?)CardRight.Content; var oldFarRightContent = (CoverFlowItem?)CardFarRight.Content; var oldSpareContent = (CoverFlowItem?)CardSpare.Content; // After shift right: // new FarRight = old Right // new Right = old Center // new Center = old Left // new Left = old FarLeft // new FarLeft = old Spare SetCard(CardFarRight, oldRightContent, XFarRight, 1); SetCard(CardRight, oldCenterContent, XRight, 1); SetCard(CardCenter, oldLeftContent, XCenter, 1); SetCard(CardLeft, oldFarLeftContent, XLeft, 1); SetCard(CardFarLeft, oldSpareContent, XFarLeft, 1); SetCard(CardSpare, null, XOffRight, 0); _isAnimating = false; TryRunNextSlide(); }; sb.Begin(this, true); } public void SlideLeft() { RequestSlide(+1); } public void SlideRight() { RequestSlide(-1); } } }