《Android自定义控件入门到精通》文章索引 ☞ https://blog.csdn.net/Jhone_csdn/article/details/118146683
《Android自定义控件入门到精通》所有源码 ☞ https://gitee.com/zengjiangwen/Code
文章目录
Text
drawText(String text, float x, float y, Paint paint)
不知道大家有没有这样的经历,在绘制文字的时候,总是把握不住绘制的精确位置,比如,我想在矩形中,居中绘制"Android"
@Override
protected void onDraw(Canvas canvas) {
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(2);
mPaint.setTextSize(14);
mPaint.setColor(Color.YELLOW);
Rect rect = new Rect(10, 10, 120, 60);
canvas.drawRect(rect, mPaint);
//获取文字宽度
float textWidth = mPaint.measureText("Android");
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
//获取文字高度
float textHeight=fontMetrics.bottom-fontMetrics.top;
float x= rect.left+(rect.width()-textWidth)/2;
float y=rect.top+(rect.height()-textHeight)/2;
canvas.drawText("Android",x,y,mPaint);
}
我们预测drawText()中的x、y为开始绘制的坐标,即猜测以上代码可以实现文字水平垂直居中,但现实往往会打脸:
水平是居中了,但这个垂直居中有点让人摸不着头脑啊!
在前几篇中,我们绘制各种图形,都是以左上角为起始点开始绘制,为啥到了文字绘制这,不灵了呢?
还记得小学时候抄过的字母吗?
本子上每行的四条线,特别是红线是不是记忆尤新?
同样的,Android中Text的绘制也有对应的四条线
@Override
protected void onDraw(Canvas canvas) {
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(1);
mPaint.setColor(Color.YELLOW);
mPaint.setTextSize(48);//设置文字大小为12像素
String text = "abcdefghijklmnopqrst";
float textWidth = mPaint.measureText(text);
int x = 100, y = 100;
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
float top = fontMetrics.top;
float ascent = fontMetrics.ascent;
float descent = fontMetrics.descent;
float bottom = fontMetrics.bottom;
mPaint.setColor(Color.YELLOW);
canvas.drawLine(x, top + y, x + textWidth, top + y, mPaint);
mPaint.setColor(Color.RED);
canvas.drawLine(x, ascent + y, x + textWidth, ascent + y, mPaint);
mPaint.setColor(Color.RED);
canvas.drawLine(x, y, x + textWidth, y, mPaint);
mPaint.setColor(Color.WHITE);
canvas.drawLine(x, descent + y, x + textWidth, descent + y, mPaint);
mPaint.setColor(Color.GREEN);
canvas.drawLine(x, bottom + y, x + textWidth, bottom + y, mPaint);
mPaint.setColor(Color.WHITE);
canvas.drawText(text, x, y, mPaint);
}
可以看到,y的坐标就是作业本上的红线,我们称之为基线(baseLine)
- fontMetrics.top:基线到top线的距离,为负数
- fontMetrics.ascent:基线到ascent线的距离,为负数
- fontMetrics.descent:基线到descent线的距离,为正数
- fontMetrics.bottom:基线到bottom线的距离,为正数
我们可以明确下以下几个知识点:
- 文字的字高=descent-ascent
- 文字的行高=bottom-top
- 文字的宽度可以通过Paint.measureText(text)计算
- 文字绘制的起点(x,y)为基点,基点的y坐标称为基线
- 文字的基线=垂直中线+(字高/2-bottom)
明白了这些,我们再来居中绘制我们的"Android"
@Override
protected void onDraw(Canvas canvas) {
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(1);
mPaint.setColor(Color.YELLOW);
Rect rect = new Rect(10, 10, 120, 60);
canvas.drawRect(rect, mPaint);
mPaint.setTextSize(12);//设置文字大小为12像素
String text = "Android";
float textWidth = mPaint.measureText(text);
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
float top = fontMetrics.top;
float bottom = fontMetrics.bottom;
float baseLineX = (rect.right - rect.left - textWidth) / 2 + rect.left;
float offset = (bottom - top) / 2 - bottom;//文字中间到基线的距离
float baseLineY = (rect.bottom - rect.top) / 2 + offset + rect.top;
canvas.drawText(text, baseLineX, baseLineY, mPaint);
}
baseY=-(fontMetrics.bottom+fontMetrics.top)/2
Paint.setTextAlign(Paint.Align)
文本的对齐方式
public enum Align {
LEFT (0),//基点左对齐
CENTER (1),//基点居中对齐
RIGHT (2);//基点又对齐
}
啥意思呢,我们直接看例子
Align.LEFT
Align.CENTER
Align.RIGHT
drawTextOnPath(String text, Path path, float hOffset,float vOffset, Paint paint)
沿着路劲绘制文本
- path:一条路径
- hOffset:水平偏移量
- vOffset:垂直偏移量
@Override
protected void onDraw(Canvas canvas) {
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(2);
Path path = new Path();
path.moveTo(40, 100);
path.lineTo(600, 100);
canvas.drawPath(path, mPaint);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setLetterSpacing(0.3f);
mPaint.setTextSize(20);
canvas.drawTextOnPath("深圳市腾讯计算机技术有限公司", path, 30, 60, mPaint);
}
理解了hOffset和vOffset,我们在来看看这个
mPaint.setColor(Color.RED);//设置画笔颜色
mPaint.setStyle(Paint.Style.STROKE);//设置画笔描边
mPaint.setStrokeWidth(10);//设置线宽
mPaint.setLetterSpacing(0.3f);//设置字间距
Path path=new Path();//新建一个路径
path.addCircle(400,400,100, Path.Direction.CW);//在路径上添加一个圆,CW:顺时针
Matrix matrix=new Matrix();//新建一个Matrix矩阵,用来旋转路径
matrix.setRotate(90,400,400);//路径旋转90度
path.transform(matrix);
canvas.drawPath(path,mPaint);
String text="深圳市腾讯计算机技术有限公司";
mPaint.setStyle(Paint.Style.FILL);
mPaint.setTextSize(20);
canvas.drawTextOnPath(text,path,130,30,mPaint);
大家有疑惑吧,path.transform(matrix)这个用来干啥的,一个圆形路径,转来转去不是一样吗?那去掉这个代码来看看效果:
我们再调下hOffset的值,来达到第一个效果:
@Override
protected void onDraw(Canvas canvas) {
mPaint.setColor(Color.RED);//设置画笔颜色
mPaint.setStyle(Paint.Style.STROKE);//设置画笔描边
mPaint.setStrokeWidth(10);//设置线宽
mPaint.setLetterSpacing(0.3f);//设置字间距
Path path=new Path();//新建一个路径
path.addCircle(160,160,100, Path.Direction.CW);//在路径上添加一个圆
// Matrix matrix=new Matrix();//新建一个Matrix矩阵,用来旋转路径
// matrix.setRotate(90,160,160);//路径旋转90度
// path.transform(matrix);
canvas.drawPath(path,mPaint);
String text="深圳市腾讯计算机技术有限公司";
mPaint.setStyle(Paint.Style.FILL);
mPaint.setTextSize(20);
canvas.drawTextOnPath(text,path,300,30,mPaint);
}
发现了吧,“公司”这两个字重叠了,如果hOffset的值继续调大,可能就是这么个效果
结论:文字超出路径(圆周长)的范围是会重叠在一起显示!
这时候,我们再来看看hOffset和vOffset两个值在这个例子中是在什么位置:
- hOffset指的是0的位置到文字起点的位置(在圆周长400的位置)
- vOffset指的是文字与圆弧垂直方向的距离
- hOffset=400,再加上文本的长度,已经超出了圆周长,所以后面的文字重叠了
所以,我们需要旋转路径,改变它的起点。
整个例子的代码
@Override
protected void onDraw(Canvas canvas) {
mPaint.setColor(Color.RED);//设置画笔颜色
mPaint.setStyle(Paint.Style.STROKE);//设置画笔描边
mPaint.setStrokeWidth(10);//设置线宽
mPaint.setLetterSpacing(0.3f);//设置字间距
Path path = new Path();//新建一个路径
path.addCircle(160, 160, 100, Path.Direction.CW);//在路径上添加一个圆
Matrix matrix=new Matrix();//新建一个Matrix矩阵,用来旋转路径
matrix.setRotate(90,160,160);//路径旋转90度
path.transform(matrix);
canvas.drawPath(path, mPaint);
String text = "深圳市腾讯计算机技术有限公司";
mPaint.setStyle(Paint.Style.FILL);
mPaint.setTextSize(20);
canvas.drawTextOnPath(text, path, 130, 30, mPaint);
String text1 = "南山总部";
Path path1 = new Path();
path1.addCircle(160, 160, 100, Path.Direction.CCW);
canvas.drawTextOnPath(text1, path1, 420, -20, mPaint);
//画五角星
double arcAngle = (72 * Math.PI) / 180;
float pointX[] = new float[5], pointY[] = new float[5];
//以五角星中心点为(0,0),定义五角星右下角一个角点(40,40),通过这个点来计算五角星的其它点坐标
pointX[0] = 40;
pointY[0] = 40;
for (int i = 1; i < 5; i++) {
pointX[i] = (int) (pointX[i - 1] * Math.cos(arcAngle) - pointY[i - 1] * Math.sin(arcAngle));
pointY[i] = (int) (pointY[i - 1] * Math.cos(arcAngle) + pointX[i - 1] * Math.sin(arcAngle));
}
Path star = new Path();
star.moveTo(pointX[0], pointY[0]);
for (int i = 0; i < 5; i += 2) {
star.lineTo(pointX[i], pointY[i]);
}
for (int i = 1; i < 5; i += 2) {
star.lineTo(pointX[i], pointY[i]);
}
//将五角星的中心点移动到我们圆心的位置
canvas.translate(160, 160);
canvas.drawPath(star, mPaint);
}
drawPosText (char[] text, int index, int count, float[] pos, Paint paint)
drawPosText (String text, float[] pos, Paint paint)
- char[] text:要绘制的文字数组
- int index:从第几个文字开始绘制(开始位置的索引)
- int count:绘制文字的个数,从index开始算
- float[] pos:给每个文字定位,两个值一组,[x1,y1,x2,y2,x3,y3……]
@Override
protected void onDraw(Canvas canvas) {
mPaint.setColor(Color.RED);//设置画笔颜色
mPaint.setStyle(Paint.Style.FILL);//设置画笔描边
canvas.drawPosText("安卓天下第一", new float[]{23, 32, 43, 21, 54, 76, 90, 100, 45, 156, 49, 123}, mPaint);
}