WinUI 3 MVVM项目图片切换淡入淡出动画实现求助
WinUI 3 MVVM 图片切换淡入淡出实现方案
核心思路
用两个叠加的媒体控件(图片/视频容器)分别承载当前显示内容和下一个待切换内容,通过控制两者的Opacity属性执行同步动画,实现旧内容淡出、新内容淡入的平滑过渡。结合MVVM模式,通过ViewModel的状态变量驱动动画,避免直接操作UI控件。
具体实现步骤
1. XAML布局调整
替换原单个Image结构,用Grid作为容器叠加两个内容控件,默认仅显示当前内容,下一个内容初始透明度为0:
<Grid> <!-- 当前显示的媒体(图片/视频) --> <ContentControl x:Name="CurrentMedia" Content="{x:Bind ViewModel.CurrentMediaContent, Mode=OneWay}" Opacity="{x:Bind ViewModel.CurrentMediaOpacity, Mode=OneWay}"/> <!-- 待切换的下一个媒体(图片/视频) --> <ContentControl x:Name="NextMedia" Content="{x:Bind ViewModel.NextMediaContent, Mode=OneWay}" Opacity="{x:Bind ViewModel.NextMediaOpacity, Mode=OneWay}"/> </Grid>
2. ViewModel 核心逻辑实现
在ViewModel中定义媒体源、透明度属性,以及自动播放、动画切换的方法:
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media.Animation; using CommunityToolkit.Mvvm.ComponentModel; using System.Collections.Generic; using System.Threading.Tasks; public partial class WallpaperViewModel : ObservableObject { private List<MediaItem> _mediaList; private int _currentIndex = 0; // 当前媒体内容控件 [ObservableProperty] private FrameworkElement _currentMediaContent; // 下一个媒体内容控件 [ObservableProperty] private FrameworkElement _nextMediaContent; // 当前媒体透明度 [ObservableProperty] private double _currentMediaOpacity = 1.0; // 下一个媒体透明度 [ObservableProperty] private double _nextMediaOpacity = 0.0; public WallpaperViewModel() { // 从JSON加载媒体列表(实际项目替换为你的JSON解析逻辑) _mediaList = LoadMediaList(); // 初始化当前媒体 CurrentMediaContent = CreateMediaControl(_mediaList[_currentIndex]); // 启动自动播放任务 _ = StartAutoPlayLoop(); } // 自动播放循环 private async Task StartAutoPlayLoop() { while (true) { // 等待当前媒体播放时长 await Task.Delay(_mediaList[_currentIndex].Duration * 1000); // 计算下一个媒体索引 int nextIndex = (_currentIndex + 1) % _mediaList.Count; // 准备下一个媒体控件 NextMediaContent = CreateMediaControl(_mediaList[nextIndex]); // 执行淡入淡出动画 await RunFadeTransition(); // 更新当前媒体状态 _currentIndex = nextIndex; CurrentMediaContent = NextMediaContent; // 重置透明度,为下一次切换做准备 CurrentMediaOpacity = 1.0; NextMediaOpacity = 0.0; } } // 执行淡入淡出过渡动画 private async Task RunFadeTransition() { // 旧媒体淡出动画(1→0) var fadeOutAnim = new DoubleAnimation { From = 1.0, To = 0.0, Duration = new Duration(TimeSpan.FromSeconds(1)) }; // 新媒体淡入动画(0→1) var fadeInAnim = new DoubleAnimation { From = 0.0, To = 1.0, Duration = new Duration(TimeSpan.FromSeconds(1)) }; // 绑定动画到ViewModel属性 var storyboard = new Storyboard(); Storyboard.SetTarget(fadeOutAnim, this); Storyboard.SetTargetProperty(fadeOutAnim, nameof(CurrentMediaOpacity)); Storyboard.SetTarget(fadeInAnim, this); Storyboard.SetTargetProperty(fadeInAnim, nameof(NextMediaOpacity)); storyboard.Children.Add(fadeOutAnim); storyboard.Children.Add(fadeInAnim); // 等待动画完成 var tcs = new TaskCompletionSource<bool>(); storyboard.Completed += (s, e) => tcs.SetResult(true); storyboard.Begin(); await tcs.Task; } // 根据媒体类型创建对应的控件(图片/视频) private FrameworkElement CreateMediaControl(MediaItem item) { if (item.Source.AbsolutePath.EndsWith(".mp4", StringComparison.OrdinalIgnoreCase)) { // 视频控件 var mediaPlayer = new MediaPlayerElement { Source = MediaSource.CreateFromUri(item.Source), Stretch = Stretch.UniformToFill, AutoPlay = true }; return mediaPlayer; } else { // 图片控件 var image = new Image { Source = new BitmapImage(item.Source), Stretch = Stretch.UniformToFill }; return image; } } // 模拟从JSON加载媒体列表 private List<MediaItem> LoadMediaList() { return new List<MediaItem> { new MediaItem { Source = new Uri("ms-appx:///Assets/Img1.jpg"), Duration = 4 }, new MediaItem { Source = new Uri("ms-appx:///Assets/Img2.jpg"), Duration = 4 }, new MediaItem { Source = new Uri("ms-appx:///Assets/Video1.mp4"), Duration = 10 } }; } } // 媒体项模型 public class MediaItem { public Uri Source { get; set; } public int Duration { get; set; } // 播放时长(秒) }
3. 原代码问题分析
- 单个Image控件切换Source时,无法实现新旧媒体的过渡衔接,
Loaded事件仅在首次加载触发,后续切换不会重新执行动画。 AutoReverse=True会让动画回到初始状态,不符合“切换到下一张图”的需求,应该是旧图淡出后保持隐藏,新图淡入后保持显示。- 动画目标为容器的
Opacity,会导致整个容器透明度变化,而非媒体内容的平滑切换。
内容的提问来源于stack exchange,提问作者Hana
相关产品推荐
相关产品推荐

