自定义View学习3:canvas绘制文字

1canvas绘制文字的方式

canvas的文字绘制方式有三种:drawText(),drawTextRun(),drawTextOnPath()。

drawText(String text,float x,float y,Paint paint)

drawText()是canvas 最基本的绘制方法:给出文字的内容和位置,Canvas按照要求去绘制文字,text是文字内容,x和y是文字的坐标,需要注意的是这个坐标并不是文字的左上角,而是一个与左下角比较接近的位置

drawTexRun()

drawTextRun() 是在 API 23 新加入的方法。它和 drawText() 一样都是绘制文字,但加入了两项额外的设置——上下文和文字方向——用于辅助一些文字结构比较特殊的语言的绘制。

drawTextonPath(String text,Path path,float hOffset,float vOffset,Paint paint);

沿着一条path路径来绘制文字。这个方法可以用来绘制竖向文字,斜向文字等等参数:hOffset 和 vOffset。它们是文字相对于 Path 的水平偏移量和竖直偏移量,利用它们可以调整文字的位置。例如你设置 hOffset 为 5, vOffset 为 10,文字就会右移 5 像素和下移 10 像素。

2.StaticLayout
staticLayouty也是一个使用canvas来绘制文字,不过它并不使用canvas的绘制方法。canvas.drawText只能绘制单行的文字,而且不能换行。
* 到了view的边缘,文字继续向后绘制到看不到的地方为不能自动换行
* 不能在换行符\n处换行
所以使用canvas.drawtext绘制文字,需要自己把文字切断后,分多次使用drawText()来绘制。
staticLayout并不是view或者viewGroup,而是android.text.Layout的自雷,他是纯粹用来绘制文字的,StaticLayout支持换行,它既支持可以为文字设置宽度上限来让文字自动换行,也会在\n出主动换行。

StaticLayout(CharSequence source, TextPaint paint, int width, Layout.Alignment align, float spacingmult, float spacingadd, boolean includepad)
  • width 是文字区域的宽度,文字到达这个宽度后就会自动换行;
  • align 是文字的对齐方向;
  • spacingmult 是行间距的倍数,通常情况下填 1 就好;
  • spacingadd 是行间距的额外增加值,通常情况下填 0 就好;
  • includeadd 是指是否在文字上下添加额外的空间,来避免某些过高的字符的绘制出现越界。
    如果需要进行多行文字的绘制,并且对文字的排列和样式没有太复杂的要求,使用staticLayout就可以。

Paint对于文字绘制的辅助
Paint 对文字绘制的辅助,有两类方法:设置显示效果的和测量文字尺寸的。

1.设置显示效果

  • setTextSize(float size);//设置text的大小
  • setTypeface(Typeface typeface)//设置字体的样式设置不同的 Typeface 就可以显示不同的字体。我们中国人谈到「字体」,比较熟悉的词是 font, typeface 和 font 是一个意思,都表示字体。 Typeface 这个类的具体用法,需要了解的话可以直接看文档,很简单。
  • setFakeBlodText(boolean fakeBoldText)//设置伪粗体,之所以叫伪粗体,是因为程序并不是通过weight设置的粗体,而是在程序运行时把文字给描粗了。
  • setStrikeThruText(boolean strikeThruText)//设置删除线
  • setUnderlineText(boolean underlineText)//设置下划线
  • setTextSkewX(float skewX)//设置文字的倾斜角度
  • setTextScaleX(float scaleX)//设置文字的横向缩放,也就是文字变胖变瘦
  • setLetterSpacing(float letterSpacing) //设置字符间距,默认是0

2.测试文字尺寸

不论是文字,还是图形或 Bitmap,只有知道了尺寸,才能更好地确定应该摆放的位置。由于文字的绘制和图形或 Bitmap 的绘制比起来,尺寸的计算复杂得多,所以它有一整套的方法来计算文字尺寸。

float getFontSpacing() 

