本文将介绍一个好玩但实际作用可能不太大的动画效果:跳动的字符。为了提高动画效果的可重用性以及调用的灵活性,通过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. 2020-09-13:判断一个正整数是a的b次方,a和b是整数,并且大于等于2,如何求解?

    福哥答案2020-09-13: 首先确定b的范围,b的范围一定在[2,logN]里.然后遍历b,求a的范围,如果范围长度等于0,说明这个正整数是a的b次方.1.遍历b范围.二分法求a,a初始范围是[2 ...

  2. docker无法启动,报错grpc: addrConn.createTransport failed to connect to {unix:///run/containerd/containerd.

    docker无法启动,报错.k8s的pod镜像加载失败. 解法方法: 删除/var/lib/docker/和/var/lib/containerd/ 这两个文件夹,重起docker服务. 问题完美解决 ...

  3. 2021-02-22:一个象棋的棋盘,然后把整个棋盘放入第一象限,棋盘的最左下角是(0,0)位置,那么整个棋盘就是横坐标上9条线、纵坐标上10条线的区域。给你三个 参数 x,y,k。返回“马”从(0,0)位置出发,必须走k步。最后落在(x,y)上的方法数有多少种?

    2021-02-22:一个象棋的棋盘,然后把整个棋盘放入第一象限,棋盘的最左下角是(0,0)位置,那么整个棋盘就是横坐标上9条线.纵坐标上10条线的区域.给你三个 参数 x,y,k.返回"马 ...

  4. 2022-03-04:爱吃香蕉的珂珂。 珂珂喜欢吃香蕉。这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 H 小时后回来。 珂珂可以决定她吃香蕉的速度 K (单位:根

    2022-03-04:爱吃香蕉的珂珂. 珂珂喜欢吃香蕉.这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉.警卫已经离开了,将在 H 小时后回来. 珂珂可以决定她吃香蕉的速度 K (单位:根 ...

  5. 【Linux】详解六种配置Linux环境变量的方法(以centos为例)

    本文时间 2023-05-19 作者:sugerqube漆瓷 本文重理解,!!忽略环境变量加载原理!! 本文目标:理解六大环境变量配置,选择合适的配置文件进行配置 配置环境的理由 以shell编程为例 ...

  6. vue3+vite2+element-plus+ts搭建一个项目

    花了几天用 vue3+ vite2+ element-plus+ ts 搭了个 极简版骨架型数据管理系统,使用静态数据模拟动态路由,路由拦截,登录页面鉴权等,使用了iconify字体图标,整合了ces ...

  7. Intellij IDEA最新激活码,适合2022,2023和所有版本,永久更新

    分享一下 IntelliJ IDEA 2023.1 最新激活注册码,破解教程如下,可免费永久激活,亲测有效,下面是详细文档哦~ 申明:本教程 IntelliJ IDEA 破解补丁.激活码均收集于网络, ...

  8. 搭建一个属于自己的springboot项目

    一.确定环境 最近公司要上个新系统,指定由我来带两个人进行开发,既然是新项目,那么项目搭建的事就落到我的头上了.现在都是使用springboot进行开发,为此我搭环境使用的是springboot,具体 ...

  9. 曲线艺术编程 coding curves 第七章 抛物线(Parabolas)

    抛物线 Parabolas 原作:Keith Peters https://www.bit-101.com/blog/2022/11/coding-curves/ 译者:池中物王二狗(sheldon) ...

  10. x.ai还是OpenAI?埃隆·马斯克的AI帝国【2】

    上期内容咱们提到了埃隆马斯克的特斯拉是自动驾驶领域的领导者,大家可能近些年也都有从各类渠道听到过Tesla自动驾驶有关的新闻.不同于像包括Google子公司Waymo在内的大多数使用激光雷达来实现自动 ...