Android画笔Paint和FontMetrics

有关自定义控件的许多内容,一直都想抽时间梳理一下,当然了梳理的内容不会很深,都是一些比较常用的类或者方法,主要是为了后续用到时方便查阅与参考。

本文主要整理的是Paint的一些基本使用方法和技巧,其中涉及到文字绘制的,涉及绘制文字时需要知道基线的坐标与View的坐标参考点是不同的,需要知道坐标之间的转换方式。另外需要知道有哪些方式可以测量已经绘制的文字所占的宽度,包括可以精确测量的方式,也有相对粗略的方式。

Paint常用方法

Paint在自定义控件中是一个比较常用的类,主要保存了颜色样式等绘制信息,通过一些属性指定如何绘制文字、图形。

如下是Paint的一些常用方法。

setARGB(int a,int r,int g,int b);

设置绘制的颜色,a代表透明度,r,g,b代表颜色值。

setAlpha(int a);

设置绘制图形的透明度,取值范围是[0-255],数值越小,越透明,颜色上表现越淡。

setColor(int color);

设置绘制的颜色,使用颜色值来表示,该颜色值包括透明度和RGB颜色。

setAntiAlias(boolean aa);

设置是否使用抗锯齿功能,会消耗较大资源,绘制图形速度会变慢。

setDither(boolean dither);

设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰,但是会损失性能。

setStyle(Paint.Style style);

设置画笔的样式,为FILL填充,FILL_AND_STROKE填充和描边,或STROKE描边。

setStrokeCap(Paint.Cap cap);

当画笔样式为STROKE或FILL_AND_STROKE时,设置笔刷的图形样式,如圆形样式Cap.ROUND,方形样式Cap.SQUARE 或者不设置Cap.BUTT。

setSrokeJoin(Paint.Join join);

设置绘制时各图形交汇处的结合方式,如Join.MITER锐角、Join.ROUND圆弧、Join.BEVEL直线。

setStrokeWidth(float width);

当画笔样式为STROKE或FILL_AND_STROKE时,设置笔刷的粗细度。

setXfermode(Xfermode xfermode);

设置图形重叠时的处理方式,如合并,取交集或并集,经常用来制作橡皮的擦除效果。

setShader(Shader shader);

设置图像效果,使用Shader可以绘制出各种渐变效果。

setColorFilter(ColorFilter colorfilter);

设置颜色过滤器,可以在绘制颜色时实现不用颜色的变换效果。

setShadowLayer(float radius ,float dx,float dy,int color);

在图形下面设置阴影层,产生阴影效果,radius为阴影的角度,dx和dy为阴影在x轴和y轴上的距离,color为阴影的颜色。

setTextAlign(Paint.Align align);

设置绘制文字的对齐方向。

setTextScaleX(float scaleX);

设置绘制文字x轴的缩放比例,可以实现文字的拉伸的效果。

setTextSize(float textSize);

设置绘制文字的字号大小。

setTextSkewX(float skewX);

设置斜体文字,skewX为倾斜弧度,默认0,官方推荐的-0.25f是斜体。

setTypeface(Typeface typeface);

设置Typeface对象,即字体风格,包括粗体,斜体以及衬线体,非衬线体等。

setUnderlineText(boolean underlineText);

设置带有下划线的文字效果。

setStrikeThruText(boolean strikeThruText);

设置带有删除线的效果。

setSubpixelText(boolean subpixelText);

设置该项为true,将有助于文本在LCD屏幕上的显示效果。

部分示例代码演示如下:

public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
	super(context, attrs, defStyleAttr);
	paint = new Paint();
	paint.setAntiAlias(true);
	paint.setStrokeWidth(50);

	path = new Path();
}

@Override
protected void onDraw(Canvas canvas) {
	super.onDraw(canvas);
	
	paint.setColor(Color.RED);
	paint.setStyle(Paint.Style.FILL);//填充
	canvas.drawCircle(200, 200, 100, paint);

	paint.setStyle(Paint.Style.STROKE);//描边
	canvas.drawCircle(500, 200, 100, paint);

	paint.setStyle(Paint.Style.FILL_AND_STROKE);//填充和描边
	canvas.drawCircle(800, 200, 100, paint);

	paint.setStrokeCap(Paint.Cap.BUTT);//没有
	canvas.drawLine(100, 400, 900, 400, paint);

	paint.setStrokeCap(Paint.Cap.ROUND);//圆形
	canvas.drawLine(100, 500, 900, 500, paint);

	paint.setStrokeCap(Paint.Cap.SQUARE);//方形
	canvas.drawLine(100, 600, 900, 600, paint);

	paint.setStyle(Paint.Style.STROKE);
	paint.setStrokeCap(Paint.Cap.BUTT);
	paint.setStrokeJoin(Paint.Join.MITER);//锐角
	path.moveTo(100, 1000);
	path.lineTo(300, 1100);
	path.lineTo(100, 1200);
	canvas.drawPath(path, paint);

	paint.setStrokeJoin(Paint.Join.ROUND);//圆弧
	path.moveTo(100, 1300);
	path.lineTo(300, 1400);
	path.lineTo(100, 1500);
	canvas.drawPath(path, paint);

	paint.setStrokeJoin(Paint.Join.BEVEL);//直线
	path.moveTo(100, 700);
	path.lineTo(300, 800);
	path.lineTo(100, 900);
	canvas.drawPath(path, paint);
}