获取推荐的行间距。即推荐的两行文字的 baseline 的距离。这个值是系统根据文字的字体和字号自动计算的。它的作用是当你要手动绘制多行文字(而不是使用 StaticLayout)的时候,可以在换行的时候给 y 坐标加上这个值来下移文字。

   canvas.drawText(texts[0], 100, 150, paint);
   canvas.drawText(texts[1], 100, 150 + paint.getFontSpacing, paint);
   canvas.drawText(texts[2], 100, 150 + paint.getFontSpacing * 2, paint);

FontMetircs getFontMetrics()

获取paint的fontMetircs。FontMetrics 是个相对专业的工具类,它提供了几个文字排印方面的数值:ascent, descent, top, bottom, leading。

这里写图片描述
如图,图中有两行文字,每一行都有 5 条线:top, ascent, baseline, descent, bottom。(leading 并没有画出来,因为画不出来,下面会给出解释)

  • baseline: 上图中黑色的线。前面已经讲过了,它的作用是作为文字显示的基准线。

  • ascent / descent: 上图中绿色和橙色的线,它们的作用是限制普通字符的顶部和底部范围。
    普通的字符,上不会高过 ascent ,下不会低过 descent ,例如上图中大部分的字形都显示在 ascent 和 descent 两条线的范围内。具体到 Android 的绘制中, ascent 的值是图中绿线和 baseline 的相对位移,它的值为负(因为它在 baseline 的上方); descent 的值是图中橙线和 baseline 相对位移,值为正(因为它在 baseline 的下方)。

  • top / bottom: 上图中蓝色和红色的线,它们的作用是限制所有字形( glyph )的顶部和底部范围。
    除了普通字符,有些字形的显示范围是会超过 ascent 和 descent 的,而 top 和 bottom 则限制的是所有字形的显示范围,包括这些特殊字形。例如上图的第二行文字里,就有两个泰文的字形分别超过了 ascent 和 descent 的限制,但它们都在 top 和 bottom 两条线的范围内。具体到 Android 的绘制中, top 的值是图中蓝线和 baseline的相对位移,它的值为负(因为它在 baseline 的上方);

  • bottom 的值是图中红线和 baseline 相对位移,值为正(因为它在 baseline 的下方)。

  • leading: 这个词在上图中没有标记出来,因为它并不是指的某条线和 baseline 的相对位移。 leading 指的是行的额外间距,即对于上下相邻的两行,上行的 bottom 线和下行的 top 线的距离,也就是上图中第一行的红线和第二行的蓝线的距离(对,就是那个小细缝)。
  • leading 这个词的本意其实并不是行的额外间距,而是行距,即两个相邻行的 baseline之间的距离。不过对于很多非专业领域,leading 的意思被改变了,被大家当做行的额外间距来用;而 Android 里的 leading ,同样也是行的额外间距的意思。
    另外,leading 在这里应该读作 “ledding” 而不是 “leeding” 哦。原因就不说了,我这越扯越远没边了。
    FontMetrics 提供的就是 Paint 根据当前字体和字号,得出的这些值的推荐值。它把这些值以变量的形式存储,供开发者需要时使用。

    • FontMetrics.ascent:float 类型。
    • FontMetrics.descent:float 类型。
    • FontMetrics.top:float 类型。
    • FontMetrics.bottom:float 类型。
    • FontMetrics.leading:float 类型。
      另外,ascent 和 descent 这两个值还可以通过 Paint.ascent() 和 Paint.descent() 来快捷获取。
      FontMetrics 和 getFontSpacing():
      从定义可以看出,上图中两行文字的 font spacing (即相邻两行的 baseline 的距离) 可以通过 bottom - top + leading (top 的值为负,前面刚说过,记得吧?)来计算得出。
      但你真的运行一下会发现, bottom - top + leading 的结果是要大于 getFontSpacing() 的返回值的。
      两个方法计算得出的 font spacing 竟然不一样?
      这并不是 bug,而是因为 getFontSpacing() 的结果并不是通过 FontMetrics 的标准值计算出来的,而是另外计算出来的一个值,它能够做到在两行文字不显得拥挤的前提下缩短行距,以此来得到更好的显示效果。所以如果你要对文字手动换行绘制,多数时候应该选取 getFontSpacing() 来得到行距,不但使用更简单,显示效果也会更好。
      getFontMetrics() 的返回值是 FontMetrics 类型。它还有一个重载方法 getFontMetrics(FontMetrics fontMetrics) ,计算结果会直接填进传入的 FontMetrics 对象,而不是重新创建一个对象。这种用法在需要频繁获取 FontMetrics 的时候性能会好些。
      另外,这两个方法还有一对同样结构的对应的方法 getFontMetricsInt() 和 getFontMetricsInt(FontMetricsInt fontMetrics) ,用于获取 FontMetricsInt 类型的结果。
  • getTextBounds(String text, int start, int end, Rect bounds) //获取文字的显示范围。参数里,text 是要测量的文字,start 和 end 分别是文字的起始和结束位置,bounds 是存储文字显示范围的对象,方法在测算完成之后会把结果写进 bounds。

    paint.setStyle(Paint.Style.FILL);
    canvas.drawText(text, offsetX, offsetY, paint);
    paint.getTextBounds(text, 0, text.length(), bounds);
    bounds.left += offsetX;
    bounds.top += offsetY;
    bounds.right += offsetX;
    bounds.bottom += offsetY;
    paint.setStyle(Paint.Style.STROKE);
    canvas.drawRect(bounds, paint);
    
  • float measureText(String text) //测量文字的宽度并返回。

    canvas.drawText(text, offsetX, offsetY, paint);
    float textWidth = paint.measureText(text);
    canvas.drawLine(offsetX, offsetY, offsetX + textWidth, offsetY, paint);
    

    如下图:
    这里写图片描述

