Android中获取文本宽度和高度

本文罗列Android中字体宽度和高度的相关概念,及测量方法 。

原文请参考Android_FontMetrics、Android字符串进阶之三:字体属性及测量(FontMetrics)、 Android UI 之居中绘制文本内容的正确方法——实现自定义一个TextView。

我们在自定义一个控件的时候,有时候会需要自己来绘制一些文本内容,这样就自然而然遇到确定文本的宽高尺寸和方位的问题,事实上明确了控件和文本的宽高,就可以根据需要确定文本的方位是居中、居上还是左上等。

Canvas 绘制文本时,使用FontMetrics对象,计算文本位置的坐标。


     
     
     
     
  1. public static class FontMetrics {
  2. /**
  3. * The maximum distance above the baseline for the tallest glyph in
  4. * the font at a given text size.
  5. */
  6. public float top;
  7. /**
  8. * The recommended distance above the baseline for singled spaced text.
  9. */
  10. public float ascent;
  11. /**
  12. * The recommended distance below the baseline for singled spaced text.
  13. */
  14. public float descent;
  15. /**
  16. * The maximum distance below the baseline for the lowest glyph in
  17. * the font at a given text size.
  18. */
  19. public float bottom;
  20. /**
  21. * The recommended additional space to add between lines of text.
  22. */
  23. public float leading;
  24. }
说明如下:
1. 基准点是baseline
2. Ascent是baseline之上至字符最高处的距离
3. Descent是baseline之下至字符最低处的距离
4. Leading文档说的很含糊,其实是上一行字符的descent到下一行的ascent之间的距离
5. Top指的是指的是最高字符到baseline的值,即ascent的最大值
6. 同上,bottom指的是最下字符到baseline的值,即descent的最大值

descent-ascent就可以看作文本的高度。
图示如下:

Paint类有两个方法,也可以获取文本的高度:


     
     
     
     
  1. /**
  2. * Return the distance above (negative) the baseline (ascent) based on the
  3. * current typeface and text size.
  4. *
  5. * @return the distance above (negative) the baseline (ascent) based on the
  6. * current typeface and text size.
  7. */
  8. public native float ascent();
  9. /**
  10. * Return the distance below (positive) the baseline (descent) based on the
  11. * current typeface and text size.
  12. *
  13. * @return the distance below (positive) the baseline (descent) based on
  14. * the current typeface and text size.
  15. */
  16. public native float descent();
ascent():the distance above the baseline(baseline以上的height)
descent():the distance below the baseline(baseline以下的height)
所以descent()-ascent()也可以看成文字的height。

上面两种方法得到的文字高度是一致的,但从本人经验来说,这种高度对数字来说略高,比如在画折线图上的坐标轴值时就有很明显的体现,这种方式drawText()在Y轴上的值看上去比其值略偏下。

这种情况下,下面这种方式计算出来的文字宽高更准确一些。


     
     
     
     
  1. Paint pFont = new Paint();
  2. Rect rect = new Rect();
  3. pFont.getTextBounds( "豆", 0, 1, rect);
  4. Log.v(TAG, "height:"+rect.height()+ "width:"+rect.width());
下面给出一个综合Demo演示:

     
     
     
     
  1. package com.example.textmeasure;
  2. import android.app.Activity;
  3. import android.graphics.Paint;
  4. import android.graphics.Rect;
  5. import android.graphics.Paint.FontMetrics;
  6. import android.os.Bundle;
  7. import android.util.DisplayMetrics;
  8. import android.util.Log;
  9. public class MainActivity extends Activity {
  10. Paint mPaint= null;
  11. public float screenDensity;
  12. public float screenScaledDensity;
  13. public float textHeight1;
  14. public float textHeight2;
  15. public float textHeight3;
  16. public float textWidthA;
  17. public float textWidthB;
  18. @Override
  19. protected void onCreate(Bundle savedInstanceState) {
  20. super.onCreate(savedInstanceState);
  21. setContentView(R.layout.activity_main);
  22. //获取屏幕密度和字体适用的密度
  23. DisplayMetrics dm = getResources().getDisplayMetrics();
  24. screenDensity = dm.density;
  25. screenScaledDensity = dm.scaledDensity;
  26. mPaint= new Paint();
  27. mPaint.setTextSize( 15*screenScaledDensity);
  28. //第一种获取文本高度的方式
  29. FontMetrics fm=mPaint.getFontMetrics();
  30. textHeight1=fm.descent-fm.ascent;
  31. //第二种获取文本高度的方式
  32. textHeight2=mPaint.descent()-mPaint.ascent();
  33. //第三种获取文本高度的方式
  34. Rect bounds= new Rect();
  35. mPaint.getTextBounds( "0.00", 0, "0.00".length(), bounds);
  36. textHeight3=bounds.height();
  37. //获取文本宽度
  38. textWidthA=bounds.width();
  39. //获取文本宽度的另一种方式
  40. textWidthB=mPaint.measureText( "0.00");
  41. Log.i( "publish", "textHeight1: "+textHeight1);
  42. Log.i( "publish", "textHeight2: "+textHeight2);
  43. Log.i( "publish", "textHeight3: "+textHeight3);
  44. Log.i( "publish", "textWidthA: "+textWidthA);
  45. Log.i( "publish", "textWidthB: "+textWidthB);
  46. }
  47. }
