Android精确测量文本宽高及基线位置的方法

前言

笔者最近在做一款弹幕控件,里面涉及到绘制文本,以及文本边框。而绘制文本边框需要知道文本的左边位置,上边位置,以及文本的宽高。

通常来说,使用 Canvas 绘制文本,可以通过画笔 Paint 来设置文字的大小。但是画笔的大小与文字的宽高并无直接关系。
大家应该能说上几种测量文字宽高的方法,如:

方案1. 通过 Paint 的 measureText 方法,可以测量文字的宽度

方案2. 通过获取 Paint 的 FontMetrics, 根据 FontMetrics 的 leading, ascent, 和 descent可以获取文字的高度。

方案3. 通过 Paint 的 getTextBounds 获取文本的边界矩形 Rect,根据 Rect 可以计算出文字的宽高。

方案4. 通过 Paint 获取文字的 Path, 根据 Path 获取文本的边界矩形 Rect, 根据 Rect 可以计算出文字的宽高。

表面上看,我们有以上四种方案可以获取文字的宽或高。但是不幸的,这四种方案里,有些方法获取到的数值不是真实的文字宽高。

我们通过以下测试代码,分别测试字母 "e" 和 "j"。

private void measureText(String str) {
 if (str == null) {
  return;
 }
 float width1 = mPaint.measureText(str);
 Log.i("lxc", "width1 ---> " + width1);

 Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
 float height1 = Math.abs(fontMetrics.leading + fontMetrics.ascent) + fontMetrics.descent;
 Log.i("lxc", "height1 ---> " + height1);

 Rect rect = new Rect();
 mPaint.getTextBounds(str, 0, str.length(), rect);
 float width2 = rect.width();
 float height2 = rect.height();
 Log.i("lxc", "width2 ---> " + width2);
 Log.i("lxc", "height2 ---> " + height2);

 Path textPath = new Path();
 mPaint.getTextPath(str, 0, str.length(), 0.0f, 0.0f, textPath);
 RectF boundsPath = new RectF();
 textPath.computeBounds(boundsPath, true);
 float width3 = boundsPath.width();
 float height3 = boundsPath.height();
 Log.i("lxc", "width3 ---> " + width3);
 Log.i("lxc", "height3 ---> " + height3);
}

调用以下代码测试

measureText("e");
Log.i("lxc", " <----分割线----> ");
measureText("j");

日志输出如下:

08-13 22:50:20.777 4977-4977/com.orzangleli.textbounddemo I/lxc: width1 ---> 21.0
08-13 22:50:20.777 4977-4977/com.orzangleli.textbounddemo I/lxc: height1 ---> 46.875
08-13 22:50:20.777 4977-4977/com.orzangleli.textbounddemo I/lxc: width2 ---> 18.0
08-13 22:50:20.778 4977-4977/com.orzangleli.textbounddemo I/lxc: height2 ---> 22.0
08-13 22:50:20.778 4977-4977/com.orzangleli.textbounddemo I/lxc: width3 ---> 17.929688
08-13 22:50:20.778 4977-4977/com.orzangleli.textbounddemo I/lxc: height3 ---> 21.914062
08-13 22:50:20.778 4977-4977/com.orzangleli.textbounddemo I/lxc:  <----分割线---->
08-13 22:50:20.778 4977-4977/com.orzangleli.textbounddemo I/lxc: width1 ---> 10.0
08-13 22:50:20.778 4977-4977/com.orzangleli.textbounddemo I/lxc: height1 ---> 46.875
08-13 22:50:20.778 4977-4977/com.orzangleli.textbounddemo I/lxc: width2 ---> 8.0
08-13 22:50:20.778 4977-4977/com.orzangleli.textbounddemo I/lxc: height2 ---> 37.0
08-13 22:50:20.778 4977-4977/com.orzangleli.textbounddemo I/lxc: width3 ---> 8.046875
08-13 22:50:20.778 4977-4977/com.orzangleli.textbounddemo I/lxc: height3 ---> 37.36328

首先,我们可以确定字母 "e" 和 "j" 的显示高度应该不一样,而使用第二种 FontMetrics 方案计算出的两种情况文字高度一样,而且从代码的调用上看,我们也是直接根据 Paint 获取的 FontMetrics, 与文字内容无关。所以我们需要测量文字真实高度的话,需要排除第二种方案了。

我们准备一个自定义 View,在 onDraw 方法中使用 mPaint 绘制一个文本 "e", 然后截图测量文本宽高,得出以下结果:

可以看到,文本的宽为 18, 高为 22。 可以得出以下结论:

