WPF 文本动画 文字BaseLine 字体 行高计算说明

1.摘要

在wpf里,显示文字我们一般用textblock或者label控件,而有时候需要显示一些文本滚动效果,比如:Led屏幕,文字自动滚屏。

这时候如果是wpf客户端的话,用textblock的时候文字绘制的效率很慢,且画面会卡顿现象,为了解决这个问题,本文将对文本显示及文本动画进行优化,达到流畅效果。

2.自定义简单文本控件

普通动画,如doubleAnimation或者使用计时器然后改变textblock的offsetx,这两种方式本质是定时刷新,而每次wpf的textblock的位置改变,都会出发render事件,render事件内部进行了一些列操作,这些操作比如字体计算,字体布局等很消耗ui性能,因此造成了卡顿现象。所以,我们自定义简单的text显示控件,来替代复杂的原生文本控件。

为了显示我们需要继承UIElement类,但是我们还要控制文本的移动和位置(因为本文是通过rendertransform控制的),所以基类我们选择了frameworkElement,如下图

public class RenderedTextControl : FrameworkElement

控件有了后,想要显示,需要实现两个重写,即:

  •  protected override void OnRender(DrawingContext drawingContext)
  • protected override Size MeasureOverride(Size constraint)

顾名思义,OnRender里面需要对DrawingContext进行操作,进行绘制。MeasureOverride是计算控件的大小。

在绘制文本的时候,为了能够精确控制文本位置,我们以文本中间点为文本基准点。因为显示文本的时候大部分是居中显示的。

这里就需要提一下wpf中的文本几个点了:FontSize、FontFamily.Baseline、FontFamily.LineSpacing。如图

 

 

 

  • 默认文本是基线左下角点为0点。
  • f到g之间表示文本的显示区域大小,比如等于一个grid或一个window的高度
  • 正常文本大小:ad,(wpf是点坐标,计算文本大小时候不直接用FontSize)
  • 文本的基线BaseLine:ac=FongSize*BaseLine,distance between the baseline and the character cell top.(https://docs.microsoft.com/en-us/dotnet/api/system.windows.media.fontfamily.baseline?view=netframework-4.6.1)
  • 文本的线间隔lineSpacing:ab+cd=FontSize*LineSpacing
  • 文本中线:e

因此为了将文本的初始坐标点(左下角,即c的位置)移动到中间,需要将文本向上移动ec的距离(中线到基线的距离),之后再移动到fg之间(居中)即可。

计算文本偏移量代码如下,:

 private Point GetTextLocation()
        {
            //基线距离=baseline
            var baseline = _formattedText.Baseline;

            //基线剧中,移动量1=-(x-0.5*行高)
            var offset1 = -(baseline - 0.5 * LineHeight);
            //中线 到 基线的距离=移动量2=字体大小*字体基线-0.5*字体大小*字体行距
            var offset2 = FontSize * _typeface.FontFamily.Baseline -
                          0.5 * FontSize * _typeface.FontFamily.LineSpacing;
            //总移动量=移动量1+移动量2
            var textLocation = new Point(0, offset1 + offset2);
            textLocation.X += DrawOffsetX;
            textLocation.Y += DrawOffsetY;
            return textLocation;
        }

 

有了偏移量,绘制代码就简单了:

protected override void OnRender(DrawingContext drawingContext)
        {
            if (!string.IsNullOrEmpty(Text))
            {
                var textLocation = GetTextLocation();
                drawingContext.DrawText(_formattedText, textLocation);
                //   InvalidateMeasure();
            }
        }

计算控件大小代码也很简单

protected override Size MeasureOverride(Size constraint)
        {
            if (_formattedText != null)
            {
                var wid = _formattedText.WidthIncludingTrailingWhitespace;

                return new Size(wid, _formattedText.Height);
            }

            return new Size();
        }

 

3.帧动画

  • 使用CompositionTarget.Rendering += CompositionTarget_Rendering;,此方法可以在界面更新之前进行处理文本,这样就保证了文本的刷新跟ui刷新的同步问题,经测试刷新率能达到60帧以上。
  • 为了形成一个完整的动画(比如只有一行字),我们需要将文本乘以2(这样就有了两行字,能形成首和尾)
  • 为了取得不足一段文字,我们将文字跟整行长度取余数。
  • 动画连续,需要在端点进行移动量重置
  • 动画停顿,需要在时间上进行判断
  • 整个文本,放在List<string>格式里面处理,便于添加及查找(如一行数据代表一项或者一组数据代表一项,多个项就是列表)

4.效果及下载源码

效果图如下:

 

 源码地址:https://files.cnblogs.com/files/lizhijian/2020-9-7-TextPlayer.rar

 

posted @ 2020-09-07 22:53  灰主流  阅读(593)  评论(0编辑  收藏  举报