打印结果:

     
     
     
     
  1. 03- 23 12: 54: 16.325: I/publish( 27219): screenDensity: 3.0
  2. 03- 23 12: 54: 16.325: I/publish( 27219): screenScaledDensity: 3.0
  3. 03- 23 12: 54: 16.325: I/publish( 27219): textHeight1: 52.734375
  4. 03- 23 12: 54: 16.325: I/publish( 27219): textHeight2: 52.734375
  5. 03- 23 12: 54: 16.325: I/publish( 27219): textHeight3: 32.0
  6. 03- 23 12: 54: 16.325: I/publish( 27219): textWidthA: 82.0
  7. 03- 23 12: 54: 16.325: I/publish( 27219): textWidthB: 87.0
上面的Demo中使用到屏幕密度,代码中出现的所有尺寸单位都是像素,因此需要使用屏幕密度进行适配,其中dm.density是与控件尺寸相关的密度,dm.scaledDensity是与字体大小相关的密度。

在Android layout文件中使用sp,dp尺寸已经在一定程度上进行了适配。

下面说一下如何在一个自定义View中drawText()时设置文本居中,这部分内容转载于http://blog.csdn.net/carrey1989/article/details/10399727。

我们在画布中绘制文本的时候,会调用Canvas.drawText(String text, float x, float y, Paint paint)这个方法,其中y的坐标就是上图中baseline的y坐标,x坐标是文本绘制的起始x轴坐标。

因此要想文本居中,应如下计算:
float textCenterVerticalBaselineY = viewHeight / 2 - fm.descent + (fm.bottom - fm.top) / 2;
其中,textCenterVerticalBaselineY就是绘制文本时候的y坐标,viewHeight是控件的高度。这个换算关系不难理解,viewHeight/2-fm.descent的意思是将整个文字区域抬高到控件的1/2,然后我们再加上(fm.bottom - fm.top) / 2的意思就是将文本下沉文本top到bottom长度的一半,从而实现文本垂直居中的目的。 
有的人或许会问,为什么最后加上的是bottom到top距离的一半而不是descent到ascent的一半呢?其实这个是我测试的结果,我发现如果用bottom到top距离的一半来设置文本垂直居中,和系统控件TextView的文本居中效果是一样的,我们来看下面的效果: 
首先是使用(fm.bottom - fm.top) / 2的:


然后是使用然后是使用(fm.descent - fm.ascent) / 2: 

左边绿色的是系统的TextView文字居中效果,右边是我们自定义控件的文字居中效果,可以看出使用(fm.bottom - fm.top) / 2与TextView的效果是一样的,当然,我们不必一定要与TextView的效果相同,所以使用(fm.descent - fm.ascent) / 2也是可以的。