方案1测量结果为近似值,存在一定误差。

方案3测量结果准确。

方案4测量结果精度更高,数值基本与方案3一致。

再多说几句。与测量文字高度类似,我们如何获取文字的基线 baseline 位置。

一般的博客上会告诉我们,如果需要计算文字的基线 baseline 位置,可以通过 FontMetrics 来计算。FontMetrics 基线上面的值为负数,基线下面的值为正数。baseline 计算公式为:

baseline = ascent + leading

如果你真的使用了这个公式就会发现坑。这个公式计算的基线位置实际上是默认字体的基线位置,与文字内容无关。我们可以看下面的例子:

在自定义 View 的 onDraw 方法中,绘制一个字符 "e", 绘制y坐标为 baseline,所以文字应该会顶着 Activity 的边界。

@Override
protected void onDraw(Canvas canvas) {
 super.onDraw(canvas);

 Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
 float baseline = Math.abs(fontMetrics.leading + fontMetrics.ascent);
 canvas.drawText("e", 0, baseline, mPaint);
}

显示结果为:

那问题来了,究竟怎么计算才能计算出真实的文本的基线位置呢。

我们使用之前的方案3来试试。代码如下:

@Override
protected void onDraw(Canvas canvas) {
 super.onDraw(canvas);
 String str = "e";
 Rect rect = new Rect();
 mPaint.getTextBounds(str, 0, str.length(), rect);
 float baseline = Math.abs(rect.top);
 canvas.drawText(str, 0, baseline, mPaint);
}

看看效果, 已经能够满足我们的需求,左上都顶着 Activity 显示了。

总结

精确测量文本宽高时,尽量不要使用 FontMetrics 去做。如果要求不精确,可以使用 Paint 的 measureText 方法计算文本宽度,如果要求精确测量,可以使用 Paint 的 getTextBounds 方法 或者 getTextPath 方法,获取文本的边界框矩形 Rect, 所获的Rect 的宽高即为文本的宽高, Rect的 top 为文本上边界距基线的距离, Rect 的 bottom 为文本下边距距离基线的距离。