咦,前面有了 getTextBounds(),这里怎么又有一个 measureText()?
如果你用代码分别使用 getTextBounds() 和 measureText() 来测量文字的宽度,你会发现 measureText() 测出来的宽度总是比 getTextBounds() 大一点点。这是因为这两个方法其实测量的是两个不一样的东西。

  • getTextBounds: 它测量的是文字的显示范围(关键词:显示)。形象点来说,你这段文字外放置一个可变的矩形,然后把矩形尽可能地缩小,一直小到这个矩形恰好紧紧包裹住文字,那么这个矩形的范围,就是这段文字的 bounds。

  • measureText(): 它测量的是文字绘制时所占用的宽度(关键词:占用)。前面已经讲过,一个文字在界面中,往往需要占用比他的实际显示宽度更多一点的宽度,以此来让文字和文字之间保留一些间距,不会显得过于拥挤。上面的这幅图,我并没有设置 setLetterSpacing() ,这里的 letter spacing 是默认值 0,但你可以看到,图中每两个字母之间都是有空隙的。另外,下方那条用于表示文字宽度的横线,在左边超出了第一个字母 H 一段距离的,在右边也超出了最后一个字母 r(虽然右边这里用肉眼不太容易分辨),而就是两边的这两个「超出」,导致了 measureText() 比 getTextBounds() 测量出的宽度要大一些。

  • 在实际的开发中,测量宽度要用 measureText() 还是 getTextBounds() ,需要根据情况而定。不过你只要掌握了上面我所说的它们的本质,在选择的时候就不会为难和疑惑了。
  • getTextWidths(String text, float[] widths) // 获取字符串中每个字符的宽度,并把结果填入参数 widths。这相当于 measureText() 的一个快捷方法,它的计算等价于对字符串中的每个字符分别调用 measureText() ,并把它们的计算结果分别填入 widths 的不同元素。
  • int breakText(String text, boolean measureForwards, float maxWidth, float[] measuredWidth)//这个方法也是用来测量文字宽度的。但和 measureText() 的区别是, breakText() 是在给出宽度上限的前提下测量文字的宽度。如果文字的宽度超出了上限,那么在临近超限的位置截断文字。
    学习来源:http://hencoder.com/ui-1-3/

猜你喜欢

转载自blog.csdn.net/ibelieveyouwxy/article/details/80399320