mirror of
https://github.com/LanZhan-Harmony/WindowsMusicPlayer-TheUntamedMusicPlayer.git
synced 2026-05-06 19:20:18 +08:00
修复动画错误逻辑
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
using System.ComponentModel;
|
||||
using System.Numerics;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Hosting;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using UntamedMusicPlayer.Controls;
|
||||
using UntamedMusicPlayer.Models;
|
||||
using UntamedMusicPlayer.Playback;
|
||||
@@ -15,6 +18,8 @@ public sealed partial class LyricPage : Page, IDisposable
|
||||
{
|
||||
public LyricViewModel ViewModel { get; }
|
||||
|
||||
private bool isFirstLoad = true;
|
||||
|
||||
private readonly Timer _autoScrollDelayTimer;
|
||||
private bool _isManualScrolling;
|
||||
private bool _isProgrammaticScroll;
|
||||
@@ -27,13 +32,7 @@ public sealed partial class LyricPage : Page, IDisposable
|
||||
private DateTimeOffset _contentGridMarginAnimationStart;
|
||||
private double _contentGridMarginFrom;
|
||||
private double _contentGridMarginTo;
|
||||
|
||||
private DispatcherQueueTimer? _coverSizeAnimationTimer;
|
||||
private DateTimeOffset _coverSizeAnimationStart;
|
||||
private double _coverSizeFromWidth;
|
||||
private double _coverSizeFromHeight;
|
||||
private double _coverSizeToWidth;
|
||||
private double _coverSizeToHeight;
|
||||
private CancellationTokenSource? _coverLoadWaitCts;
|
||||
|
||||
public LyricPage()
|
||||
{
|
||||
@@ -57,13 +56,89 @@ public sealed partial class LyricPage : Page, IDisposable
|
||||
{
|
||||
if (ReferenceGrid.ActualWidth > 0 && ReferenceGrid.ActualHeight > 0)
|
||||
{
|
||||
ChangeCoverSize(ReferenceGrid.ActualWidth, ReferenceGrid.ActualHeight);
|
||||
RestartWaitForCoverAndRecalculate();
|
||||
}
|
||||
_isManualScrolling = false;
|
||||
_autoScrollDelayTimer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
|
||||
private void RestartWaitForCoverAndRecalculate()
|
||||
{
|
||||
_coverLoadWaitCts?.Cancel();
|
||||
_coverLoadWaitCts?.Dispose();
|
||||
_coverLoadWaitCts = new CancellationTokenSource();
|
||||
_ = RecalculateCoverSizeWhenCoverReadyAsync(_coverLoadWaitCts.Token);
|
||||
}
|
||||
|
||||
private async Task RecalculateCoverSizeWhenCoverReadyAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var cover = Data.PlayState.CurrentSong?.Cover;
|
||||
if (cover is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var loaded = await WaitCoverLoadedAsync(cover, cancellationToken);
|
||||
if (!loaded || cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ReferenceGrid.ActualWidth > 0 && ReferenceGrid.ActualHeight > 0)
|
||||
{
|
||||
ChangeCoverSize(ReferenceGrid.ActualWidth, ReferenceGrid.ActualHeight);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<bool> WaitCoverLoadedAsync(
|
||||
BitmapImage cover,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
if (cover.PixelWidth > 0 && cover.PixelHeight > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<bool>(
|
||||
TaskCreationOptions.RunContinuationsAsynchronously
|
||||
);
|
||||
|
||||
void OnImageOpened(object sender, RoutedEventArgs args) => tcs.TrySetResult(true);
|
||||
void OnImageFailed(object sender, ExceptionRoutedEventArgs args) => tcs.TrySetResult(false);
|
||||
|
||||
cover.ImageOpened += OnImageOpened;
|
||||
cover.ImageFailed += OnImageFailed;
|
||||
|
||||
using var cancellationRegistration = cancellationToken.Register(() =>
|
||||
tcs.TrySetCanceled(cancellationToken)
|
||||
);
|
||||
|
||||
try
|
||||
{
|
||||
if (cover.PixelWidth > 0 && cover.PixelHeight > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var completedTask = await Task.WhenAny(tcs.Task, Task.Delay(1500, cancellationToken));
|
||||
if (completedTask != tcs.Task)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return await tcs.Task;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
cover.ImageOpened -= OnImageOpened;
|
||||
cover.ImageFailed -= OnImageFailed;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRootPlayBarChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (
|
||||
@@ -308,6 +383,14 @@ public sealed partial class LyricPage : Page, IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
if (isFirstLoad)
|
||||
{
|
||||
isFirstLoad = false;
|
||||
CoverBorder.Width = targetWidth;
|
||||
CoverBorder.Height = targetHeight;
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
Math.Abs(currentWidth - targetWidth) < 0.5
|
||||
&& Math.Abs(currentHeight - targetHeight) < 0.5
|
||||
@@ -318,40 +401,23 @@ public sealed partial class LyricPage : Page, IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
_coverSizeAnimationTimer ??= DispatcherQueue.CreateTimer();
|
||||
_coverSizeAnimationTimer.Stop();
|
||||
_coverSizeAnimationTimer.Interval = TimeSpan.FromMilliseconds(16);
|
||||
_coverSizeAnimationTimer.Tick -= CoverSizeAnimationTick;
|
||||
_coverSizeAnimationTimer.Tick += CoverSizeAnimationTick;
|
||||
CoverBorder.Width = targetWidth;
|
||||
CoverBorder.Height = targetHeight;
|
||||
|
||||
_coverSizeFromWidth = currentWidth;
|
||||
_coverSizeFromHeight = currentHeight;
|
||||
_coverSizeToWidth = targetWidth;
|
||||
_coverSizeToHeight = targetHeight;
|
||||
_coverSizeAnimationStart = DateTimeOffset.Now;
|
||||
var visual = ElementCompositionPreview.GetElementVisual(CoverBorder);
|
||||
visual.StopAnimation("Scale");
|
||||
visual.CenterPoint = new Vector3((float)(targetWidth / 2), (float)(targetHeight / 2), 0f);
|
||||
|
||||
_coverSizeAnimationTimer.Start();
|
||||
}
|
||||
var initialScaleX = (float)(currentWidth / targetWidth);
|
||||
var initialScaleY = (float)(currentHeight / targetHeight);
|
||||
visual.Scale = new Vector3(initialScaleX, initialScaleY, 1f);
|
||||
|
||||
private void CoverSizeAnimationTick(DispatcherQueueTimer sender, object args)
|
||||
{
|
||||
const double durationMs = 450;
|
||||
var elapsedMs = (DateTimeOffset.Now - _coverSizeAnimationStart).TotalMilliseconds;
|
||||
var progress = Math.Clamp(elapsedMs / durationMs, 0d, 1d);
|
||||
var easedProgress = 1 - Math.Pow(1 - progress, 3);
|
||||
var compositor = visual.Compositor;
|
||||
var scaleAnimation = compositor.CreateVector3KeyFrameAnimation();
|
||||
scaleAnimation.InsertKeyFrame(1f, Vector3.One);
|
||||
scaleAnimation.Duration = TimeSpan.FromMilliseconds(450);
|
||||
|
||||
CoverBorder.Width =
|
||||
_coverSizeFromWidth + ((_coverSizeToWidth - _coverSizeFromWidth) * easedProgress);
|
||||
CoverBorder.Height =
|
||||
_coverSizeFromHeight + ((_coverSizeToHeight - _coverSizeFromHeight) * easedProgress);
|
||||
|
||||
if (progress >= 1)
|
||||
{
|
||||
sender.Stop();
|
||||
sender.Tick -= CoverSizeAnimationTick;
|
||||
CoverBorder.Width = _coverSizeToWidth;
|
||||
CoverBorder.Height = _coverSizeToHeight;
|
||||
}
|
||||
visual.StartAnimation("Scale", scaleAnimation);
|
||||
}
|
||||
|
||||
private void TextBlock_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
@@ -431,12 +497,12 @@ public sealed partial class LyricPage : Page, IDisposable
|
||||
{
|
||||
_autoScrollDelayTimer.Dispose();
|
||||
_titleBarHideTimer?.Dispose();
|
||||
_coverLoadWaitCts?.Cancel();
|
||||
_coverLoadWaitCts?.Dispose();
|
||||
_coverLoadWaitCts = null;
|
||||
_contentGridMarginAnimationTimer?.Stop();
|
||||
_contentGridMarginAnimationTimer?.Tick -= ContentGridMarginAnimationTick;
|
||||
_contentGridMarginAnimationTimer = null;
|
||||
_coverSizeAnimationTimer?.Stop();
|
||||
_coverSizeAnimationTimer?.Tick -= CoverSizeAnimationTick;
|
||||
_coverSizeAnimationTimer = null;
|
||||
Data.PlayState.PropertyChanged -= OnStateChanged;
|
||||
Data.RootPlayBarViewModel?.PropertyChanged -= OnRootPlayBarChanged;
|
||||
Data.LyricPage = null;
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:model="using:UntamedMusicPlayer.Models"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
Unloaded="RootPlayBarView_Unloaded"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid KeyTipPlacementMode="Top">
|
||||
@@ -215,41 +214,22 @@
|
||||
</Border>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Width="6" Visibility="{x:Bind GetNotDetailedVisibility(ViewModel.IsDetail), Mode=OneWay}"/>
|
||||
<Grid x:Name="SongInfoTransitionHost"
|
||||
Margin="0,0,4,0" HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center">
|
||||
<StackPanel x:Name="CurrentSongInfoPanel">
|
||||
<StackPanel.RenderTransform>
|
||||
<CompositeTransform x:Name="CurrentSongInfoTransform"/>
|
||||
</StackPanel.RenderTransform>
|
||||
<TextBlock x:Name="SongTitleTextBlock"
|
||||
Margin="10,0,0,4"
|
||||
FontSize="20" FontWeight="Medium"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap"/>
|
||||
<TextBlock x:Name="ArtistAndAlbumTextBlock"
|
||||
Margin="10,0,0,0"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap"/>
|
||||
</StackPanel>
|
||||
<StackPanel x:Name="IncomingSongInfoPanel"
|
||||
Opacity="0" Visibility="Collapsed">
|
||||
<StackPanel.RenderTransform>
|
||||
<CompositeTransform x:Name="IncomingSongInfoTransform" TranslateY="16"/>
|
||||
</StackPanel.RenderTransform>
|
||||
<TextBlock x:Name="IncomingSongTitleTextBlock"
|
||||
Margin="10,0,0,4"
|
||||
FontSize="20" FontWeight="Medium"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap"/>
|
||||
<TextBlock x:Name="IncomingArtistAndAlbumTextBlock"
|
||||
Margin="10,0,0,0"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<StackPanel Margin="0,0,4,0" HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock x:Name="SongTitleTextBlock"
|
||||
Margin="10,0,0,4"
|
||||
FontSize="20" FontWeight="Medium"
|
||||
Text="{x:Bind model:Data.PlayState.CurrentSong.Title, Mode=OneWay}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap"/>
|
||||
<TextBlock x:Name="ArtistAndAlbumTextBlock"
|
||||
Margin="10,0,0,0"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind model:Data.PlayState.CurrentSong.ArtistAndAlbumStr, Mode=OneWay}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap"
|
||||
Visibility="{x:Bind GetArtistAndAlbumStrVisibility(model:Data.PlayState.CurrentSong), Mode=OneWay}"/>
|
||||
</StackPanel>
|
||||
<TextBlock Width="8" Visibility="{x:Bind ViewModel.IsDetail, Mode=OneWay}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
using System.ComponentModel;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using UntamedMusicPlayer.Contracts.Models;
|
||||
using UntamedMusicPlayer.Controls;
|
||||
using UntamedMusicPlayer.Helpers;
|
||||
@@ -18,7 +15,6 @@ namespace UntamedMusicPlayer.Views;
|
||||
public sealed partial class RootPlayBarView : UserControl
|
||||
{
|
||||
private bool _hasPointerPressed = false;
|
||||
private Storyboard? _songInfoTransitionStoryboard;
|
||||
|
||||
public RootPlayBarViewModel ViewModel { get; }
|
||||
|
||||
@@ -28,119 +24,6 @@ public sealed partial class RootPlayBarView : UserControl
|
||||
InitializeComponent();
|
||||
ViewModel = App.GetService<RootPlayBarViewModel>();
|
||||
Data.RootPlayBarView = this;
|
||||
|
||||
Data.PlayState.PropertyChanged += OnStateChanged;
|
||||
UpdateSongInfoWithoutAnimation();
|
||||
}
|
||||
|
||||
private void OnStateChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName is nameof(SharedPlaybackState.CurrentSong))
|
||||
{
|
||||
AnimateSongInfoToCurrentSong();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSongInfoWithoutAnimation()
|
||||
{
|
||||
var title = Data.PlayState.CurrentSong?.Title ?? "";
|
||||
var artistAndAlbum = Data.PlayState.CurrentSong?.ArtistAndAlbumStr ?? "";
|
||||
|
||||
SongTitleTextBlock.Text = title;
|
||||
ArtistAndAlbumTextBlock.Text = artistAndAlbum;
|
||||
ArtistAndAlbumTextBlock.Visibility = string.IsNullOrWhiteSpace(artistAndAlbum)
|
||||
? Visibility.Collapsed
|
||||
: Visibility.Visible;
|
||||
|
||||
CurrentSongInfoPanel.Opacity = 1;
|
||||
IncomingSongInfoPanel.Visibility = Visibility.Collapsed;
|
||||
IncomingSongInfoPanel.Opacity = 0;
|
||||
}
|
||||
|
||||
private void AnimateSongInfoToCurrentSong()
|
||||
{
|
||||
var title = Data.PlayState.CurrentSong?.Title ?? "";
|
||||
var artistAndAlbum = Data.PlayState.CurrentSong?.ArtistAndAlbumStr ?? "";
|
||||
if (SongTitleTextBlock.Text == title && ArtistAndAlbumTextBlock.Text == artistAndAlbum)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StopSongInfoTransition();
|
||||
|
||||
IncomingSongTitleTextBlock.Text = title;
|
||||
IncomingArtistAndAlbumTextBlock.Text = artistAndAlbum;
|
||||
IncomingArtistAndAlbumTextBlock.Visibility = string.IsNullOrWhiteSpace(artistAndAlbum)
|
||||
? Visibility.Collapsed
|
||||
: Visibility.Visible;
|
||||
|
||||
IncomingSongInfoPanel.Visibility = Visibility.Visible;
|
||||
|
||||
var currentFadeOut = new DoubleAnimation
|
||||
{
|
||||
From = 1,
|
||||
To = 0,
|
||||
Duration = TimeSpan.FromMilliseconds(200),
|
||||
};
|
||||
Storyboard.SetTarget(currentFadeOut, CurrentSongInfoPanel);
|
||||
Storyboard.SetTargetProperty(currentFadeOut, nameof(Opacity));
|
||||
|
||||
var currentSlideUp = new DoubleAnimation
|
||||
{
|
||||
From = 0,
|
||||
To = -70,
|
||||
Duration = TimeSpan.FromMilliseconds(220),
|
||||
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn },
|
||||
};
|
||||
Storyboard.SetTarget(currentSlideUp, CurrentSongInfoTransform);
|
||||
Storyboard.SetTargetProperty(currentSlideUp, nameof(CompositeTransform.TranslateY));
|
||||
|
||||
var incomingFadeIn = new DoubleAnimation
|
||||
{
|
||||
From = 0,
|
||||
To = 1,
|
||||
Duration = TimeSpan.FromMilliseconds(400),
|
||||
};
|
||||
Storyboard.SetTarget(incomingFadeIn, IncomingSongInfoPanel);
|
||||
Storyboard.SetTargetProperty(incomingFadeIn, nameof(Opacity));
|
||||
|
||||
var incomingSlideIn = new DoubleAnimation
|
||||
{
|
||||
From = 70,
|
||||
To = 0,
|
||||
Duration = TimeSpan.FromMilliseconds(450),
|
||||
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut },
|
||||
};
|
||||
Storyboard.SetTarget(incomingSlideIn, IncomingSongInfoTransform);
|
||||
Storyboard.SetTargetProperty(incomingSlideIn, nameof(CompositeTransform.TranslateY));
|
||||
|
||||
_songInfoTransitionStoryboard = new Storyboard();
|
||||
_songInfoTransitionStoryboard.Children.Add(currentFadeOut);
|
||||
_songInfoTransitionStoryboard.Children.Add(currentSlideUp);
|
||||
_songInfoTransitionStoryboard.Children.Add(incomingFadeIn);
|
||||
_songInfoTransitionStoryboard.Children.Add(incomingSlideIn);
|
||||
_songInfoTransitionStoryboard.Completed += SongInfoTransitionStoryboard_Completed;
|
||||
_songInfoTransitionStoryboard.Begin();
|
||||
}
|
||||
|
||||
private void SongInfoTransitionStoryboard_Completed(object? sender, object e)
|
||||
{
|
||||
ArtistAndAlbumTextBlock.Visibility = IncomingArtistAndAlbumTextBlock.Visibility;
|
||||
IncomingSongInfoPanel.Visibility = Visibility.Collapsed;
|
||||
StopSongInfoTransition();
|
||||
}
|
||||
|
||||
private void StopSongInfoTransition()
|
||||
{
|
||||
SongTitleTextBlock.Text = IncomingSongTitleTextBlock.Text;
|
||||
ArtistAndAlbumTextBlock.Text = IncomingArtistAndAlbumTextBlock.Text;
|
||||
if (_songInfoTransitionStoryboard is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_songInfoTransitionStoryboard.Completed -= SongInfoTransitionStoryboard_Completed;
|
||||
_songInfoTransitionStoryboard.Stop();
|
||||
_songInfoTransitionStoryboard = null;
|
||||
}
|
||||
|
||||
public string GetCurrent(TimeSpan current) =>
|
||||
@@ -390,10 +273,4 @@ public sealed partial class RootPlayBarView : UserControl
|
||||
var dialog = new PropertiesDialog(currentSong!) { XamlRoot = XamlRoot };
|
||||
await dialog.ShowAsync();
|
||||
}
|
||||
|
||||
private void RootPlayBarView_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Data.PlayState.PropertyChanged -= OnStateChanged;
|
||||
StopSongInfoTransition();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user