理解TextView三部曲概览

215 阅读5分钟

最近项目中用到一个StrokeTextView的组件,主要是给文本内容添加描边的效果,从开始可以“描边”满足需求,到最后一步步优化成一个成熟的控件,StrokeTextView就像从一块地基变成了我想要的一栋大别墅,真是有辛酸有喜悦。这一次的优化,也是让我对TextView的绘制有了更加深刻的理解,这里总结成三部曲来记录完整的从开发到优化的过程,相信读者看完后也会对TextView的绘制有了更进一步的理解和把握。

先来看看三部曲所分别要达到的效果:

三部曲(一):

width20_height=Wrapcontent_gravity=Center

上面这张是当width = 20dp,height = wrap_content的效果图。

下面的是当width = match_parent,height = match_parent时,不同gravity的效果图:

same_width_height1

可以看到,只要不设置padding,StrokeTextView都能描边正常

当width=20dp时效果能看,但是配置有Padding的时候,就变成了下面这样

width20_height=Wrapcontent_paddingRight=15dp_paddingBottom=10dp

此时只是添加了paddingRight=15dp,paddingBottom=10dp的属性,结果就成了这样。

显然stroke描边的时候,并没有考虑padding,同时在width=20dp的情况下(设置的宽度不够实际显示的情况下),设置paddingRight=15dp也会导致文字显示有问题的情况。

三部曲(二):

width50_height=Wrapcontent_paddingRight=15dp_paddingBottom=10dp

第二篇在第一篇的基础上,添加了对Padding的计算,使StrokeTextView能够支持padding来正确描边。

不过当width=wrap_content(设置的宽度不够实际显示的情况)的时候,描的边会被遮住,不够显示,如下图:

width=wrapCotnent_height=wrapContent_paddingBottom=10dp

  可以看到右边的描边就被遮住了。

而当width=1dp的时候,直接整个都几乎不显示了(这也是TextView的默认实现,部曲三就要改变这种默认实现,让我们的StrokeTextView无论什么情况都能显示),效果如图:

width=1dp_height=wrapContent_paddingBottom=10dp

三部曲(三):

same_gravity

上图就是我们部曲三最终要实现的效果

StrokeTextView不仅支持padding,同时在width = 1 或 height = 1的情况、以及width=wrap_content && height=wrap_content的情况下,都能正确的描边,并且完美的展示(左、右描边区域没有被遮住的情况)

上面就是我完成优化的整个过程以及各阶段优化的成果,总体看起来效果还是不错的,那么事不宜迟,接下来就一步步来实现我们最终的效果。

本篇虽然为效果的概览,但是因为三部曲所做的优化,都是建立在了解了安卓文本绘制机制的基础之上。

所以我决定在本篇概览这里,先把最基本安卓的文本绘制机制介绍了,同学们完成了这部分内容的阅读之后再看后面的三部曲,相信会容易理解很多。

Android的文本绘制

小时候我们写英语单词的时候,英语老师就有教过我们,英语本上一行有几根不同颜色的线,单词要对齐着这几根线来写才写的好看~

代码思想都是源于生活的,那么在安卓中的一段文本,也有几根线需要对齐。

text_lines

可以看到,和现实生活中差不多,安卓的文本绘制同样有5条线来规范。

红色的Baseline是基准线,用来确定文本的y坐标,紫色的Top是文字的最顶部,橙色的Bottom是文字的底部。

这些值如何获取呢?

get_lines

这里为了演示,令baseline为0,实际开发中baseline的位置由Gravtiy确定

以baseline基准线为参照线,在baseline上面的线(ascent, top)的y值为负,在其下面的线(decent, bottom)的值为正。

那么要获取一段文本的高度,就应该是bottom - top

那么文本的y坐标参照线由baseline确定,那么x坐标的参照线又是由什么确定呢?

文本是有对齐方式的,可由 Paint.setTextAlign() 确定

看下面这段代码

if (mLinePaint == null) {
    mLinePaint = new Paint();
    mLinePaint.setStrokeWidth(2);
    mLinePaint.setStyle(Paint.Style.FILL);
}

if (mTextPaint == null)
    mTextPaint = getPaint();

String text = "理解TextView三部曲";

// ------------设置TextAlign ------------
mTextPaint.setTextAlign(Paint.Align.LEFT);

// 在(400, 400)位置画文本
canvas.drawText(text, 400, 400, mTextPaint);

// 标记TextView落笔点
mLinePaint.setColor(Color.RED);
canvas.drawLine(0, 400, getWidth(), 400, mLinePaint);
mLinePaint.setColor(Color.BLUE);
canvas.drawLine(400, 0, 400, getHeight(), mLinePaint);

super.onDraw(canvas);

运行,当textAlign = Paint.Align.LEFT (默认值)时

textalign=start

textAlign = Paint.Algin.RIGHT

textalign=right

textAlign = Paint.Algin.CENTER

textalign=center

虽然,我们的落笔点一直都是(400, 400),但是由于TextAlign的不同,文本绘制的区域也就不同,那么x坐标的参照线也就因TextAlign而异。

总结一下就是:

  1. Paint.Algin.CENTER:文本将以落笔点x坐标为中心绘制
  2. Paint.Algin.LEFT:文本将在落笔点x坐标右边绘制
  3. Paint.Align.RIGHT:文本将在落笔点x坐标左边绘制

由baseline基准线来确定落笔点的y坐标。

一般我们自定义TextView时,都是通过canvas.drawText(String text, float x, float y, Paint paint)这个方法来绘制文本,传入的x,y就指定了文本绘制的落笔点,当然还受TextAlign影响。

不过我们接下来的优化都是使用的TextAlign的默认值,也就是Paint.Algin.LEFT来进行,所以可以暂且忽略这一影响。

更新传送门

理解TextView三部曲(一):TextView的文本绘制过程

理解TextView三部曲(二):支持Padding的StrokeTextView

理解TextView三部曲(三):倔强的StrokeTextView(我无论如何都要展示出来!)

理解TextView三部曲之番外篇:或许这会是最终的进化

项目源码地址

兄dei,如果觉得我写的还不错,麻烦帮个忙呗 :-)

  1. 给俺点个赞被,激励激励我,同时也能让这篇文章让更多人看见,(#^.^#)
  2. 不用点收藏,诶别点啊,你怎么点了?这多不好意思!
  3. 噢!还有,我维护了一个路由库。。没别的意思,就是提一下,我维护了一个路由库 =.= !!

拜托拜托,谢谢各位同学!