前言
ChatGPT类的应用风靡一时,其最引人注目的特点之一就是在回答用户问题时,像真人打字一样逐字输出内容。这种效果不仅增强了交互的真实感,也让用户在等待回复的过程中有了更自然的体验。出于对这一效果的好奇,决定尝试用WPF来模拟实现这一功能。
虽然真实的ChatGPT逐字输出效果涉及复杂的语言生成模型和前后端通信机制,本文并不打算深入探讨这些底层技术,而是聚焦于如何在WPF中通过动画技术实现类似的视觉效果。接下来,我将介绍两种不同的实现方法,并重点展示其中一种通过关键帧动画拼接字符串的具体实现。
效果预览
方法一效果
方法二效果
技术实现
对于逐字输出的效果,构思了两种实现方法:
方法一:关键帧动画拼接字符串
这种方法的核心思想是根据目标字符串的长度,创建多个关键帧(DiscreteStringKeyFrame)。每个关键帧的值依次增加一个字符,从第一个字符开始,直到完整的目标字符串。
通过这种方式,TextBlock的文本内容会逐步"生长",从而实现逐字输出的效果。
具体实现如下:
public classTypingCharAnimationBehavior : Behavior<TextBlock>{ private Storyboard _storyboard; protected override void OnAttached() { base.OnAttached(); this.AssociatedObject.Loaded += AssociatedObject_Loaded; this.AssociatedObject.Unloaded += AssociatedObject_Unloaded; BindingOperations.SetBinding(this, TypingCharAnimationBehavior.InternalTextProperty, new Binding("Tag") { Source = this.AssociatedObject }); } private void AssociatedObject_Unloaded(object sender, RoutedEventArgs e) { StopEffect(); } private void AssociatedObject_Loaded(object sender, RoutedEventArgs e) { if (IsEnabled) BeginEffect(InternalText); } protected override void OnDetaching() { base.OnDetaching(); this.AssociatedObject.Loaded -= AssociatedObject_Loaded; this.AssociatedObject.Unloaded -= AssociatedObject_Unloaded; this.ClearValue(TypingCharAnimationBehavior.InternalTextProperty); if (_storyboard != null) { _storyboard.Remove(this.AssociatedObject); _storyboard.Children.Clear(); } } privatestring InternalText { get { return (string)GetValue(InternalTextProperty); } set { SetValue(InternalTextProperty, value); } } privatestaticreadonly DependencyProperty InternalTextProperty = DependencyProperty.Register("InternalText", typeof(string), typeof(TypingCharAnimationBehavior), new PropertyMetadata(OnInternalTextChanged)); private static void OnInternalTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var source = d as TypingCharAnimationBehavior; if (source._storyboard != null) { source._storyboard.Stop(source.AssociatedObject); source._storyboard.Children.Clear(); } source.SetEffect(e.NewValue == null ? string.Empty : e.NewValue.ToString()); } publicbool IsEnabled { get { return (bool)GetValue(IsEnabledProperty); } set { SetValue(IsEnabledProperty, value); } } publicstaticreadonly DependencyProperty IsEnabledProperty = DependencyProperty.Register("IsEnabled", typeof(bool), typeof(TypingCharAnimationBehavior), new PropertyMetadata(true, (d, e) => { bool b = (bool)e.NewValue; var source = d as TypingCharAnimationBehavior; source.SetEffect(source.InternalText); })); private void SetEffect(string text) { if (string.IsNullOrEmpty(text) || this.AssociatedObject.IsLoaded == false) { StopEffect(); return; } BeginEffect(text); } private void StopEffect() { if (_storyboard != null) { _storyboard.Stop(this.AssociatedObject); } } private void BeginEffect(string text) { StopEffect(); int textLength = text.Length; if (textLength < 1 || IsEnabled == false) return; if (_storyboard == null) _storyboard = new Storyboard(); double duration = 0.15d; StringAnimationUsingKeyFrames frames = new StringAnimationUsingKeyFrames(); Storyboard.SetTargetProperty(frames, new PropertyPath(TextBlock.TextProperty)); frames.Duration = TimeSpan.FromSeconds(textLength * duration); for (int i = 0; i < textLength; i++) { frames.KeyFrames.Add(new DiscreteStringKeyFrame() { Value = text.Substring(0, i + 1), KeyTime = TimeSpan.FromSeconds(i * duration), }); } _storyboard.Children.Add(frames); _storyboard.Begin(this.AssociatedObject, true); }}
由于每一帧都在修改TextBlock的Text属性,如果直接绑定TextBlock的Text属性,当数据源发生变化时,无法区分是关键帧动画修改的还是外部数据源变化导致的。因此,这里使用TextBlock的Tag属性来暂存要显示的字符串内容。调用时只需将需要显示的字符串变量绑定到Tag,并在TextBlock中添加Behavior即可,代码如下:
<TextBlock x:Name="source" IsEnabled="True" Tag="{Binding TypingText, ElementName=self}" TextWrapping="Wrap"> <i:Interaction.Behaviors> <local:TypingCharAnimationBehavior IsEnabled="True" /> </i:Interaction.Behaviors></TextBlock>
方法二:通过TextEffect控制字体颜色
另一种方法是先将TextBlock的字体颜色设置为透明,然后通过TextEffect的PositionStart和PositionCount属性控制应用动画效果的子字符串的起始位置和长度,同时使用ColorAnimation设置TextEffect的Foreground属性由透明变为目标颜色(如黑色)。
这种方法的实现思路与WPF中实现跳动字符效果类似,具体实现不再详述。
两种方案各有优劣
关键帧动画拼接字符串的优点是最大程度还原了逐字输出的过程,缺点是需要额外的属性(如Tag)来辅助,并且在遇到英文单词换行时,可能会出现单词从上一行行尾跳到下一行行首的问题。
通过TextEffect设置字体颜色的方法则相反,不需要额外的属性辅助,且不会出现单词在输入过程中从行尾跳到行首的问题。然而,这种方法实际上是将所有文字预先渲染到界面上,只是通过透明的字体颜色"欺骗"用户的眼睛,逐字改变字体颜色来模拟逐字打印的效果。
总结
通过本次实践,我们成功地在WPF中模拟了ChatGPT类应用的逐字输出效果。两种实现方法各有特点,开发者可以根据具体需求选择合适的方式。
关键帧动画拼接字符串更贴近真实的逐字输入过程,而通过TextEffect控制字体颜色则在性能和稳定性上更具优势。
无论选择哪种方法,WPF的强大动画系统都为我们提供了丰富的工具来实现复杂的视觉效果。希望本文能为有类似需求的开发提供一些启发和帮助。
关键词
WPF、逐字输出、关键帧动画、TextEffect、Behavior、ChatGPT、动画效果、字符串拼接、字体颜色、UI设计
最后
如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。
也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!
优秀是一种习惯,欢迎大家留言学习!
作者:czwy
出处:cnblogs.com/czwy/p/17617600.html
声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!