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

技术要点与实现

通过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来设置,或者增加依赖属性来控制;动画是否倒退播放,是否循环播放,以及动画的速度都可以通过增加依赖属性在调用时灵活设置。

WPF实现跳动的字符效果的更多相关文章

  1. WPF Multi-Touch 开发:惯性效果(Inertia)

    原文 WPF Multi-Touch 开发:惯性效果(Inertia) 从上一篇实例可以发现在图片移动过程中如果将手指移开屏幕则图片会立刻停止,根据这种情况WPF 提供另外一种惯性效果(Inertia ...

  2. WPF 图片浏览 伪3D效果

    原文:WPF 图片浏览 伪3D效果 首先上效果图: 因项目要求,需要把图片以"好看"."炫"的效果展示出来,特地研究了一下WPF关于3D方面的制作,奈何最终成果 ...

  3. 【WPF】两则动画效果

    原文:[WPF]两则动画效果 引言 利用WPF的动画可以轻而易举的实现各种各样的特效,如擦除,滑动进入等,先看两个效果图 第一个效果 这个动画其实利用了OpacityMask和LinearGradie ...

  4. C#控制台输出退格实现变换闪烁的字符效果

    C#控制台输出退格实现变换闪烁的字符效果,传统的Console.Clear()方法能清除控制台上的所有内容. 如果用 Console.Write('\u0008');可以实现输出退格,这样就可以方便地 ...

  5. [WPF,XAML] 跳动的心

    原文:[WPF,XAML] 跳动的心 没什么艺术细胞,原谅,原谅! <Canvas Width="0" Height="0"> <Canvas ...

  6. 用WPF轻松打造iTunes CoverFlow效果

    原文:用WPF轻松打造iTunes CoverFlow效果 用WPF轻松打造iTunes CoverFlow效果                                             ...

  7. WPF Path实现虚线流动效果

    原文:WPF Path实现虚线流动效果 最近闲来无事,每天上上网,看看博客生活也过得惬意,这下老总看不过去了,给我一个任务,叫我用WPF实现虚线流动效果,我想想,不就是虚线流动嘛,这简单于是就答应下来 ...

  8. WPF特效-实现3D足球效果

    原文:WPF特效-实现3D足球效果 WPF 实现 3D足球效果,效果图如下:  每个面加载不同贴图. <UserControl x:Class="MediaBalll.Model3Ds ...

  9. 【笔记】WPF实现ViewPager引导界面效果及问题汇总

    最近在开发项目的首次使用引导界面时,遇到了问题,引导界面类似于安卓手机ViewPager那样的效果,希望通过左右滑动手指来实现切换不同页面,其间伴随动画. 实现思路: 1.界面布局:新建一个UserC ...

  10. 获取当前url并指定url中的字符 效果

    效果介绍:1.获取当前url 2.通过获取的url,找到指定的字符并判断 3.如果是指定字符,页面跳转到博客园:如果不是页面跳转到百度 例如:http://www.cnblogs.com/fs521c ...

随机推荐

  1. bean的作用域和@scope注解

    bean的作用域由@scope注解来修改,该注解有五个不同的取值,分别是:singleton.prototype.request.session.global-session. singleton,在 ...

  2. 2021-05-20:给定一个数组arr, 返回如果排序之后,相邻两数的最大差值。要求:时间复杂度O(N) 。

    2021-05-20:给定一个数组arr, 返回如果排序之后,相邻两数的最大差值.要求:时间复杂度O(N) . 福大大 答案2021-05-20: 假设答案法.N个数,根据最大值和最小值的范围等分成N ...

  3. 2021-08-13:给定一个每一行有序、每一列也有序,整体可能无序的二维数组 ,在给定一个正数k,返回二维数组中,最小的第k个数。

    2021-08-13:给定一个每一行有序.每一列也有序,整体可能无序的二维数组 ,在给定一个正数k,返回二维数组中,最小的第k个数. 福大大 答案2021-08-13: 二分法. 代码用golang编 ...

  4. 2021-10-27:课程表。你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisi

    2021-10-27:课程表.你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 .在选修某些课程之前需要一些先修课程. 先修课程按数组 prerequisi ...

  5. MMCM and PLL Dynamic Reconfiguration

    Reconfiguration is performed through the DRP. The DRP provides access to the configuration bits that ...

  6. vue自定义组件——search-box

    pre { overflow-y: auto; max-height: 300px } github地址: https://github.com/lxmghct/my-vue-components 组 ...

  7. ET介绍—— 一切皆实体的设计

    一切皆实体 目前十分流行ECS设计,主要是守望先锋的成功,引爆了这种技术.守望先锋采用了状态帧这种网络技术,客户端会进行预测,预测不准需要进行回滚,由于组件式的设计,回滚可以只回滚某些组件即可.ECS ...

  8. Dashboard监控页面和Zuul路由网关

    Dashboard监控页面 dashboard监控功能:我们需要前端页面能够监控提供者provider8001的工作状态 对dashboard监控页面的介绍: 1.在客户端导依赖 <?xml v ...

  9. Nature 重大发现:癌基因竟不在染色体上?第一作者吴思涵亲身解读!

    编辑|李丽 记者|布德鸟 图片提供|吴思涵 今日凌晨,美国加州大学圣地亚哥分校 Ludwig 癌症研究所的 Paul Mischel 教授领导的研究团队发现, 大量的癌基因并不在染色体上,而是会从染色 ...

  10. Access+Trunk 配置

    实验拓扑 实验需求 按图示给各 PC 配置 IP 地址 在SW1和SW2上创建聚合组 PC1 与 PC3 属于 VLAN 10 : PC2 与 PC4 属于 VLAN 20 利用 Access 与 T ...