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

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

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

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

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

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

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

/** * Return the distance above (negative) the baseline (ascent) based on the * current typeface and text size. * * @return the distance above (negative) the baseline (ascent) based on the *         current typeface and text size. */public native float ascent();/** * Return the distance below (positive) the baseline (descent) based on the * current typeface and text size. * * @return the distance below (positive) the baseline (descent) based on *         the current typeface and text size. */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轴上的值看上去比其值略偏下。

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

Paint pFont = new Paint();Rect rect = new Rect();pFont.getTextBounds("豆", 0, 1, rect);Log.v(TAG, "height:"+rect.height()+"width:"+rect.width());
下面给出一个综合Demo演示:
package com.example.textmeasure;import android.app.Activity;import android.graphics.Paint;import android.graphics.Rect;import android.graphics.Paint.FontMetrics;import android.os.Bundle;import android.util.DisplayMetrics;import android.util.Log;public class MainActivity extends Activity {Paint mPaint=null;public float screenDensity;public float screenScaledDensity;public float textHeight1;public float textHeight2;public float textHeight3;public float textWidthA;public float textWidthB; @Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//获取屏幕密度和字体适用的密度        DisplayMetrics dm = getResources().getDisplayMetrics();        screenDensity = dm.density;        screenScaledDensity = dm.scaledDensity;mPaint=new Paint();mPaint.setTextSize(15*screenScaledDensity);//第一种获取文本高度的方式FontMetrics fm=mPaint.getFontMetrics();textHeight1=fm.descent-fm.ascent;//第二种获取文本高度的方式textHeight2=mPaint.descent()-mPaint.ascent();//第三种获取文本高度的方式Rect bounds=new Rect();mPaint.getTextBounds("0.00", 0, "0.00".length(), bounds);    textHeight3=bounds.height();    //获取文本宽度    textWidthA=bounds.width();        //获取文本宽度的另一种方式    textWidthB=mPaint.measureText("0.00");        Log.i("publish","textHeight1: "+textHeight1);    Log.i("publish","textHeight2: "+textHeight2);    Log.i("publish","textHeight3: "+textHeight3);    Log.i("publish","textWidthA: "+textWidthA);    Log.i("publish","textWidthB: "+textWidthB);}}
打印结果:
03-23 12:54:16.325: I/publish(27219): screenDensity: 3.003-23 12:54:16.325: I/publish(27219): screenScaledDensity: 3.003-23 12:54:16.325: I/publish(27219): textHeight1: 52.73437503-23 12:54:16.325: I/publish(27219): textHeight2: 52.73437503-23 12:54:16.325: I/publish(27219): textHeight3: 32.003-23 12:54:16.325: I/publish(27219): textWidthA: 82.003-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:

//项目代码: //MyTextView.java package com.example.textalignment.mytextview;import com.example.textalignment.util.DisplayParams;import com.example.textalignment.util.DisplayUtil;import android.content.Context;import android.graphics.Bitmap;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Matrix;import android.graphics.Paint;import android.graphics.RectF;import android.graphics.Paint.Align;import android.graphics.Paint.FontMetrics;import android.graphics.drawable.Drawable;import android.util.AttributeSet;import android.util.DisplayMetrics;import android.view.View;/** * 自定义文本显示控件 * 该自定义控件中的文本可以在9个方位进行控制 * 左上——中上——右上 * 左中——中中——右中 * 左下——中下——右下 * @author carrey * */public class MyTextView extends View {/** 要显示的文字 */private String text;/** 文字的颜色 */private int textColor;/** 文字的大小 */private int textSize;/** 文字的方位 */private int textAlign;//public static final int TEXT_ALIGN_CENTER            = 0x00000000;public static final int TEXT_ALIGN_LEFT              = 0x00000001;public static final int TEXT_ALIGN_RIGHT             = 0x00000010;public static final int TEXT_ALIGN_CENTER_VERTICAL   = 0x00000100;public static final int TEXT_ALIGN_CENTER_HORIZONTAL = 0x00001000;public static final int TEXT_ALIGN_TOP               = 0x00010000;public static final int TEXT_ALIGN_BOTTOM            = 0x00100000;/** 文本中轴线X坐标 */private float textCenterX;/** 文本baseline线Y坐标 */private float textBaselineY;/** 控件的宽度 */private int viewWidth;/** 控件的高度 */private int viewHeight;/** 控件画笔 */private Paint paint;private FontMetrics fm;/** 场景 */private Context context;public MyTextView(Context context) {super(context);this.context = context;init();}public MyTextView(Context context, AttributeSet attrs) {super(context, attrs);this.context = context;init();}/** * 变量初始化 */private void init() {paint = new Paint();paint.setAntiAlias(true);paint.setTextAlign(Align.CENTER);//默认情况下文字居中显示textAlign = TEXT_ALIGN_CENTER_HORIZONTAL | TEXT_ALIGN_CENTER_VERTICAL;//默认的文本颜色是黑色this.textColor = Color.BLACK;}@Overrideprotected void onLayout(boolean changed, int left, int top, int right,int bottom) {viewWidth = getWidth();viewHeight = getHeight();super.onLayout(changed, left, top, right, bottom);}@Overrideprotected void onDraw(Canvas canvas) {//绘制控件内容setTextLocation();canvas.drawText(text, textCenterX, textBaselineY, paint);super.onDraw(canvas);}/** * 定位文本绘制的位置 */private void setTextLocation() {paint.setTextSize(textSize);paint.setColor(textColor);fm = paint.getFontMetrics();//文本的宽度float textWidth = paint.measureText(text);float textCenterVerticalBaselineY = viewHeight / 2 - fm.descent + (fm.descent - fm.ascent) / 2;switch (textAlign) {case TEXT_ALIGN_CENTER_HORIZONTAL | TEXT_ALIGN_CENTER_VERTICAL:textCenterX = (float)viewWidth / 2;textBaselineY = textCenterVerticalBaselineY;break;case TEXT_ALIGN_LEFT | TEXT_ALIGN_CENTER_VERTICAL:textCenterX = textWidth / 2;textBaselineY = textCenterVerticalBaselineY;break;case TEXT_ALIGN_RIGHT | TEXT_ALIGN_CENTER_VERTICAL:textCenterX = viewWidth - textWidth / 2;textBaselineY = textCenterVerticalBaselineY;break;case TEXT_ALIGN_BOTTOM | TEXT_ALIGN_CENTER_HORIZONTAL:textCenterX = viewWidth / 2;textBaselineY = viewHeight - fm.bottom; break;case TEXT_ALIGN_TOP | TEXT_ALIGN_CENTER_HORIZONTAL:textCenterX = viewWidth / 2;textBaselineY = -fm.ascent;break;case TEXT_ALIGN_TOP | TEXT_ALIGN_LEFT:textCenterX = textWidth / 2;textBaselineY = -fm.ascent;break;case TEXT_ALIGN_BOTTOM | TEXT_ALIGN_LEFT:textCenterX = textWidth / 2;textBaselineY = viewHeight - fm.bottom; break;case TEXT_ALIGN_TOP | TEXT_ALIGN_RIGHT:textCenterX = viewWidth - textWidth / 2;textBaselineY = -fm.ascent;break;case TEXT_ALIGN_BOTTOM | TEXT_ALIGN_RIGHT:textCenterX = viewWidth - textWidth / 2;textBaselineY = viewHeight - fm.bottom; break;}}/** * 设置文本内容 * @param text */public void setText(String text) {this.text = text;invalidate();}/** * 设置文本大小 * @param textSizeSp 文本大小,单位是sp */public void setTextSize(int textSizeSp) {DisplayParams displayParams = DisplayParams.getInstance(context);this.textSize = DisplayUtil.sp2px(textSizeSp, displayParams.fontScale);invalidate();}/** * 设置文本的方位 */public void setTextAlign(int textAlign) {this.textAlign = textAlign;invalidate();}/** * 设置文本的颜色 * @param textColor */public void setTextColor(int textColor) {this.textColor = textColor;invalidate();}}
//MainActivity.java package com.example.textalignment;import com.example.textalignment.mytextview.MyTextView;import com.example.textalignment.util.DisplayParams;import com.example.textalignment.util.DisplayUtil;import android.os.Bundle;import android.app.Activity;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Color;import android.view.Menu;import android.widget.LinearLayout;import android.widget.ScrollView;public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);DisplayParams displayParams = DisplayParams.getInstance(this);LinearLayout container = (LinearLayout) findViewById(R.id.container);MyTextView myTextView1 = new MyTextView(this);myTextView1.setText("居中的文本");myTextView1.setTextSize(30);myTextView1.setTextAlign(MyTextView.TEXT_ALIGN_CENTER_HORIZONTAL | MyTextView.TEXT_ALIGN_CENTER_VERTICAL);myTextView1.setTextColor(Color.BLUE);myTextView1.setBackgroundColor(Color.RED);container.addView(myTextView1, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px(150, displayParams.scale));MyTextView myTextView2 = new MyTextView(this);myTextView2.setText("居左的文本");myTextView2.setTextSize(25);myTextView2.setTextAlign(MyTextView.TEXT_ALIGN_CENTER_VERTICAL | MyTextView.TEXT_ALIGN_LEFT);myTextView2.setTextColor(Color.GREEN);myTextView2.setBackgroundColor(Color.YELLOW);container.addView(myTextView2, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px(150, displayParams.scale));MyTextView myTextView3 = new MyTextView(this);myTextView3.setText("右下的文本");myTextView3.setTextSize(15);myTextView3.setTextAlign(MyTextView.TEXT_ALIGN_BOTTOM | MyTextView.TEXT_ALIGN_RIGHT);myTextView3.setTextColor(Color.RED);myTextView3.setBackgroundColor(Color.BLUE);container.addView(myTextView3, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px(150, displayParams.scale));MyTextView myTextView4 = new MyTextView(this);myTextView4.setText("左下的文本");myTextView4.setTextSize(15);myTextView4.setTextAlign(MyTextView.TEXT_ALIGN_BOTTOM | MyTextView.TEXT_ALIGN_LEFT);myTextView4.setTextColor(Color.YELLOW);myTextView4.setBackgroundColor(Color.GREEN);container.addView(myTextView4, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px(150, displayParams.scale));MyTextView myTextView5 = new MyTextView(this);myTextView5.setText("中下的文本");myTextView5.setTextSize(35);myTextView5.setTextAlign(MyTextView.TEXT_ALIGN_BOTTOM | MyTextView.TEXT_ALIGN_CENTER_HORIZONTAL);myTextView5.setTextColor(Color.GRAY);myTextView5.setBackgroundColor(Color.RED);container.addView(myTextView5, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px(150, displayParams.scale));MyTextView myTextView6 = new MyTextView(this);myTextView6.setText("居右的文本");myTextView6.setTextSize(25);myTextView6.setTextAlign(MyTextView.TEXT_ALIGN_RIGHT | MyTextView.TEXT_ALIGN_CENTER_VERTICAL);myTextView6.setTextColor(Color.BLUE);myTextView6.setBackgroundColor(Color.YELLOW);container.addView(myTextView6, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px(150, displayParams.scale));MyTextView myTextView7 = new MyTextView(this);myTextView7.setText("左上的文本");myTextView7.setTextSize(25);myTextView7.setTextAlign(MyTextView.TEXT_ALIGN_TOP | MyTextView.TEXT_ALIGN_LEFT);myTextView7.setTextColor(Color.GREEN);myTextView7.setBackgroundColor(Color.CYAN);container.addView(myTextView7, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px(150, displayParams.scale));MyTextView myTextView8 = new MyTextView(this);myTextView8.setText("中上的文本");myTextView8.setTextSize(25);myTextView8.setTextAlign(MyTextView.TEXT_ALIGN_TOP | MyTextView.TEXT_ALIGN_CENTER_HORIZONTAL);myTextView8.setTextColor(Color.RED);myTextView8.setBackgroundColor(Color.GREEN);container.addView(myTextView8, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px(150, displayParams.scale));MyTextView myTextView9 = new MyTextView(this);myTextView9.setText("右上的文本");myTextView9.setTextSize(25);myTextView9.setTextAlign(MyTextView.TEXT_ALIGN_TOP | MyTextView.TEXT_ALIGN_RIGHT);myTextView9.setTextColor(Color.YELLOW);myTextView9.setBackgroundColor(Color.BLUE);container.addView(myTextView9, LinearLayout.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px(150, displayParams.scale));}}

activity_main.xml    

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



更多相关文章

  1. Android事件分发之dispatchTouchEvent()
  2. Android控件之TextView全解析
  3. LinearLayout(线性布局)
  4. Android(安卓)布局----让一个控件居底部
  5. Android控件开发之四----ListView(3)
  6. Android调用百度地图API实现――实时定位代码
  7. android上多样式文本的使用
  8. Android输入法的显示与隐藏
  9. Android(安卓)UI开发专题(四) View自绘控件

随机推荐

  1. 为什么我的Android应用程序偶尔可以非常
  2. Android开发学习之ImageView手势拖拽、缩
  3. 尝试使用Async任务获取json时的java.lang
  4. 彻底了解RxJava(一)基础知识
  5. Android - 从@drawable String打开资源
  6. 同时兼容高低版本的setBackground跟setTe
  7. Android逆向实例笔记—那些搜不到的中文
  8. Android学习笔记(一):基本概念
  9. Android基础 - 对话框和浮动Activity
  10. 安装使用Android-x86打造快速流畅的Aandr