2025-08-23 21:39:00 +08:00
using System ;
using System.Collections.Generic ;
2025-08-23 23:13:39 +08:00
using System.Linq ;
2025-08-23 21:39:00 +08:00
using System.Windows ;
using System.Windows.Media.Animation ;
using System.Windows.Threading ;
using System.Windows.Ink ;
2025-08-23 23:13:39 +08:00
using System.Windows.Controls ;
using System.Windows.Input ;
using System.Windows.Media ;
using System.Windows.Shapes ;
2025-08-23 21:39:00 +08:00
namespace Ink_Canvas.Helpers
{
/// <summary>
/// 墨迹渐隐管理器 - 管理墨迹的渐隐动画和状态
/// </summary>
public class InkFadeManager
{
#region Properties
/// <summary>
/// 是否启用墨迹渐隐功能
/// </summary>
public bool IsEnabled { get ; set ; } = false ;
/// <summary>
/// 墨迹渐隐时间(毫秒)
/// </summary>
public int FadeTime { get ; set ; } = 3000 ;
/// <summary>
/// 渐隐动画持续时间(毫秒)
/// </summary>
public int AnimationDuration { get ; set ; } = 1000 ;
#endregion
#region Private Fields
private readonly MainWindow _mainWindow ;
private readonly Dispatcher _dispatcher ;
private readonly Dictionary < Stroke , DispatcherTimer > _fadeTimers ;
private readonly Dictionary < Stroke , UIElement > _strokeVisuals ;
2025-08-23 23:13:39 +08:00
private readonly Dictionary < Stroke , Point > _strokeStartPoints ;
private readonly Dictionary < Stroke , Point > _strokeEndPoints ;
2025-08-23 21:39:00 +08:00
#endregion
#region Constructor
public InkFadeManager ( MainWindow mainWindow )
{
_mainWindow = mainWindow ? ? throw new ArgumentNullException ( nameof ( mainWindow ) ) ;
_dispatcher = _mainWindow . Dispatcher ;
_fadeTimers = new Dictionary < Stroke , DispatcherTimer > ( ) ;
_strokeVisuals = new Dictionary < Stroke , UIElement > ( ) ;
2025-08-23 23:13:39 +08:00
_strokeStartPoints = new Dictionary < Stroke , Point > ( ) ;
_strokeEndPoints = new Dictionary < Stroke , Point > ( ) ;
2025-08-23 21:39:00 +08:00
}
#endregion
#region Public Methods
/// <summary>
/// 添加需要渐隐的墨迹
/// </summary>
/// <param name="stroke">墨迹对象</param>
2025-08-23 23:13:39 +08:00
/// <param name="startPoint">落笔点</param>
/// <param name="endPoint">抬笔点</param>
public void AddFadingStroke ( Stroke stroke , Point startPoint , Point endPoint )
2025-08-23 21:39:00 +08:00
{
2025-08-23 23:13:39 +08:00
LogHelper . WriteLogToFile ( $"AddFadingStroke 被调用,IsEnabled: {IsEnabled}, stroke: {(stroke != null ? " 非 空 " : " 空 ")}" , LogHelper . LogType . Info ) ;
if ( ! IsEnabled | | stroke = = null )
{
LogHelper . WriteLogToFile ( $"AddFadingStroke 被拒绝,IsEnabled: {IsEnabled}, stroke: {(stroke != null ? " 非 空 " : " 空 ")}" , LogHelper . LogType . Warning ) ;
return ;
}
2025-08-23 21:39:00 +08:00
try
{
2025-08-23 23:13:39 +08:00
LogHelper . WriteLogToFile ( $"开始添加渐隐墨迹,渐隐时间:{FadeTime}ms,动画时间:{AnimationDuration}ms" , LogHelper . LogType . Info ) ;
// 记录墨迹的起点和终点
_strokeStartPoints [ stroke ] = startPoint ;
_strokeEndPoints [ stroke ] = endPoint ;
// 创建墨迹的视觉元素(湿墨迹状态)
var strokeVisual = CreateStrokeVisual ( stroke ) ;
if ( strokeVisual = = null ) return ;
_strokeVisuals [ stroke ] = strokeVisual ;
2025-08-23 21:39:00 +08:00
// 创建定时器,在指定时间后开始渐隐动画
var timer = new DispatcherTimer
{
Interval = TimeSpan . FromMilliseconds ( FadeTime )
} ;
timer . Tick + = ( sender , e ) = >
{
2025-08-23 23:13:39 +08:00
LogHelper . WriteLogToFile ( "定时器触发,开始渐隐动画" , LogHelper . LogType . Info ) ;
2025-08-23 21:39:00 +08:00
StartFadeAnimation ( stroke ) ;
timer . Stop ( ) ;
_fadeTimers . Remove ( stroke ) ;
} ;
_fadeTimers [ stroke ] = timer ;
timer . Start ( ) ;
2025-08-23 23:13:39 +08:00
LogHelper . WriteLogToFile ( $"定时器已启动,将在 {FadeTime}ms 后开始渐隐" , LogHelper . LogType . Info ) ;
2025-08-23 21:39:00 +08:00
// 将视觉元素添加到画布上
_dispatcher . InvokeAsync ( ( ) = >
{
try
{
if ( _mainWindow . inkCanvas ! = null )
{
2025-08-23 23:13:39 +08:00
// 将墨迹添加到 inkCanvas 的父容器中,而不是 inkCanvas.Children
// 这样可以避免坐标系统问题
var parent = _mainWindow . inkCanvas . Parent as System . Windows . Controls . Panel ;
if ( parent ! = null )
{
parent . Children . Add ( strokeVisual ) ;
LogHelper . WriteLogToFile ( "墨迹已添加到父容器" , LogHelper . LogType . Info ) ;
}
else
{
// 如果无法获取父容器,则添加到 inkCanvas.Children
_mainWindow . inkCanvas . Children . Add ( strokeVisual ) ;
LogHelper . WriteLogToFile ( "墨迹已添加到 inkCanvas.Children" , LogHelper . LogType . Info ) ;
}
2025-08-23 21:39:00 +08:00
}
}
catch ( Exception ex )
{
LogHelper . WriteLogToFile ( $"添加墨迹视觉元素到画布失败: {ex}" , LogHelper . LogType . Error ) ;
}
} ) ;
}
catch ( Exception ex )
{
LogHelper . WriteLogToFile ( $"添加渐隐墨迹失败: {ex}" , LogHelper . LogType . Error ) ;
}
}
/// <summary>
/// 移除墨迹
/// </summary>
/// <param name="stroke">要移除的墨迹</param>
public void RemoveStroke ( Stroke stroke )
{
if ( stroke = = null ) return ;
try
{
if ( _fadeTimers . TryGetValue ( stroke , out var timer ) )
{
timer . Stop ( ) ;
_fadeTimers . Remove ( stroke ) ;
}
if ( _strokeVisuals . TryGetValue ( stroke , out var visual ) )
{
_dispatcher . InvokeAsync ( ( ) = >
{
try
{
2025-08-23 23:13:39 +08:00
// 从父容器中移除墨迹
var parent = _mainWindow . inkCanvas ? . Parent as System . Windows . Controls . Panel ;
if ( parent ! = null & & parent . Children . Contains ( visual ) )
{
parent . Children . Remove ( visual ) ;
}
else if ( _mainWindow . inkCanvas ! = null & & _mainWindow . inkCanvas . Children . Contains ( visual ) )
2025-08-23 21:39:00 +08:00
{
_mainWindow . inkCanvas . Children . Remove ( visual ) ;
}
}
catch ( Exception ex )
{
LogHelper . WriteLogToFile ( $"从画布移除墨迹视觉元素失败: {ex}" , LogHelper . LogType . Error ) ;
}
} ) ;
_strokeVisuals . Remove ( stroke ) ;
}
2025-08-23 23:13:39 +08:00
_strokeStartPoints . Remove ( stroke ) ;
_strokeEndPoints . Remove ( stroke ) ;
2025-08-23 21:39:00 +08:00
}
catch ( Exception ex )
{
LogHelper . WriteLogToFile ( $"移除渐隐墨迹失败: {ex}" , LogHelper . LogType . Error ) ;
}
}
/// <summary>
/// 清除所有渐隐墨迹
/// </summary>
public void ClearAllFadingStrokes ( )
{
try
{
foreach ( var timer in _fadeTimers . Values )
{
timer . Stop ( ) ;
}
_fadeTimers . Clear ( ) ;
_dispatcher . InvokeAsync ( ( ) = >
{
try
{
if ( _mainWindow . inkCanvas ! = null )
{
2025-08-23 23:13:39 +08:00
var parent = _mainWindow . inkCanvas . Parent as System . Windows . Controls . Panel ;
2025-08-23 21:39:00 +08:00
foreach ( var visual in _strokeVisuals . Values )
{
2025-08-23 23:13:39 +08:00
if ( parent ! = null & & parent . Children . Contains ( visual ) )
{
parent . Children . Remove ( visual ) ;
}
else if ( _mainWindow . inkCanvas . Children . Contains ( visual ) )
2025-08-23 21:39:00 +08:00
{
_mainWindow . inkCanvas . Children . Remove ( visual ) ;
}
}
}
}
catch ( Exception ex )
{
LogHelper . WriteLogToFile ( $"清除所有墨迹视觉元素失败: {ex}" , LogHelper . LogType . Error ) ;
}
} ) ;
_strokeVisuals . Clear ( ) ;
2025-08-23 23:13:39 +08:00
_strokeStartPoints . Clear ( ) ;
_strokeEndPoints . Clear ( ) ;
2025-08-23 21:39:00 +08:00
}
catch ( Exception ex )
{
LogHelper . WriteLogToFile ( $"清除所有渐隐墨迹失败: {ex}" , LogHelper . LogType . Error ) ;
}
}
/// <summary>
/// 更新渐隐时间设置
/// </summary>
/// <param name="fadeTime">新的渐隐时间(毫秒)</param>
public void UpdateFadeTime ( int fadeTime )
{
FadeTime = fadeTime ;
foreach ( var kvp in _fadeTimers )
{
var stroke = kvp . Key ;
var timer = kvp . Value ;
timer . Stop ( ) ;
timer . Interval = TimeSpan . FromMilliseconds ( FadeTime ) ;
timer . Start ( ) ;
}
}
2025-08-23 23:13:39 +08:00
2025-08-23 21:39:00 +08:00
/// <summary>
2025-08-23 23:13:39 +08:00
/// 启用墨迹渐隐功能
2025-08-23 21:39:00 +08:00
/// </summary>
2025-08-23 23:13:39 +08:00
public void Enable ( )
2025-08-23 21:39:00 +08:00
{
2025-08-23 23:13:39 +08:00
IsEnabled = true ;
LogHelper . WriteLogToFile ( "墨迹渐隐功能已启用" , LogHelper . LogType . Info ) ;
}
/// <summary>
/// 禁用墨迹渐隐功能
/// </summary>
public void Disable ( )
{
IsEnabled = false ;
LogHelper . WriteLogToFile ( "墨迹渐隐功能已禁用" , LogHelper . LogType . Info ) ;
2025-08-23 21:39:00 +08:00
}
#endregion
#region Private Methods
2025-08-23 23:13:39 +08:00
/// <summary>
/// 创建墨迹的视觉元素
/// </summary>
/// <param name="stroke">墨迹对象</param>
/// <returns>视觉元素</returns>
private UIElement CreateStrokeVisual ( Stroke stroke )
{
try
{
LogHelper . WriteLogToFile ( "开始创建墨迹视觉元素" , LogHelper . LogType . Info ) ;
// 创建路径几何,使用墨迹的实际位置
var geometry = stroke . GetGeometry ( ) ;
if ( geometry = = null )
{
LogHelper . WriteLogToFile ( "墨迹几何为空,无法创建视觉元素" , LogHelper . LogType . Error ) ;
return null ;
}
LogHelper . WriteLogToFile ( $"墨迹几何边界:{geometry.Bounds}" , LogHelper . LogType . Info ) ;
// 获取绘画属性
var drawingAttribs = stroke . DrawingAttributes ;
// 创建路径元素,确保使用正确的绘画属性
var path = new Path
{
Data = geometry ,
Stroke = new SolidColorBrush ( drawingAttribs . Color ) ,
StrokeThickness = drawingAttribs . Width , // 使用原始墨迹的粗细
StrokeStartLineCap = PenLineCap . Round ,
StrokeEndLineCap = PenLineCap . Round ,
StrokeLineJoin = PenLineJoin . Round ,
Fill = drawingAttribs . IsHighlighter ? new SolidColorBrush ( drawingAttribs . Color ) : null , // 高亮笔需要填充
Opacity = 0.95 , // 初始透明度更高,显得更自然
// 优化渲染质量
UseLayoutRounding = false ,
SnapsToDevicePixels = false
} ;
// 如果是高亮笔,调整透明度和混合模式
if ( drawingAttribs . IsHighlighter )
{
path . Opacity = 0.4 ; // 高亮笔初始透明度更低,更符合荧光笔特性
// 为高亮笔添加特殊的混合效果
// 使用更柔和的笔触样式
path . StrokeStartLineCap = PenLineCap . Flat ;
path . StrokeEndLineCap = PenLineCap . Flat ;
path . StrokeLineJoin = PenLineJoin . Miter ;
// 高亮笔通常需要更宽的笔触来覆盖下面的内容
if ( drawingAttribs . Width < 20 )
{
path . StrokeThickness = Math . Max ( drawingAttribs . Width * 1.5 , 20 ) ;
}
LogHelper . WriteLogToFile ( $"高亮笔特殊处理:透明度={path.Opacity},笔触宽度={path.StrokeThickness}" , LogHelper . LogType . Info ) ;
}
// 不设置任何变换,保持墨迹原有粗细
var bounds = geometry . Bounds ;
// 设置墨迹的初始位置
System . Windows . Controls . Canvas . SetLeft ( path , bounds . Left ) ;
System . Windows . Controls . Canvas . SetTop ( path , bounds . Top ) ;
LogHelper . WriteLogToFile ( $"墨迹视觉元素创建完成,位置:({bounds.Left}, {bounds.Top}),大小:{bounds.Width} x {bounds.Height},粗细:{drawingAttribs.Width},高亮笔:{drawingAttribs.IsHighlighter}" , LogHelper . LogType . Info ) ;
return path ;
}
catch ( Exception ex )
{
LogHelper . WriteLogToFile ( $"创建墨迹视觉元素失败: {ex}" , LogHelper . LogType . Error ) ;
return null ;
}
}
2025-08-23 21:39:00 +08:00
/// <summary>
/// 开始渐隐动画
/// </summary>
/// <param name="stroke">要渐隐的墨迹</param>
private void StartFadeAnimation ( Stroke stroke )
{
if ( ! _strokeVisuals . TryGetValue ( stroke , out var visual ) ) return ;
try
{
2025-08-23 23:13:39 +08:00
LogHelper . WriteLogToFile ( "开始执行渐隐动画" , LogHelper . LogType . Info ) ;
2025-08-23 21:39:00 +08:00
_dispatcher . InvokeAsync ( ( ) = >
{
2025-08-23 23:13:39 +08:00
// 获取当前透明度和判断是否为高亮笔
var currentOpacity = visual . Opacity ;
var isHighlighter = stroke . DrawingAttributes . IsHighlighter ;
// 根据墨迹类型选择不同的动画效果
if ( isHighlighter )
{
StartHighlighterFadeAnimation ( visual , stroke , currentOpacity ) ;
}
else
{
StartNormalStrokeFadeAnimation ( visual , stroke , currentOpacity ) ;
}
} ) ;
}
catch ( Exception ex )
{
LogHelper . WriteLogToFile ( $"开始渐隐动画失败: {ex}" , LogHelper . LogType . Error ) ;
}
}
/// <summary>
/// 开始普通墨迹的渐隐动画
/// </summary>
private void StartNormalStrokeFadeAnimation ( UIElement visual , Stroke stroke , double currentOpacity )
{
try
{
LogHelper . WriteLogToFile ( "开始普通墨迹渐进式渐隐动画" , LogHelper . LogType . Info ) ;
StartProgressiveFadeAnimation ( visual , stroke , currentOpacity , AnimationDuration ) ;
}
catch ( Exception ex )
{
LogHelper . WriteLogToFile ( $"开始普通墨迹渐隐动画失败: {ex}" , LogHelper . LogType . Error ) ;
}
}
/// <summary>
/// 开始高亮笔的渐隐动画
/// </summary>
private void StartHighlighterFadeAnimation ( UIElement visual , Stroke stroke , double currentOpacity )
{
try
{
LogHelper . WriteLogToFile ( "开始高亮笔渐进式渐隐动画" , LogHelper . LogType . Info ) ;
StartProgressiveFadeAnimation ( visual , stroke , currentOpacity , ( int ) ( AnimationDuration * 1.5 ) ) ;
}
catch ( Exception ex )
{
LogHelper . WriteLogToFile ( $"开始高亮笔渐隐动画失败: {ex}" , LogHelper . LogType . Error ) ;
}
}
/// <summary>
/// 渐进式渐隐动画 - 从起点到终点逐渐消失
/// </summary>
private void StartProgressiveFadeAnimation ( UIElement visual , Stroke stroke , double currentOpacity , int duration )
{
try
{
LogHelper . WriteLogToFile ( $"开始渐进式渐隐动画,墨迹点数:{stroke.StylusPoints.Count}" , LogHelper . LogType . Info ) ;
// 确保所有墨迹都能显示动画,包括短墨迹
if ( stroke . StylusPoints . Count < 2 )
{
// 只有1个点的墨迹也使用分段动画,确保视觉效果
CreateSegmentedStroke ( visual , stroke , currentOpacity , duration ) ;
return ;
}
// 将墨迹分段并创建多个 Path
CreateSegmentedStroke ( visual , stroke , currentOpacity , duration ) ;
}
catch ( Exception ex )
{
LogHelper . WriteLogToFile ( $"渐进式渐隐动画失败: {ex}" , LogHelper . LogType . Error ) ;
// 失败时回退到简单动画
StartSimpleFadeAnimation ( visual , stroke , currentOpacity , duration ) ;
}
}
/// <summary>
/// 创建分段墨迹并开始渐进消失
/// </summary>
private void CreateSegmentedStroke ( UIElement originalVisual , Stroke stroke , double opacity , int duration )
{
try
{
var stylusPoints = stroke . StylusPoints ;
var totalPoints = stylusPoints . Count ;
// 分段算法 - 确保所有墨迹都有足够的动画效果
var strokeLength = CalculateStrokeLength ( stylusPoints ) ;
var segmentCount = CalculateOptimalSegmentCount ( totalPoints , strokeLength ) ;
// 强制最小分段数量,确保短墨迹也有动画效果
segmentCount = Math . Max ( segmentCount , 4 ) ;
var pointsPerSegment = Math . Max ( 1 , totalPoints / segmentCount ) ;
LogHelper . WriteLogToFile ( $"创建 {segmentCount} 个分段,每段约 {pointsPerSegment} 个点" , LogHelper . LogType . Info ) ;
// 隐藏原始视觉元素
originalVisual . Visibility = Visibility . Hidden ;
var segments = new List < UIElement > ( ) ;
var parent = _mainWindow . inkCanvas ? . Parent as System . Windows . Controls . Panel ;
if ( parent = = null )
{
// 如果父容器不是Panel,直接使用InkCanvas
parent = null ; // 稍后会检查并使用InkCanvas.Children
}
// 创建各个分段 - 确保短墨迹也能正确分段
for ( int i = 0 ; i < segmentCount ; i + + )
{
var startIndex = i * pointsPerSegment ;
var endIndex = ( i = = segmentCount - 1 ) ? totalPoints - 1 : ( i + 1 ) * pointsPerSegment ;
// 确保有足够的点来创建分段,对于短墨迹特殊处理
if ( endIndex < = startIndex & & totalPoints > 1 )
{
// 短墨迹:每个点作为一个分段
startIndex = i ;
endIndex = Math . Min ( i + 1 , totalPoints - 1 ) ;
}
// 为每个分段添加重叠,确保连接处平滑
var overlap = Math . Max ( 1 , pointsPerSegment / 6 ) ; // 15%的重叠,平衡平滑与速度
var actualStartIndex = Math . Max ( 0 , startIndex - overlap ) ;
var actualEndIndex = Math . Min ( totalPoints - 1 , endIndex + overlap ) ;
var segment = CreateStrokeSegment ( stroke , actualStartIndex , actualEndIndex , opacity ) ;
if ( segment ! = null )
2025-08-23 21:39:00 +08:00
{
2025-08-23 23:13:39 +08:00
segments . Add ( segment ) ;
if ( parent ! = null )
{
parent . Children . Add ( segment ) ;
}
else if ( _mainWindow . inkCanvas ! = null )
{
_mainWindow . inkCanvas . Children . Add ( segment ) ;
}
}
}
// 开始分段渐隐动画
StartSegmentedFadeAnimation ( segments , stroke , originalVisual , duration ) ;
}
catch ( Exception ex )
{
LogHelper . WriteLogToFile ( $"创建分段墨迹失败: {ex}" , LogHelper . LogType . Error ) ;
StartSimpleFadeAnimation ( originalVisual , stroke , opacity , duration ) ;
}
}
/// <summary>
/// 创建墨迹分段
/// </summary>
private UIElement CreateStrokeSegment ( Stroke originalStroke , int startIndex , int endIndex , double opacity )
{
try
{
// 创建分段的 StylusPoint 集合
var segmentPoints = new StylusPointCollection ( ) ;
for ( int i = startIndex ; i < = endIndex & & i < originalStroke . StylusPoints . Count ; i + + )
{
segmentPoints . Add ( originalStroke . StylusPoints [ i ] ) ;
}
if ( segmentPoints . Count < 2 ) return null ;
// 创建分段墨迹
var segmentStroke = new Stroke ( segmentPoints )
{
DrawingAttributes = originalStroke . DrawingAttributes . Clone ( )
} ;
// 创建分段的视觉元素
var geometry = segmentStroke . GetGeometry ( ) ;
if ( geometry = = null ) return null ;
var drawingAttribs = segmentStroke . DrawingAttributes ;
var path = new Path
{
Data = geometry ,
Stroke = new SolidColorBrush ( drawingAttribs . Color ) ,
StrokeThickness = drawingAttribs . Width ,
StrokeStartLineCap = drawingAttribs . IsHighlighter ? PenLineCap . Flat : PenLineCap . Round ,
StrokeEndLineCap = drawingAttribs . IsHighlighter ? PenLineCap . Flat : PenLineCap . Round ,
StrokeLineJoin = drawingAttribs . IsHighlighter ? PenLineJoin . Miter : PenLineJoin . Round ,
Fill = drawingAttribs . IsHighlighter ? new SolidColorBrush ( drawingAttribs . Color ) : null ,
Opacity = opacity ,
UseLayoutRounding = false ,
SnapsToDevicePixels = false
} ;
// 设置位置
var bounds = geometry . Bounds ;
System . Windows . Controls . Canvas . SetLeft ( path , bounds . Left ) ;
System . Windows . Controls . Canvas . SetTop ( path , bounds . Top ) ;
return path ;
}
catch ( Exception ex )
{
LogHelper . WriteLogToFile ( $"创建墨迹分段失败: {ex}" , LogHelper . LogType . Error ) ;
return null ;
}
}
/// <summary>
/// 开始分段渐隐动画
/// </summary>
private void StartSegmentedFadeAnimation ( List < UIElement > segments , Stroke originalStroke , UIElement originalVisual , int totalDuration )
{
try
{
LogHelper . WriteLogToFile ( $"开始 {segments.Count} 个分段的渐隐动画" , LogHelper . LogType . Info ) ;
// 动画时序算法
var segmentDuration = CalculateOptimalSegmentDuration ( totalDuration , segments . Count ) ;
var animationCurve = CreateAppleStyleAnimationCurve ( segments . Count , totalDuration ) ;
// 跟踪动画完成状态
var completedSegments = new HashSet < UIElement > ( ) ;
var totalSegments = segments . Count ;
// 渐隐效果 - 使用自然的动画曲线
for ( int i = 0 ; i < segments . Count ; i + + )
{
var segment = segments [ i ] ;
// 使用预计算的动画曲线获取延迟时间
var delay = animationCurve [ i ] ;
// 使用定时器延迟启动每个分段的动画
var timer = new DispatcherTimer
{
Interval = TimeSpan . FromMilliseconds ( delay )
2025-08-23 21:39:00 +08:00
} ;
2025-08-23 23:13:39 +08:00
int segmentIndex = i ; // 捕获当前索引
timer . Tick + = ( sender , e ) = >
2025-08-23 21:39:00 +08:00
{
2025-08-23 23:13:39 +08:00
StartSingleSegmentFadeAnimation ( segment , segmentDuration , ( ) = >
2025-08-23 21:39:00 +08:00
{
2025-08-23 23:13:39 +08:00
// 动画完成回调
lock ( completedSegments )
2025-08-23 21:39:00 +08:00
{
2025-08-23 23:13:39 +08:00
completedSegments . Add ( segment ) ;
LogHelper . WriteLogToFile ( $"分段 {segmentIndex} 动画完成,已完成 {completedSegments.Count}/{totalSegments}" , LogHelper . LogType . Info ) ;
// 检查是否所有分段都完成了
if ( completedSegments . Count > = totalSegments )
{
LogHelper . WriteLogToFile ( "所有分段动画完成,开始清理" , LogHelper . LogType . Info ) ;
CleanupSegmentedAnimation ( segments , originalStroke , originalVisual ) ;
}
2025-08-23 21:39:00 +08:00
}
2025-08-23 23:13:39 +08:00
} ) ;
timer . Stop ( ) ;
} ;
2025-08-23 21:39:00 +08:00
2025-08-23 23:13:39 +08:00
timer . Start ( ) ;
}
// 设置一个安全超时定时器,防止无限等待
var safetyTimeout = totalDuration + ( segments . Count * segmentDuration ) + 1200 ; // 额外1.2秒缓冲,确保动画完整
var safetyTimer = new DispatcherTimer
{
Interval = TimeSpan . FromMilliseconds ( safetyTimeout )
} ;
safetyTimer . Tick + = ( sender , e ) = >
{
LogHelper . WriteLogToFile ( $"安全超时触发,强制清理动画" , LogHelper . LogType . Warning ) ;
CleanupSegmentedAnimation ( segments , originalStroke , originalVisual ) ;
safetyTimer . Stop ( ) ;
} ;
safetyTimer . Start ( ) ;
}
catch ( Exception ex )
{
LogHelper . WriteLogToFile ( $"分段渐隐动画失败: {ex}" , LogHelper . LogType . Error ) ;
CleanupSegmentedAnimation ( segments , originalStroke , originalVisual ) ;
}
}
/// <summary>
/// 单个分段的渐隐动画
/// </summary>
private void StartSingleSegmentFadeAnimation ( UIElement segment , int duration , Action onCompleted = null )
{
try
{
// 只使用透明度动画,保持墨迹原有粗细
var fadeAnimation = new DoubleAnimation
{
From = segment . Opacity ,
To = 0.0 ,
Duration = TimeSpan . FromMilliseconds ( duration ) ,
EasingFunction = new CubicEase { EasingMode = EasingMode . EaseInOut } // 更平滑的缓动
} ;
// 添加动画完成事件
if ( onCompleted ! = null )
{
fadeAnimation . Completed + = ( sender , e ) = >
{
onCompleted ? . Invoke ( ) ;
2025-08-23 21:39:00 +08:00
} ;
2025-08-23 23:13:39 +08:00
}
2025-08-23 21:39:00 +08:00
2025-08-23 23:13:39 +08:00
// 只应用透明度动画,不改变墨迹大小
segment . BeginAnimation ( UIElement . OpacityProperty , fadeAnimation ) ;
2025-08-23 21:39:00 +08:00
}
catch ( Exception ex )
{
2025-08-23 23:13:39 +08:00
LogHelper . WriteLogToFile ( $"单个分段渐隐动画失败: {ex}" , LogHelper . LogType . Error ) ;
// 即使失败也要调用完成回调
onCompleted ? . Invoke ( ) ;
}
}
/// <summary>
/// 清理分段动画
/// </summary>
private void CleanupSegmentedAnimation ( List < UIElement > segments , Stroke originalStroke , UIElement originalVisual )
{
try
{
LogHelper . WriteLogToFile ( "清理分段渐隐动画" , LogHelper . LogType . Info ) ;
// 移除所有分段
var parent = _mainWindow . inkCanvas ? . Parent as System . Windows . Controls . Panel ;
foreach ( var segment in segments )
{
if ( parent ! = null & & parent . Children . Contains ( segment ) )
{
parent . Children . Remove ( segment ) ;
}
else if ( _mainWindow . inkCanvas ! = null & & _mainWindow . inkCanvas . Children . Contains ( segment ) )
{
_mainWindow . inkCanvas . Children . Remove ( segment ) ;
}
}
// 清理原始墨迹
OnAnimationCompleted ( originalVisual , originalStroke ) ;
}
catch ( Exception ex )
{
LogHelper . WriteLogToFile ( $"清理分段动画失败: {ex}" , LogHelper . LogType . Error ) ;
}
}
/// <summary>
/// 简单渐隐动画(备用方案)
/// </summary>
private void StartSimpleFadeAnimation ( UIElement visual , Stroke stroke , double currentOpacity , int duration )
{
try
{
LogHelper . WriteLogToFile ( "使用简单渐隐动画" , LogHelper . LogType . Info ) ;
var fadeAnimation = new DoubleAnimation
{
From = currentOpacity ,
To = 0.0 ,
Duration = TimeSpan . FromMilliseconds ( duration ) ,
EasingFunction = new QuadraticEase { EasingMode = EasingMode . EaseIn }
} ;
fadeAnimation . Completed + = ( sender , e ) = > OnAnimationCompleted ( visual , stroke ) ;
visual . BeginAnimation ( UIElement . OpacityProperty , fadeAnimation ) ;
}
catch ( Exception ex )
{
LogHelper . WriteLogToFile ( $"简单渐隐动画失败: {ex}" , LogHelper . LogType . Error ) ;
OnAnimationCompleted ( visual , stroke ) ;
}
}
/// <summary>
/// 计算墨迹的实际长度
/// </summary>
private double CalculateStrokeLength ( StylusPointCollection points )
{
if ( points . Count < 2 ) return 0 ;
double totalLength = 0 ;
for ( int i = 1 ; i < points . Count ; i + + )
{
var p1 = points [ i - 1 ] . ToPoint ( ) ;
var p2 = points [ i ] . ToPoint ( ) ;
totalLength + = Math . Sqrt ( Math . Pow ( p2 . X - p1 . X , 2 ) + Math . Pow ( p2 . Y - p1 . Y , 2 ) ) ;
}
return totalLength ;
}
/// <summary>
/// 根据墨迹特性计算最优分段数量 - 平衡速度与完整性
/// </summary>
private int CalculateOptimalSegmentCount ( int pointCount , double strokeLength )
{
// 平衡速度与完整性,确保动画效果的同时提高速度
const double PIXELS_PER_SEGMENT = 12.0 ; // 每段适中长度,平衡效果与速度
const int MIN_SEGMENTS = 5 ; // 适当的最小分段数,确保动画效果
const int MAX_SEGMENTS = 100 ; // 适中的最大分段数,平衡性能与效果
// 根据长度计算基础分段数
var lengthBasedSegments = Math . Max ( MIN_SEGMENTS , ( int ) ( strokeLength / PIXELS_PER_SEGMENT ) ) ;
// 根据点密度调整,平衡效果与速度
var density = pointCount > 0 ? strokeLength / pointCount : 1 ;
var densityFactor = Math . Max ( 0.4 , Math . Min ( 2.5 , density / 1.8 ) ) ;
var finalSegments = ( int ) ( lengthBasedSegments * densityFactor ) ;
// 对于短墨迹,确保至少有4个分段
if ( pointCount < = 5 )
{
finalSegments = Math . Max ( finalSegments , 4 ) ;
}
// 限制在合理范围内
return Math . Min ( MAX_SEGMENTS , Math . Max ( MIN_SEGMENTS , finalSegments ) ) ;
}
/// <summary>
/// 计算最优的单段动画持续时间 - 平衡速度与完整性
/// </summary>
private int CalculateOptimalSegmentDuration ( int totalDuration , int segmentCount )
{
// 平衡速度与动画完整性
var baseDuration = totalDuration / Math . Max ( segmentCount , 1 ) ;
var minDuration = 150 ; // 每段最少150ms,确保动画完整显示
var maxDuration = 500 ; // 每段最多500ms,平衡速度与完整性
return Math . Max ( minDuration , Math . Min ( maxDuration , baseDuration ) ) ;
}
/// <summary>
/// 创建优化的动画时间曲线 - 平衡速度与完整性
/// </summary>
private int [ ] CreateAppleStyleAnimationCurve ( int segmentCount , int totalDuration )
{
var curve = new int [ segmentCount ] ;
// 平衡速度与完整性,确保动画有足够时间播放
var availableTime = totalDuration * 0.6 ; // 使用60%的总时间,给动画留足够缓冲
var delayBetweenSegments = Math . Max ( 60 , availableTime / Math . Max ( segmentCount , 1 ) ) ;
for ( int i = 0 ; i < segmentCount ; i + + )
{
// 线性延迟,确保每个分段都有足够时间
curve [ i ] = ( int ) ( i * delayBetweenSegments ) ;
}
return curve ;
}
/// <summary>
/// 动画完成后的统一处理
/// </summary>
private void OnAnimationCompleted ( UIElement visual , Stroke stroke )
{
try
{
LogHelper . WriteLogToFile ( "渐隐动画完成,开始清理墨迹" , LogHelper . LogType . Info ) ;
// 从父容器中移除墨迹
var parent = _mainWindow . inkCanvas ? . Parent as System . Windows . Controls . Panel ;
if ( parent ! = null & & parent . Children . Contains ( visual ) )
{
parent . Children . Remove ( visual ) ;
LogHelper . WriteLogToFile ( "墨迹已从父容器移除" , LogHelper . LogType . Info ) ;
}
else if ( _mainWindow . inkCanvas ! = null & & _mainWindow . inkCanvas . Children . Contains ( visual ) )
{
_mainWindow . inkCanvas . Children . Remove ( visual ) ;
LogHelper . WriteLogToFile ( "墨迹已从 inkCanvas.Children 移除" , LogHelper . LogType . Info ) ;
}
RemoveStroke ( stroke ) ;
}
catch ( Exception ex )
{
LogHelper . WriteLogToFile ( $"渐隐动画完成后清理墨迹失败: {ex}" , LogHelper . LogType . Error ) ;
2025-08-23 21:39:00 +08:00
}
}
#endregion
}
}