WPF实现跳动的字符效果

  • WPF实现跳动的字符效果已关闭评论
  • 172 次浏览
  • A+
所属分类:.NET技术
摘要

本文将介绍一个好玩但实际作用可能不太大的动画效果:跳动的字符。为了提高动画效果的可重用性以及调用的灵活性,通过Behavior实现跳动的字符动画。先看下效果:

本文将介绍一个好玩但实际作用可能不太大的动画效果:跳动的字符。为了提高动画效果的可重用性以及调用的灵活性,通过Behavior实现跳动的字符动画。先看下效果:

WPF实现跳动的字符效果

技术要点与实现

通过TextEffectPositionStartPositionCount属性控制应用动画效果的子字符串的起始位置以及长度,同时使用TranslateTransform设置字符纵坐标的移动变换,以实现跳动的效果。主要步骤如下:

  • 在OnAttached方法中,注册Loaded事件,在Load事件中为TextBlock添加TextEffect效果,其中PositionCount设置为1,每次只跳动一个字符。
  • 添加启动动画效果的BeginEffect方法,并创建控制子字符纵向移动变换的线性动画。然后根据字符串(剔除空字符)的长度n,创建n个关键帧,每个关键帧中把PositionStart设置为要跳动的字符在字符串中的索引
  • 在开启动画属性IsEnabled=trueTextBlock内容变化时,启动动画效果

在创建关键帧设置跳动字符位置时剔除了空字符,是为了是动画效果显得连贯

public class DanceCharEffectBehavior : Behavior<TextBlock>     {         private TextEffect _textEffect;         private string _textEffectName;         private TranslateTransform _translateTransform = null;         private string _translateTransformName;         private Storyboard _storyboard;          protected override void OnAttached()         {             base.OnAttached();              this.AssociatedObject.Loaded += AssociatedObject_Loaded;             this.AssociatedObject.Unloaded += AssociatedObject_Unloaded;             this.AssociatedObject.IsVisibleChanged += AssociatedObject_IsVisibleChanged;             BindingOperations.SetBinding(this, DanceCharEffectBehavior.InternalTextProperty, new Binding("Text") { Source = this.AssociatedObject });         }          protected override void OnDetaching()         {             base.OnDetaching();              this.AssociatedObject.Loaded -= AssociatedObject_Loaded;             this.AssociatedObject.Unloaded -= AssociatedObject_Unloaded;             this.AssociatedObject.IsVisibleChanged -= AssociatedObject_IsVisibleChanged;             this.ClearValue(DanceCharEffectBehavior.InternalTextProperty);              if (_storyboard != null)             {                 _storyboard.Remove(this.AssociatedObject);                 _storyboard.Children.Clear();             }             if (_textEffect != null)                 this.AssociatedObject.TextEffects.Remove(_textEffect);         }          private void AssociatedObject_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)         {             if ((bool)e.NewValue == false)             {                 if (_storyboard != null)                     _storyboard.Stop(this.AssociatedObject);             }             else             {                 BeginEffect(this.AssociatedObject.Text);             }         }          private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)         {             if (_textEffect == null)             {                 this.AssociatedObject.TextEffects.Add(_textEffect = new TextEffect()                 {                     PositionCount = 1,                     Transform = _translateTransform = new TranslateTransform(),                 });                 NameScope.SetNameScope(this.AssociatedObject, new NameScope());                 this.AssociatedObject.RegisterName(_textEffectName = "n" + Guid.NewGuid().ToString("N"), _textEffect);                 this.AssociatedObject.RegisterName(_translateTransformName = "n" + Guid.NewGuid().ToString("N"), _translateTransform);                 if (IsEnabled)                     BeginEffect(this.AssociatedObject.Text);             }         }          private void AssociatedObject_Unloaded(object sender, RoutedEventArgs e)         {             StopEffect();         }           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 || _translateTransformName == null || IsEnabled == false) return;              if (_storyboard == null)                 _storyboard = new Storyboard();             double duration = 0.5d;             DoubleAnimation da = new DoubleAnimation();              Storyboard.SetTargetName(da, _translateTransformName);             Storyboard.SetTargetProperty(da, new PropertyPath(TranslateTransform.YProperty));             da.From = 0d;             da.To = 10d;             da.Duration = TimeSpan.FromSeconds(duration / 2d);             da.RepeatBehavior = RepeatBehavior.Forever;             da.AutoReverse = true;              char emptyChar = ' ';             List<int> lsb = new List<int>();             for (int i = 0; i < textLength; ++i)             {                 if (text[i] != emptyChar)                 {                     lsb.Add(i);                 }             }              Int32AnimationUsingKeyFrames frames = new Int32AnimationUsingKeyFrames();             Storyboard.SetTargetName(frames, _textEffectName);             Storyboard.SetTargetProperty(frames, new PropertyPath(TextEffect.PositionStartProperty));             frames.Duration = TimeSpan.FromSeconds((lsb.Count) * duration);             frames.RepeatBehavior = RepeatBehavior.Forever;             frames.AutoReverse = true;              int ii = 0;             foreach (int index in lsb)             {                 frames.KeyFrames.Add(new DiscreteInt32KeyFrame()                 {                     Value = index,                     KeyTime = TimeSpan.FromSeconds(ii * duration),                 });                 ++ii;             }              _storyboard.Children.Add(da);             _storyboard.Children.Add(frames);             _storyboard.Begin(this.AssociatedObject, true);         }          private string InternalText         {             get { return (string)GetValue(InternalTextProperty); }             set { SetValue(InternalTextProperty, value); }         }          private static readonly DependencyProperty InternalTextProperty =         DependencyProperty.Register("InternalText", typeof(string), typeof(DanceCharEffectBehavior),         new PropertyMetadata(OnInternalTextChanged));          private static void OnInternalTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)         {             var source = d as DanceCharEffectBehavior;             if (source._storyboard != null)             {                 source._storyboard.Stop(source.AssociatedObject);                 source._storyboard.Children.Clear();             }             source.SetEffect(e.NewValue == null ? string.Empty : e.NewValue.ToString());         }           public bool IsEnabled         {             get { return (bool)GetValue(IsEnabledProperty); }             set { SetValue(IsEnabledProperty, value); }         }          public static readonly DependencyProperty IsEnabledProperty =             DependencyProperty.Register("IsEnabled", typeof(bool), typeof(DanceCharEffectBehavior), new PropertyMetadata(true, (d, e) =>             {                 bool b = (bool)e.NewValue;                 var source = d as DanceCharEffectBehavior;                 source.SetEffect(source.InternalText);             }));      } 

调用的时候只需要在TextBlock添加Behavior即可,代码如下

<TextBlock FontSize="20" Text="Hello">     <i:Interaction.Behaviors>         <local:DanceCharEffectBehavior x:Name="titleEffect" IsEnabled="True" />     </i:Interaction.Behaviors> </TextBlock> 

结尾

本例中还有许多可以完善的地方,比如字符跳动的幅度可以根据实际的FontSize来设置,或者增加依赖属性来控制;动画是否倒退播放,是否循环播放,以及动画的速度都可以通过增加依赖属性在调用时灵活设置。