本文涉及的代码可以在我的 GitHub 项目 AndroidBlogDemo github.com/hust2010107 (本地下载)… 。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Android View 测量流程(Measure)全面解析

    前言 上一篇文章,笔者主要讲述了DecorView以及ViewRootImpl相关的作用,这里回顾一下上一章所说的内容:DecorView是视图的顶级View,我们添加的布局文件是它的一个子布局,而ViewRootImpl则负责渲染视图,它调用了一个performTraveals方法使得ViewTree开始三大工作流程,然后使得View展现在我们面前.本篇文章主要内容是:详细讲述View的测量(Measure)流程,主要以源码的形式呈现,源码均取自Android API 21. 从ViewRoo

  • Android视图的绘制流程(上) View的测量

    综述 View的绘制流程可以分为三大步,它们分别是measure,layout和draw过程.measure表示View的测量过程,用于测量View的宽度和高度:layout用于确定View在父容器的位置:draw则是负责将View绘制到屏幕中.下面主要来看一下View的Measure过程. 测量过程 View的绘制流程是从ViewRoot的performTraversals方法开始的,ViewRoot对应ViewRootImpl类.ViewRoot在performTraversals中会调用p

  • Android View如何测量

    对于Android View的测量,我们一句话总结为:"给我位置和大小,我就知道您长到那里". 为了让大家更好的理解这个结论,我这里先讲一个日常生活中的小故事:不知道大家玩过"瞎子画画"的游戏没,一个人蒙上眼睛,拿笔去画板上画一些指定的图案,另外一个人则充当他的"眼睛",通过语言告诉他在画板那个位置画一个多大的图案.倘若,这个人不告诉那个蒙着眼睛的人,在那个画一个多大的图案.那么这个蒙着眼睛的人此时真是"河里赶大车----------没

  • Android测量每秒帧数Frames Per Second (FPS)的方法

    本文实例讲述了Android测量每秒帧数Frames Per Second (FPS)的方法.分享给大家供大家参考.具体如下: MainThread.java: package net.obviam.droidz; import java.text.DecimalFormat; import android.graphics.Canvas; import android.util.Log; import android.view.SurfaceHolder; /** * @author impa

  • Android精确测量文本宽高及基线位置的方法

    前言 笔者最近在做一款弹幕控件,里面涉及到绘制文本,以及文本边框.而绘制文本边框需要知道文本的左边位置,上边位置,以及文本的宽高. 通常来说,使用 Canvas 绘制文本,可以通过画笔 Paint 来设置文字的大小.但是画笔的大小与文字的宽高并无直接关系. 大家应该能说上几种测量文字宽高的方法,如: 方案1. 通过 Paint 的 measureText 方法,可以测量文字的宽度 方案2. 通过获取 Paint 的 FontMetrics, 根据 FontMetrics 的 leading, a

  • Android 获得屏幕宽高的三种方式

    老风格,废话不多说了,直接给大家贴android获取屏幕宽高的代码了. 主要代码: package com.km.screeninfo; import android.os.Bundle; import android.support.v.app.Fragment; import android.support.v.app.FragmentActivity; import android.util.DisplayMetrics; import android.view.LayoutInflate

  • 4种Android获取View宽高的方式

    有时我们会有基于这样的需求,当Activity创建时,需要获取某个View的宽高,然后进行相应的操作,但是我们在onCreate,onStart中获取View的大小,获取到的值都是0,只是由于View的绘制工程还未完成,和在onCreate中弹出Dialog或者PopupWindow会报一个Activity not running原理类似. 接下来就为大家介绍几种获取View宽高的方法: 第一种方式:重写Activity中的onWindowFocusChanged,当Activity获取到焦点的

  • Android简单记录和恢复ListView滚动位置的方法

    本文实例讲述了Android简单记录和恢复ListView滚动位置的方法.分享给大家供大家参考,具体如下: 有时候我们需要记录当前ListView滚动到的位置,重新加载的时候要回到原位,不罗嗦,给出代码: //列表滚动 private OnScrollListener ScrollLis = new OnScrollListener() { @Override public void onScroll(AbsListView arg0, int arg1, int arg2, int arg3)

  • Android 获得View宽高的几种方式总结

    <Android开发艺术探索>笔记: 在Activity的onCreate()或者onResume()中去获得View的高度的时候不能正确获得宽度和高度信息,这是因为 View的measure过程和Activity的生命周期不是同步执行的,因此无法保证Activity执行了onCreate onStart onResume时,某个View已经测量完毕了,如果还没有测量完,那么获得的宽高就是0.可以通过下面几种方式来获得: 1.onWindowFocusChanged onWindowFocus

  • android获取屏幕宽高与获取控件宽高(三种方法)

    1.获取屏幕宽高 方法1: int screenWidth = getWindowManager().getDefaultDisplay().getWidth(); // 屏幕宽(像素,如:480px) int screenHeight = getWindowManager().getDefaultDisplay().getHeight(); // 屏幕高(像素,如:800p) Log.e(TAG + " getDefaultDisplay", "screenWidth=&q

  • Android获取设备屏幕宽高pix值的方法

    本篇文章主要介绍了Android获取设备屏幕宽高pix值的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 方法一: WindowManager wm = (WindowManager) this .getSystemService(Context.WINDOW_SERVICE); int width = wm.getDefaultDisplay().getWidth(); int height = wm.getDefaultDisplay().getHeight

  • 如何在Vue中获取DOM元素的实际宽高

    目录 前言 一.获取元素 二.获取元素宽高 补充:vue项目获取dom元素宽高总是不准确 总结 前言 最近使用 D3.js 开发可视化图表,因为移动端做了 rem 适配,所以需要动态计算获取图表容器的宽高,其中涉及到一些原生DOM API的使用,避免遗忘这里总结一下. 一.获取元素 在 Vue 中可以使用 ref 来获取一个真实的 DOM 元素. 为了保险起见,所有的 DOM 操作建议都放在 $nextTick() 方法中. <template> <div class="box

  • js 获取图像缩放后的实际宽高,位置等信息

    项目中遇到图片实际显示尺寸的判定问题,图片可能被缩放过,所以实际显示的宽高无法通过常见的各种width,height拿到(都是得到图像的自然宽度或者字面宽度,并非实际显示结果) 在网上百度了一大圈也没有找到,今天早上偶然找到2个很方便的方法: object.getClientRects();和object.getBoundingClientRect(); 可以轻易获得当前元素的绝对位置(不含scrollLeft和scrollTop,需要用的话单独加上去)和显示出来的实际宽高 getClientR

  • Android获取手机屏幕宽高、状态栏高度以及字符串宽高信息的方法

    本文实例讲述了Android获取手机屏幕宽高.状态栏高度以及字符串宽高信息的方法.分享给大家供大家参考.具体如下: 首先定义TextView对象commentText 获取文字的宽高: TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); textPaint.setTextSize(commentText.getTextSize()); textPaint.setColor(Color.WHITE); FontMetrics fo

随机推荐