This commit is contained in:
tanglong 2025-09-25 12:03:23 +08:00
parent 99f4bfe607
commit 3f343a5931
4 changed files with 169 additions and 53 deletions

View File

@ -1,16 +1,16 @@
<UserControl x:Class="Wpf_AiSportsMicrospace.MyUserControl.CoverFlowControl1" <UserControl x:Class="Wpf_AiSportsMicrospace.MyUserControl.CoverFlowControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:_3DTools="clr-namespace:_3DTools;assembly=3DTools"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Wpf_AiSportsMicrospace.MyUserControl" xmlns:local="clr-namespace:Wpf_AiSportsMicrospace.MyUserControl">
Height="300" Width="600">
<UserControl.Resources> <UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter"/> <BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
<local:ProgressToRectangleGeometryConverter x:Key="ProgressToRectangleGeometryConverter"/> <local:ProgressToRectangleGeometryConverter x:Key="ProgressToRectangleGeometryConverter"/>
</UserControl.Resources> </UserControl.Resources>
<Grid Background="Transparent"> <Grid Background="Transparent">
<ItemsControl x:Name="ItemsHost" ItemsSource="{Binding Images, RelativeSource={RelativeSource AncestorType=UserControl}}"> <ItemsControl x:Name="ItemsHost" ItemsSource="{Binding Images, RelativeSource={RelativeSource AncestorType=UserControl}}">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
@ -30,13 +30,19 @@
</TransformGroup> </TransformGroup>
</Border.RenderTransform> </Border.RenderTransform>
<Grid> <Canvas Width="150" Height="200">
<Image Source="{Binding ImageUri}" Stretch="UniformToFill" Width="150" Height="200"/> <Image Source="{Binding ImageUri}" Width="150" Height="200" Stretch="UniformToFill"/>
<!-- 矩形进度条 --> <!-- 矩形进度条 -->
<Path Stroke="Red" StrokeThickness="4" Visibility="{Binding IsSelected, Converter={StaticResource BoolToVisibilityConverter}}" <Path Stroke="Gold" StrokeThickness="4"
Data="{Binding Progress, Converter={StaticResource ProgressToRectangleGeometryConverter}, ConverterParameter='150,200'}"/> Data="{Binding Progress, Converter={StaticResource ProgressToRectangleGeometryConverter}, ConverterParameter='150,200'}"/>
</Grid> <!-- 火花 -->
<Ellipse x:Name="Spark" Width="8" Height="8" Fill="Yellow" Visibility="Collapsed">
<Ellipse.Effect>
<DropShadowEffect Color="White" BlurRadius="10" ShadowDepth="0"/>
</Ellipse.Effect>
</Ellipse>
</Canvas>
</Border> </Border>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>

View File

