WPF实现跳动的字符效果

wpf,实现,跳动,字符,效果 · 浏览次数 : 951

小编点评

该代码介绍了一种好玩但实际作用可能不太大的动画效果:跳动的字符。该代码主要分为以下几个部分: 1. **Behavior实现跳动字符动画**: - 使用 `TextEffect` 控制字符的动画效果。 - 创建控制子字符纵向移动变换的线性动画。 - 根据字符串(剔除空字符)的长度创建n个关键帧,每个关键帧中设置 `PositionStart` 属性。 2. **OnAttach 方法**: - 注册 `Loaded` 事件,当文本加载完成后触发 `AssociatedObject_Loaded` 方法。 3. **OnDetach 方法**: - 当文本卸载时停止动画,清理相关属性。 4. **SetEffect 方法**: - 从字符串中获取动画效果的长度。 - 创建 `Int32AnimationUsingKeyFrames` 动画,设置目标属性为 `PositionStart`。 - 设置动画循环播放,自动反弹。 5. **OnInternalTextChanged 方法**: - 当文本内容发生变化时调用 `SetEffect` 方法设置新的动画效果。 6. **调用**: - 在 `TextBlock` 中添加 `DanceCharEffectBehavior` 对象。 - 设置 `IsEnabled` 属性控制动画是否可见。 **一些可以完善的地方:** * 可以根据实际的 `FontSize` 设置字符跳动的幅度。 * 可以增加依赖属性控制动画是否倒退播放、循环播放以及动画的速度等。 * 可以使用其他动画效果,例如旋转、缩放等。

正文

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

image

技术要点与实现

通过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实现跳动的字符效果相似的内容:

WPF实现跳动的字符效果

本文将介绍一个好玩但实际作用可能不太大的动画效果:跳动的字符。为了提高动画效果的可重用性以及调用的灵活性,通过Behavior实现跳动的字符动画。先看下效果: ![image](https://img2023.cnblogs.com/blog/3056716/202308/3056716-20230

[WPF]用HtmlTextBlock实现消息对话框的内容高亮和跳转

动手写一个简单的消息对话框一文介绍了如何实现满足常见应用场景的消息对话框。但是内容区域的文字仅仅起到信息展示作用,对于需要部分关键字高亮,或者部分内容有交互性的场景(例如下图提示信息中的“what's the risk?”需要跳转)则无能为力了。本文将介绍如何在WPF中灵活的实现消息对话框中局部文字

WPF实现类似ChatGPT的逐字打印效果

###背景 前一段时间ChatGPT类的应用十分火爆,这类应用在回答用户的问题时逐字打印输出,像极了真人打字回复消息。出于对这个效果的兴趣,决定用WPF模拟这个效果。 >真实的ChatGPT逐字输出效果涉及其语言生成模型原理以及服务端与前端通信机制,本文不做过多阐述,重点是如何用WPF模拟这个效果。

WPF实现Element UI风格的日期时间选择器

### 背景 业务开发过程中遇到一个日期范围选择的需求,和Element UI的DateTimePicker组件比较类似,由两个日历控件组成,联动选择起始时间和结束时间。 ### 问题 WPF中提供了一个`DatePicker`的控件,主要由`DatePickerTextBox`、`Button`和

Simple WPF: WPF 实现按钮的长按,短按功能

实现了一个支持长短按得按钮组件,单击可以触发Click事件,长按可以触发LongPressed事件,长按松开时触发LongClick事件。还可以和自定义外观相结合,实现自定义的按钮外形。

Simple WPF: WPF实现一个MINIO等S3兼容对象存储上传文件的小工具

之前在阿里云ECS 99元/年的活动实例上搭建了一个测试用的MINIO服务,以前都是直接当基础设施来使用的,这次准备自己学一下S3兼容API相关的对象存储开发,因此有了这个小工具。目前仅包含上传功能,后续计划开发一个类似图床的对象存储应用。

循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(4) -- 实现DataGrid数据的导入和导出操作

在我们设计软件的很多地方,都看到需要对表格数据进行导入和导出的操作,主要是方便客户进行快速的数据处理和分享的功能,本篇随笔介绍基于WPF实现DataGrid数据的导入和导出操作。

一款.NET开源的i茅台自动预约小助手

前言 今天大姚给大家分享一款.NET开源、基于WPF实现的i茅台APP接口自动化每日自动预约(抢茅台)小助手:HyggeImaotai。 项目介绍 该项目通过接口自动化模拟i茅台APP实现每日自动预约茅台酒的功能,软件会在指定时间开始对管理的用户进行批量预约。 项目功能 用户管理 预约项目 店铺管理

WPF/C#:实现导航功能

前言 在WPF中使用导航功能可以使用Frame控件,这是比较基础的一种方法。前几天分享了wpfui中NavigationView的基本用法,但是如果真正在项目中使用起来,基础的用法是无法满足的。今天通过wpfui中的mvvm例子来说明在wpfui中如何通过依赖注入与MVVM模式使用导航功能。实践起来

【WPF】单例软件实现自重启

原文地址 https://www.cnblogs.com/younShieh/p/17749694.html ❤如果本文对你有所帮助,不妨点个关注和推荐呀,这是对笔者最大的支持~❤ 在WPF应用程序中,想要实现软件重启,可以再Start一次该软件的exe程序。 但是有些时候我们想要这个程序是唯一运行