本文罗列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;
-
}
说明如下:
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)上面两种方法得到的文字高度是一致的,但从本人经验来说,这种高度对数字来说略高,比如在画折线图上的坐标轴值时就有很明显的体现,这种方式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;
-
-
@Override
-
protected 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.0
-
03-
23
12:
54:
16.325: I/publish(
27219): screenScaledDensity:
3.0
-
03-
23
12:
54:
16.325: I/publish(
27219): textHeight1:
52.734375
-
03-
23
12:
54:
16.325: I/publish(
27219): textHeight2:
52.734375
-
03-
23
12:
54:
16.325: I/publish(
27219): textHeight3:
32.0
-
03-
23
12:
54:
16.325: I/publish(
27219): textWidthA:
82.0
-
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:
-
//项目代码:
-
//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;
-
}
-
-
@Override
-
protected void onLayout(boolean changed, int left, int top, int right,
-
int bottom) {
-
viewWidth = getWidth();
-
viewHeight = getHeight();
-
super.onLayout(changed, left, top, right, bottom);
-
}
-
-
@Override
-
protected 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 {
-
-
@Override
-
protected 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://schemas.android.com/apk/res/android"
-
xmlns:tools=
"http://schemas.android.com/tools"
-
android:layout_width=
"match_parent"
-
android:layout_height=
"match_parent"
-
android:paddingBottom=
"@dimen/activity_vertical_margin"
-
android:paddingLeft=
"@dimen/activity_horizontal_margin"
-
android:paddingRight=
"@dimen/activity_horizontal_margin"
-
android:paddingTop=
"@dimen/activity_vertical_margin"
-
tools:context=
".MainActivity" >
-
-
-
android:id=
"@+id/container"
-
android:layout_width=
"match_parent"
-
android:layout_height=
"wrap_content"
-
android:orientation=
"vertical"/>
-
-
还用到了两个工具类,代码可以参考这篇文章http://blog.csdn.net/carrey1989/article/details/10360613
在进行垂直偏上和垂直偏下的设置时,关键是设置baseline的y坐标分别等于-fm.ascent和viewHeight - fm.bottom,意思就是可以让文字刚好不超过控件的边缘。