@ -24,7 +24,6 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
/// <summary> /// <summary>
/// CoverFlowControl1.xaml 的交互逻辑 /// CoverFlowControl1.xaml 的交互逻辑
/// </summary> /// </summary>
public partial class CoverFlowControl1 : UserControl public partial class CoverFlowControl1 : UserControl
{ {
public ObservableCollection<CoverFlowItem> Images { get; set; } = new ObservableCollection<CoverFlowItem>(); public ObservableCollection<CoverFlowItem> Images { get; set; } = new ObservableCollection<CoverFlowItem>();
@ -36,18 +35,21 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
set set
{ {
if (Images.Count == 0) return; if (Images.Count == 0) return;
if (value < 0) _selectedIndex = Images.Count - 1; if (value < 0) _selectedIndex = Images.Count - 1;
else if (value >= Images.Count) _selectedIndex = 0; else if (value >= Images.Count) _selectedIndex = 0;
else _selectedIndex = value; else _selectedIndex = value;
for (int i = 0; i < Images.Count; i++) for (int i = 0; i < Images.Count; i++)
{ Images[i].IsSelected = i == _selectedIndex;
Images[i].IsSelected = (i == _selectedIndex);
if (i == _selectedIndex) Images[i].Progress = 0;
}
UpdateLayoutWithAnimation(); UpdateLayoutWithAnimation();
// 启动进度条动画
var current = Images[_selectedIndex];
StartProgress(current);
} }
} }
public CoverFlowControl1() public CoverFlowControl1()
@ -56,15 +58,95 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
DataContext = this; DataContext = this;
Loaded += (s, e) => UpdateLayoutWithAnimation(true); Loaded += (s, e) => UpdateLayoutWithAnimation(true);
var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(30) }; //var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(5) };
timer.Tick += (s, e) => //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<Canvas>(container);
var spark = FindVisualChild<Ellipse>(canvas);
if (spark != null)
spark.Visibility = Visibility.Visible;
_renderHandler = (s, e) =>
{ {
if (Images.Count == 0) return; if (_currentItem.Progress < 1)
var current = Images[_selectedIndex]; {
if (current.Progress < 1) _currentItem.Progress += 0.002; // 控制速度
current.Progress += 0.01; // 调整速度 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<Canvas>(container);
var spark = FindVisualChild<Ellipse>(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) private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
@ -82,6 +164,7 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
private void UpdateLayoutWithAnimation(bool instant = false) private void UpdateLayoutWithAnimation(bool instant = false)
{ {
double centerX = ActualWidth / 2; double centerX = ActualWidth / 2;
double spacing = 180; double spacing = 180;
double sideScale = 0.8; double sideScale = 0.8;
@ -145,6 +228,19 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
border.BeginAnimation(Border.OpacityProperty, border.BeginAnimation(Border.OpacityProperty,
new DoubleAnimation(targetOpacity, TimeSpan.FromMilliseconds(400))); 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 SlideLeft() => SelectedIndex++;
public void SlideRight() => SelectedIndex--; public void SlideRight() => SelectedIndex--;
} }
} }

View File

@ -10,25 +10,37 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
{ {
public class CoverFlowItem : INotifyPropertyChanged public class CoverFlowItem : INotifyPropertyChanged
{ {
private bool _isSelected;
private double _progress; // 0~1
public Uri ImageUri { get; set; } public Uri ImageUri { get; set; }
public bool IsSelected private double _progress;
{
get => _isSelected;
set { _isSelected = value; OnPropertyChanged(nameof(IsSelected)); }
}
public double Progress public double Progress
{ {
get => _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; public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
} }
} }

View File

@ -12,57 +12,60 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
{ {
public class ProgressToRectangleGeometryConverter : IValueConverter public class ProgressToRectangleGeometryConverter : IValueConverter
{ {
// parameter: "width,height" // parameter: "width,height" (例如 "150,200")
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{ {
double progress = (double)value; if (!(value is double progress)) return Geometry.Empty;
string[] sizes = parameter.ToString().Split(','); if (parameter == null) return Geometry.Empty;
double width = double.Parse(sizes[0]);
double height = double.Parse(sizes[1]); 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(); var geo = new StreamGeometry();
using (var ctx = geo.Open()) using (var ctx = geo.Open())
{ {
ctx.BeginFigure(new Point(0, 0), false, false); ctx.BeginFigure(new Point(0, 0), false, false);
double total = 2 * (width + height); // top edge
double len = progress * total;
// 上边
if (len <= width) if (len <= width)
{ {
ctx.LineTo(new Point(len, 0), true, true); ctx.LineTo(new Point(len, 0), true, false);
return geo; return geo;
} }
ctx.LineTo(new Point(width, 0), true, true); ctx.LineTo(new Point(width, 0), true, false);
len -= width; len -= width;
// 右边 // right edge
if (len <= height) if (len <= height)
{ {
ctx.LineTo(new Point(width, len), true, true); ctx.LineTo(new Point(width, len), true, false);
return geo; return geo;
} }
ctx.LineTo(new Point(width, height), true, true); ctx.LineTo(new Point(width, height), true, false);
len -= height; len -= height;
// 下边 // bottom edge
if (len <= width) if (len <= width)
{ {
ctx.LineTo(new Point(width - len, height), true, true); ctx.LineTo(new Point(width - len, height), true, false);
return geo; return geo;
} }
ctx.LineTo(new Point(0, height), true, true); ctx.LineTo(new Point(0, height), true, false);
len -= width; len -= width;
// 左边 // left edge
if (len <= height) if (len <= height)
{ {
ctx.LineTo(new Point(0, height - len), true, true); ctx.LineTo(new Point(0, height - len), true, false);
} }
else else
{ {
ctx.LineTo(new Point(0, 0), true, true); ctx.LineTo(new Point(0, 0), true, false);
} }
} }
geo.Freeze(); geo.Freeze();