本文将介绍一个好玩但实际作用可能不太大的动画效果:跳动的字符。为了提高动画效果的可重用性以及调用的灵活性,通过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. #Powerbi 利用视觉对象着色地图,制作数据地图

    日常工作中,有时我们会遇到需要地图来展示我们的数据场景,利用POWERBI,我们可以快速的制作自己的业务地图. Powerbi自带了三大地图,今天我们用到的是形状地图. 步骤讲解: 第一步:下载对应的 ...

  2. 揭秘Karmada百倍集群规模多云基础设施体系

    摘要:本文结合Karmada社区对大规模场景的思考,揭示Karmada稳定支持100个大规模集群.管理超过50万个节点和200万个Pod背后的原理 本文分享自华为云社区<Karmada百倍集群规 ...

  3. 2023-01-07:hyper/docker-registry-web是registry的web界面工具之一。请问部署在k3s中,yaml如何写?

    2023-01-07:hyper/docker-registry-web是registry的web界面工具之一.请问部署在k3s中,yaml如何写? 答案2023-01-07: yaml如下: api ...

  4. 2020-11-10:golang中的接口,类型不空,值为空,如何判断是nil?

    福哥答案2020-11-10: reflect.ValueOf(接口变量).IsNil(),用这个即可判断.对于值类型,会panic.两种方法如下:1.异常判断:recover捕获.2.类型判断:re ...

  5. vue全家桶进阶之路18:Vue Router

    Vue Router 是 Vue.js 的官方路由管理器,它可以帮助我们实现单页应用(Single Page Application,SPA)的前端路由. Vue Router 基于 Vue.js 的 ...

  6. drf——restful规范、序列化反序列化、drf介绍和快速使用、drf之APIView源码

    1.restful规范 # restful是一种定义API接口的设计风格,API接口的编写规范,尤其适用于前后端分离的应用模式中 这种风格的理念人为后端开发任务就是提供数据的,对外提供的是数据资源的访 ...

  7. 鼠标移入select options会触发mouseleave 事件处理方案

    近来遇到一项目有一侧边工具菜单,在鼠标mouseenter事件打开对应的详细操作列表,当mouseleave时进行关闭,然操作列表中有一个select , 每当鼠标移入select options 时 ...

  8. 警惕看不见的重试机制:为什么使用RPC必须考虑幂等性

    0 文章概述 在RPC场景中因为重试或者没有实现幂等机制而导致的重复数据问题,必须引起大家重视,有可能会造成例如一次购买创建多笔订单,一条通知信息被发送多次等问题,这是技术人员必须面对和解决的问题. ...

  9. Java的标识符、关键字、 常量、变量、数据类型、 数据类型转换、字符ASCII码表(基础语法的学习)

    一.Java的关键字和标识符 关键字 1.定义: 在Java程序中,已经定义好的被预先使用的一些特殊的单词称为关键字,一共有50个关键字(48+2个保留字),关键字都是小写的英文单词 2.关键字的分类 ...

  10. 生信服务器 | 更改 CentOS/RHEL 6/7 中的时区

    这几天在学习折腾 docker 的时候遇到一个很常见的问题,就是 run container 的时候发现大部分 image 默认使用的时间都是 UTC  (Universal Time Coordin ...