下面自定义一个可以控制内部文本绘制方位的TextView:


     
     
     
     
  1. //项目代码:
  2. //MyTextView.java
  3. package com.example.textalignment.mytextview;
  4. import com.example.textalignment.util.DisplayParams;
  5. import com.example.textalignment.util.DisplayUtil;
  6. import android.content.Context;
  7. import android.graphics.Bitmap;
  8. import android.graphics.Canvas;
  9. import android.graphics.Color;
  10. import android.graphics.Matrix;
  11. import android.graphics.Paint;
  12. import android.graphics.RectF;
  13. import android.graphics.Paint.Align;
  14. import android.graphics.Paint.FontMetrics;
  15. import android.graphics.drawable.Drawable;
  16. import android.util.AttributeSet;
  17. import android.util.DisplayMetrics;
  18. import android.view.View;
  19. /**
  20. * 自定义文本显示控件
  21. * 该自定义控件中的文本可以在9个方位进行控制
  22. * 左上——中上——右上
  23. * 左中——中中——右中
  24. * 左下——中下——右下
  25. * @author carrey
  26. *
  27. */
  28. public class MyTextView extends View {
  29. /** 要显示的文字 */
  30. private String text;
  31. /** 文字的颜色 */
  32. private int textColor;
  33. /** 文字的大小 */
  34. private int textSize;
  35. /** 文字的方位 */
  36. private int textAlign;
  37. // public static final int TEXT_ALIGN_CENTER = 0x00000000;
  38. public static final int TEXT_ALIGN_LEFT = 0x00000001;
  39. public static final int TEXT_ALIGN_RIGHT = 0x00000010;
  40. public static final int TEXT_ALIGN_CENTER_VERTICAL = 0x00000100;
  41. public static final int TEXT_ALIGN_CENTER_HORIZONTAL = 0x00001000;
  42. public static final int TEXT_ALIGN_TOP = 0x00010000;
  43. public static final int TEXT_ALIGN_BOTTOM = 0x00100000;
  44. /** 文本中轴线X坐标 */
  45. private float textCenterX;
  46. /** 文本baseline线Y坐标 */
  47. private float textBaselineY;
  48. /** 控件的宽度 */
  49. private int viewWidth;
  50. /** 控件的高度 */
  51. private int viewHeight;
  52. /** 控件画笔 */
  53. private Paint paint;
  54. private FontMetrics fm;
  55. /** 场景 */
  56. private Context context;
  57. public MyTextView(Context context) {
  58. super(context);
  59. this.context = context;
  60. init();
  61. }
  62. public MyTextView(Context context, AttributeSet attrs) {
  63. super(context, attrs);
  64. this.context = context;
  65. init();
  66. }
  67. /**
  68. * 变量初始化
  69. */
  70. private void init() {
  71. paint = new Paint();
  72. paint.setAntiAlias( true);
  73. paint.setTextAlign(Align.CENTER);
  74. //默认情况下文字居中显示
  75. textAlign = TEXT_ALIGN_CENTER_HORIZONTAL | TEXT_ALIGN_CENTER_VERTICAL;
  76. //默认的文本颜色是黑色
  77. this.textColor = Color.BLACK;
  78. }
  79. @Override
  80. protected void onLayout(boolean changed, int left, int top, int right,
  81. int bottom) {
  82. viewWidth = getWidth();
  83. viewHeight = getHeight();
  84. super.onLayout(changed, left, top, right, bottom);
  85. }
  86. @Override
  87. protected void onDraw(Canvas canvas) {
  88. //绘制控件内容
  89. setTextLocation();
  90. canvas.drawText(text, textCenterX, textBaselineY, paint);
  91. super.onDraw(canvas);
  92. }
  93. /**
  94. * 定位文本绘制的位置
  95. */
  96. private void setTextLocation() {
  97. paint.setTextSize(textSize);
  98. paint.setColor(textColor);
  99. fm = paint.getFontMetrics();
  100. //文本的宽度
  101. float textWidth = paint.measureText(text);
  102. float textCenterVerticalBaselineY = viewHeight / 2 - fm.descent + (fm.descent - fm.ascent) / 2;
  103. switch (textAlign) {
  104. case TEXT_ALIGN_CENTER_HORIZONTAL | TEXT_ALIGN_CENTER_VERTICAL:
  105. textCenterX = ( float)viewWidth / 2;
  106. textBaselineY = textCenterVerticalBaselineY;
  107. break;
  108. case TEXT_ALIGN_LEFT | TEXT_ALIGN_CENTER_VERTICAL:
  109. textCenterX = textWidth / 2;
  110. textBaselineY = textCenterVerticalBaselineY;
  111. break;
  112. case TEXT_ALIGN_RIGHT | TEXT_ALIGN_CENTER_VERTICAL:
  113. textCenterX = viewWidth - textWidth / 2;
  114. textBaselineY = textCenterVerticalBaselineY;
  115. break;
  116. case TEXT_ALIGN_BOTTOM | TEXT_ALIGN_CENTER_HORIZONTAL:
  117. textCenterX = viewWidth / 2;
  118. textBaselineY = viewHeight - fm.bottom;
  119. break;
  120. case TEXT_ALIGN_TOP | TEXT_ALIGN_CENTER_HORIZONTAL:
  121. textCenterX = viewWidth / 2;
  122. textBaselineY = -fm.ascent;
  123. break;
  124. case TEXT_ALIGN_TOP | TEXT_ALIGN_LEFT:
  125. textCenterX = textWidth / 2;
  126. textBaselineY = -fm.ascent;
  127. break;
  128. case TEXT_ALIGN_BOTTOM | TEXT_ALIGN_LEFT:
  129. textCenterX = textWidth / 2;
  130. textBaselineY = viewHeight - fm.bottom;
  131. break;
  132. case TEXT_ALIGN_TOP | TEXT_ALIGN_RIGHT:
  133. textCenterX = viewWidth - textWidth / 2;
  134. textBaselineY = -fm.ascent;
  135. break;
  136. case TEXT_ALIGN_BOTTOM | TEXT_ALIGN_RIGHT:
  137. textCenterX = viewWidth - textWidth / 2;
  138. textBaselineY = viewHeight - fm.bottom;
  139. break;
  140. }
  141. }
  142. /**
  143. * 设置文本内容
  144. * @param text
  145. */
  146. public void setText(String text) {
  147. this.text = text;
  148. invalidate();
  149. }
  150. /**
  151. * 设置文本大小
  152. * @param textSizeSp 文本大小,单位是sp
  153. */
  154. public void setTextSize(int textSizeSp) {
  155. DisplayParams displayParams = DisplayParams.getInstance(context);
  156. this.textSize = DisplayUtil.sp2px(textSizeSp, displayParams.fontScale);
  157. invalidate();
  158. }
  159. /**
  160. * 设置文本的方位
  161. */
  162. public void setTextAlign(int textAlign) {
  163. this.textAlign = textAlign;
  164. invalidate();
  165. }
  166. /**
  167. * 设置文本的颜色
  168. * @param textColor
  169. */
  170. public void setTextColor(int textColor) {
  171. this.textColor = textColor;
  172. invalidate();
  173. }
  174. }

     
     
     
     
  1. //MainActivity.java
  2. package com.example.textalignment;
  3. import com.example.textalignment.mytextview.MyTextView;
  4. import com.example.textalignment.util.DisplayParams;
  5. import com.example.textalignment.util.DisplayUtil;
  6. import android.os.Bundle;
  7. import android.app.Activity;
  8. import android.graphics.Bitmap;
  9. import android.graphics.BitmapFactory;
  10. import android.graphics.Color;
  11. import android.view.Menu;
  12. import android.widget.LinearLayout;
  13. import android.widget.ScrollView;
  14. public class MainActivity extends Activity {
  15. @Override
  16. protected void onCreate(Bundle savedInstanceState) {
  17. super.onCreate(savedInstanceState);
  18. setContentView(R.layout.activity_main);
  19. DisplayParams displayParams = DisplayParams.getInstance( this);
  20. LinearLayout container = (LinearLayout) findViewById(R.id.container);
  21. MyTextView myTextView1 = new MyTextView( this);
  22. myTextView1.setText( "居中的文本");
  23. myTextView1.setTextSize( 30);
  24. myTextView1.setTextAlign(MyTextView.TEXT_ALIGN_CENTER_HORIZONTAL | MyTextView.TEXT_ALIGN_CENTER_VERTICAL);
  25. myTextView1.setTextColor(Color.BLUE);
  26. myTextView1.setBackgroundColor(Color.RED);
  27. container.addView(myTextView1, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px( 150, displayParams.scale));
  28. MyTextView myTextView2 = new MyTextView( this);
  29. myTextView2.setText( "居左的文本");
  30. myTextView2.setTextSize( 25);
  31. myTextView2.setTextAlign(MyTextView.TEXT_ALIGN_CENTER_VERTICAL | MyTextView.TEXT_ALIGN_LEFT);
  32. myTextView2.setTextColor(Color.GREEN);
  33. myTextView2.setBackgroundColor(Color.YELLOW);
  34. container.addView(myTextView2, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px( 150, displayParams.scale));
  35. MyTextView myTextView3 = new MyTextView( this);
  36. myTextView3.setText( "右下的文本");
  37. myTextView3.setTextSize( 15);
  38. myTextView3.setTextAlign(MyTextView.TEXT_ALIGN_BOTTOM | MyTextView.TEXT_ALIGN_RIGHT);
  39. myTextView3.setTextColor(Color.RED);
  40. myTextView3.setBackgroundColor(Color.BLUE);
  41. container.addView(myTextView3, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px( 150, displayParams.scale));
  42. MyTextView myTextView4 = new MyTextView( this);
  43. myTextView4.setText( "左下的文本");
  44. myTextView4.setTextSize( 15);
  45. myTextView4.setTextAlign(MyTextView.TEXT_ALIGN_BOTTOM | MyTextView.TEXT_ALIGN_LEFT);
  46. myTextView4.setTextColor(Color.YELLOW);
  47. myTextView4.setBackgroundColor(Color.GREEN);
  48. container.addView(myTextView4, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px( 150, displayParams.scale));
  49. MyTextView myTextView5 = new MyTextView( this);
  50. myTextView5.setText( "中下的文本");
  51. myTextView5.setTextSize( 35);
  52. myTextView5.setTextAlign(MyTextView.TEXT_ALIGN_BOTTOM | MyTextView.TEXT_ALIGN_CENTER_HORIZONTAL);
  53. myTextView5.setTextColor(Color.GRAY);
  54. myTextView5.setBackgroundColor(Color.RED);
  55. container.addView(myTextView5, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px( 150, displayParams.scale));
  56. MyTextView myTextView6 = new MyTextView( this);
  57. myTextView6.setText( "居右的文本");
  58. myTextView6.setTextSize( 25);
  59. myTextView6.setTextAlign(MyTextView.TEXT_ALIGN_RIGHT | MyTextView.TEXT_ALIGN_CENTER_VERTICAL);
  60. myTextView6.setTextColor(Color.BLUE);
  61. myTextView6.setBackgroundColor(Color.YELLOW);
  62. container.addView(myTextView6, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px( 150, displayParams.scale));
  63. MyTextView myTextView7 = new MyTextView( this);
  64. myTextView7.setText( "左上的文本");
  65. myTextView7.setTextSize( 25);
  66. myTextView7.setTextAlign(MyTextView.TEXT_ALIGN_TOP | MyTextView.TEXT_ALIGN_LEFT);
  67. myTextView7.setTextColor(Color.GREEN);
  68. myTextView7.setBackgroundColor(Color.CYAN);
  69. container.addView(myTextView7, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px( 150, displayParams.scale));
  70. MyTextView myTextView8 = new MyTextView( this);
  71. myTextView8.setText( "中上的文本");
  72. myTextView8.setTextSize( 25);
  73. myTextView8.setTextAlign(MyTextView.TEXT_ALIGN_TOP | MyTextView.TEXT_ALIGN_CENTER_HORIZONTAL);
  74. myTextView8.setTextColor(Color.RED);
  75. myTextView8.setBackgroundColor(Color.GREEN);
  76. container.addView(myTextView8, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px( 150, displayParams.scale));
  77. MyTextView myTextView9 = new MyTextView( this);
  78. myTextView9.setText( "右上的文本");
  79. myTextView9.setTextSize( 25);
  80. myTextView9.setTextAlign(MyTextView.TEXT_ALIGN_TOP | MyTextView.TEXT_ALIGN_RIGHT);
  81. myTextView9.setTextColor(Color.YELLOW);
  82. myTextView9.setBackgroundColor(Color.BLUE);
  83. container.addView(myTextView9, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px( 150, displayParams.scale));
  84. }
  85. }


     
     
     
     
  1. activity_main.xml
  2. "http://schemas.android.com/apk/res/android"
  3. xmlns:tools= "http://schemas.android.com/tools"
  4. android:layout_width= "match_parent"
  5. android:layout_height= "match_parent"
  6. android:paddingBottom= "@dimen/activity_vertical_margin"
  7. android:paddingLeft= "@dimen/activity_horizontal_margin"
  8. android:paddingRight= "@dimen/activity_horizontal_margin"
  9. android:paddingTop= "@dimen/activity_vertical_margin"
  10. tools:context= ".MainActivity" >
  11. android:id= "@+id/container"
  12. android:layout_width= "match_parent"
  13. android:layout_height= "wrap_content"
  14. android:orientation= "vertical"/>

还用到了两个工具类,代码可以参考这篇文章http://blog.csdn.net/carrey1989/article/details/10360613 
在进行垂直偏上和垂直偏下的设置时,关键是设置baseline的y坐标分别等于-fm.ascent和viewHeight - fm.bottom,意思就是可以让文字刚好不超过控件的边缘。 



你可能感兴趣的:(View相关)