字体以及FontMetrics

在自定义View时,如果需要绘制字体,Android提供了如下方法:

drawText(String text, float x, float y, Paint paint)

这里参数text是一个String类型,就是需要绘制的文字,float类型的x和y需要着重介绍一下。

x跟Paint设置的setTextAlign()有关系,一般默认是LEFT,这时候x就是文字绘制的左侧起点。

y理解跟View中坐标有些不同,这里的y其实是一个基线,在TextView中设置的字体,垂直方向上面默认都是基线对齐。为了更好的理解基线,可以参看下图:

位于基线上面的值为负值,基线下面的值是负值。

Android提供了FontMetrics类定义了字体规格对象,该对象封装了有关在特定屏幕上呈现特定字体的信息。

  • ascent是baseline之上至字符最高处的距离。
  • descent是baseline之下至字符最低处的距离。
  • leading文档说的很含糊,其实是上一行字符的descent到下一行的ascent之间的距离。
  • top指的是指的是最高字符到baseline的值,即ascent的最大值。
  • bottom指的是最下字符到baseline的值,即descent的最大值。

在一些自定义控件中,如果需要将绘制的文本垂直居中,这时候我们就要重新计算baseLineY的值了,因为View本身坐标系是以左上角为原点,而绘制文字时是以基线BaseLine为参考线,需要将坐标系转换一下,根据下图可以很方面的得出转换公式。

float baselineY = centerY + (fontMetrics.bottom-fontMetrics.top)/2 - fontMetrics.bottom

如果指定左上角的顶点坐标绘制文本。

float baselineY = Y - fontMetrics.top。

将文本绘制完成后,如果计算文字所占的空间宽度呢。Android也提供了相应的方法。

public class PaintView extends View {
    private static final String TAG = "PaintView";
    private Paint paint;
    private Path path;
    private String str = "世界HelloWorld";
    private Paint.FontMetrics fontMetrics;

    private float baseY = 200;

    public PaintView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStrokeWidth(5);
        paint.setTextSize(120);

        fontMetrics = paint.getFontMetrics();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawText(str, 100, baseY, paint);

        paint.setColor(Color.RED);
        canvas.drawLine(100, baseY, 950, baseY, paint);

        float topY = baseY + fontMetrics.top;
        paint.setColor(Color.GREEN);
        canvas.drawLine(100, topY, 950, topY, paint);

        float bottomY = baseY + fontMetrics.bottom;
        paint.setColor(Color.GREEN);
        canvas.drawLine(100, bottomY, 950, bottomY, paint);

        float ascentY = baseY + fontMetrics.ascent;
        paint.setColor(Color.YELLOW);
        canvas.drawLine(100, ascentY, 950, ascentY, paint);

        float descentY = baseY + fontMetrics.descent;
        paint.setColor(Color.BLUE);
        canvas.drawLine(100, descentY, 950, descentY, paint);

        float[] measuredWidth = new float[1];
        //计算指定长度的字符串(字符长度、字符个数、显示的时候真实的长度)
        int breakText = paint.breakText(str, true, 1000, measuredWidth);
		Log.i(TAG, "breakText="+breakText+", str.length()="+str.length()+", measuredWidth:"+measuredWidth[0]);

        Rect bound=new Rect();
        //获取文本的宽度,但是是一个比较粗略的结果
		paint.getTextBounds(str,0,str.length(),bound);
        Log.i(TAG, "getTextBounds="+bound.width());

        //测量字符宽度
        float measureText = paint.measureText(str);
        Log.i(TAG, "measureText="+measureText);

        float[] measuredWidths = new float[12];
        //测量得到每一个字符的宽度;textWidths字符数
        int textWidths = paint.getTextWidths(str, measuredWidths);
        Log.i(TAG, "textWidths:"+textWidths+"  "+ Arrays.toString(measuredWidths));
    }
}

输出结果如下:

PaintView: breakText=12, str.length()=12, measuredWidth:814.0
PaintView: getTextBounds=800
PaintView: measureText=814.0
PaintView: textWidths:12  [114.0, 114.0, 86.0, 64.0, 29.0, 29.0, 68.0, 104.0, 68.0, 41.0, 29.0, 68.0]

评论

您确定要删除吗?删除之后不可恢复