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