自定义view-drawText

参考:自定义控件之绘图篇( 五):drawText()详解
博客笔记:自定义View之绘图(1)–drawText
Android 自定义View-怎么绘制居中文本?

drawText方法参数介绍:

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

y:所代表的是基线的位置
x:代表所要绘制文字所在矩形的相对位置。相对位置就是指指定点(x,y)在在所要绘制矩形的位置。我们知道所绘制矩形的纵坐标是由Y值来确定的,而相对x坐标的位置,只有左、中、右三个位置了。也就是所绘制矩形可能是在x坐标的左侧绘制,也有可能在x坐标的中间,也有可能在x坐标的右侧。

而定义在x坐标在所绘制矩形相对位置的函数是:

/** 
* 其中Align的取值为:Panit.Align.LEFT,Paint.Align.CENTER,Paint.Align.RIGHT 
*/  
Paint::setTextAlign(Align align);  

我们如果要画”harvic’s blog”这几个字,这个(x,y)坐标应当是下图中绿色小点的位置:

设置: paint.setTextAlign(Paint.Align.LEFT);

x坐标在所绘制矩形相对位置:左

设置paint.setTextAlign(Paint.Align.CENTER);

x坐标在所绘制矩形相对位置:中间

设置paint.setTextAlign(Paint.Align.RIGHT);

x坐标在所绘制矩形相对位置:右

FontMetrics

系统在画文字时的五条线,baseline、ascent、descent、top、bottom,我们知道baseline的位置是我们在构造drawText()时的参数y来决定的,那ascent,descent,top,bottom这些线的位置要怎么计算出来呢?
Android给我们提供了一个类:FontMetrics,它里面有四个成员变量:

FontMetrics::ascent;  
FontMetrics::descent;  
FontMetrics::top;  
FontMetrics::bottom;  

他们的意义与值的计算方法分别如下:

ascent = ascent线的y坐标 - baseline线的y坐标;//ascent变量的值是负的。
descent = descent线的y坐标 - baseline线的y坐标;//descent变量的值是正数。
top = top线的y坐标 - baseline线的y坐标;//top 变量的值是负的。
bottom = bottom线的y坐标 - baseline线的y坐标;//bottom变量的值是正数。

Text四线格的各线位置:

ascent线Y坐标 = baseline线的y坐标 + fontMetric.ascent;
descent线Y坐标 = baseline线的y坐标 + fontMetric.descent;
top线Y坐标 = baseline线的y坐标 + fontMetric.top;
bottom线Y坐标 = baseline线的y坐标 + fontMetric.bottom

-1> topLine–可绘制最高线
-2> ascentLine–建议绘制单行字符最高线
-3> descentLine–建议绘制单行字符最低线
-4> bottomLine–可绘制最低线

获取FontMetrics对象

Paint paint = new Paint();  
Paint.FontMetrics fm = paint.getFontMetrics();  
Paint.FontMetricsInt fmInt = paint.getFontMetricsInt(); 

通过paint.getFontMetrics()得到对应的FontMetrics对象。

这里还有另外一个FontMetrics同样的类叫做FontMetricsInt,它的意义与FontMetrics完全相同,只是得到的值的类型不一样而已,FontMetricsInt中的四个成员变量的值都是Int类型,而FontMetrics得到的四个成员变量的值则都是float类型的。

字符串所占高度:

Paint.FontMetricsInt fm = paint.getFontMetricsInt();  
int top = baseLineY + fm.top;  
int bottom = baseLineY + fm.bottom;  
//所占高度  
int height = bottom - top;  

字符串所占宽度:

Paint paint = new Paint();  

//设置paint  
paint.setTextSize(120); //以px为单位  
//获取宽度  
int width = (int)paint.measureText("harvic\'s blog");  

字符串所占最小矩形:

要获取最小矩形,也是通过系统函数来获取的,函数及意义如下:

/** 
 * 获取指定字符串所对应的最小矩形,以(0,0)点所在位置为基线 
 * @param text  要测量最小矩形的字符串 
 * @param start 要测量起始字符在字符串中的索引 
 * @param end   所要测量的字符的长度 
 * @param bounds 接收测量结果 
 */  
public void getTextBounds(String text, int start, int end, Rect bounds); 

示例:

String text = "harvic\'s blog";  
Paint paint = new Paint();  
//设置paint  
paint.setTextSize(120); //以px为单位  

Rect minRect = new Rect();  
paint.getTextBounds(text,0,text.length(),minRect);  
Log.e("qijian",minRect.toShortString());  

有时候会发现左上角为负数,比如:左上角位置为(8,-90),右下角的位置为(634,26);
原因:从代码中,我们也可以看到,我们并没有给getTextBounds()传递基线位置。那它就是以(0,0)为基线来得到这个最小矩形的!所以这个最小矩形的位置就是以(0,0)为基线的结果!

由于paint.getTextBounds()得到最小矩形的基线是y = 0;那我们直接将这个矩形移动baseline的距离就可以得到这个矩形实际应当在的位置了。
所以矩形应当所在实际位置的坐标是:

Rect minRect = new Rect();  
paint.getTextBounds(text,0,text.length(),minRect);  
//最小矩形,实际top位置  
int minTop = bounds.top + baselineY;  
//最小矩形,实际bottom位置  
int minBottom = bounds.bottom + baselineY;  

示例:

MyTextView

package com.android.imooc;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

public class MyTextView extends View {

    public MyTextView(Context context) {
        this(context, null);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint paint = new Paint();

        String mText = "xu qiang";
        int baseLineX = 0;
        int baseLineY = 200;
        paint.setTextSize(180); //以px为单位
        paint.setTextAlign(Paint.Align.LEFT);//会影响Paint在字符串哪个位置开始绘制
        canvas.drawText(mText, baseLineX, baseLineY, paint);

        /*---------------------------------五条线-------------------------------------*/
        //计算各线在位置
        Paint.FontMetrics fm = paint.getFontMetrics();
        float ascent = baseLineY + fm.ascent;
        float descent = baseLineY + fm.descent;
        float top = baseLineY + fm.top;
        float bottom = baseLineY + fm.bottom;
        Log.e("111", "top==" + top);//9.892578
        Log.e("111", "ascent==" + ascent);//33.007812
        Log.e("111", "baseLineY==" + baseLineY);//200
        Log.e("111", "descent==" + descent);//243.94531
        Log.e("111", "bottom==" + bottom);//248.7793

        //画top线
        paint.setColor(Color.BLUE);
        paint.setStrokeWidth(2);
        canvas.drawLine(baseLineX, top, 3000, top, paint);

        //画ascent线
        paint.setColor(Color.GREEN);
        paint.setStrokeWidth(2);
        canvas.drawLine(baseLineX, ascent, 3000, ascent, paint);

        //画基线线
        paint.setColor(Color.RED);
        paint.setStrokeWidth(2);
        canvas.drawLine(baseLineX, baseLineY, 3000, baseLineY, paint);

        //画descent线
        paint.setColor(Color.CYAN);
        paint.setStrokeWidth(2);
        canvas.drawLine(baseLineX, descent, 3000, descent, paint);

        //画bottom线
        paint.setColor(Color.BLACK);
        paint.setStrokeWidth(2);
        canvas.drawLine(baseLineX, bottom, 3000, bottom, paint);

        /*----------------------------------矩形区域--------------------------------*/
        //字符串所占高度、宽度和最小矩形
        Paint.FontMetricsInt fmInt = paint.getFontMetricsInt();
        int str_top = baseLineY + fmInt.top;
        int str_bottom = baseLineY + fmInt.bottom;
        //字符串所占高度
        int str_height = str_bottom - str_top;
        //字符串所占宽度
        int str_width = (int) paint.measureText(mText);

        //字符串所占最小矩形
        Rect minRect = new Rect();
        paint.getTextBounds(mText, 0, mText.length(), minRect);
        Log.e("111", "str_top==" + str_top);//9
        Log.e("111", "str_bottom==" + str_bottom);//249
        Log.e("111", "str_height==" + str_height);//240
        Log.e("111", "str_width==" + str_width);//676
        Log.e("111", "最小矩形==" + minRect.toString());//Rect(3, -130 - 664, 38)
        Log.e("111", "最小矩形==" + minRect.toShortString());//[3,-130][664,38]

        //画text所占的区域:高度从top到bottom(绿色区域)
        int width = (int) paint.measureText(mText);
        Rect rect = new Rect(baseLineX, str_top, baseLineX + width, str_bottom);
        paint.setColor(Color.GREEN);
        canvas.drawRect(rect, paint);

        //画最小区域矩形(红色区域)
        minRect.top = baseLineY + minRect.top;
        minRect.bottom = baseLineY + minRect.bottom;
        paint.setColor(Color.RED);
        canvas.drawRect(minRect, paint);

        //写文字(黑色字体)
        paint.setColor(Color.BLACK);
        paint.setTextSize(180); //以px为单位
        paint.setTextAlign(Paint.Align.LEFT);//会影响Paint在字符串哪个位置开始绘制
        canvas.drawText(mText, baseLineX, baseLineY, paint);
    }
}

布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/container"
    android:background="#ff4081"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.android.imooc.MyTextView
        android:layout_width="300dip"
        android:layout_height="200dip"
        android:background="#ffffff"/>
</LinearLayout>

效果图:

这里写图片描述

其他:

获取文本宽度:

public class MyTextView extends View {

    private int baseline;

    public MyTextView(Context context) {
        this(context, null);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        String testString = "测试文本宽度";
        Paint mPaint = new Paint();
        mPaint.setTextSize(100);
        mPaint.setTextAlign(Paint.Align.LEFT);
        mPaint.setColor(Color.RED);
        Rect mBound = new Rect();
        mPaint.getTextBounds(testString, 0, testString.length(), mBound);


        int x = getWidth() / 2 - (int) mPaint.measureText(testString) / 2;
        Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
        baseline = getHeight() / 2 - fontMetrics.descent + (fontMetrics.bottom - fontMetrics.top) / 2;
        canvas.drawText(testString, x, baseline, mPaint);

        //在使用Canvas绘制文字时,需要得到字符串的长度,Paint类内给了两个方法,measureText(),getTextBound();
        //getTextBounds() 得到的宽度总是比 measureText() 得到的宽度要小一点。
        //measureTextWidth来获取文本宽度

        //Paint的getTextBounds方法--获取边界
        int getTextBoundsWidth = mBound.right - mBound.left;
        mPaint.setColor(Color.GREEN);
        mPaint.setStrokeWidth(5);
        Log.e("111", "getTextBounds===" + getTextBoundsWidth);//getTextBounds===592
        canvas.drawLine(x, baseline + 15, x + getTextBoundsWidth, baseline + 15, mPaint);
        //Paint的measureText方法--获取宽度
        int measureTextWidth = (int) mPaint.measureText(testString);
        Log.e("111", "measureTextWidth===" + measureTextWidth);//measureTextWidth===600
        mPaint.setStrokeWidth(5);
        mPaint.setColor(Color.BLACK);
        canvas.drawLine(x, baseline + 10, x + measureTextWidth, baseline + 10, mPaint);
    }
}

效果图:

这里写图片描述

绘制文本在中间:

一般常见方法,MyTextView1(不推荐)

    /*
         * 控件宽度/2 - 文字宽度/2
         */
        float startX = getWidth() / 2 - mBound.width() / 2;

        /*
         * 控件高度/2 + 文字高度/2,绘制文字从文字左下角开始,因此"+"
         */
        float startY = getHeight() / 2 + mBound.height() / 2;

        // 绘制文字
        canvas.drawText(mText, startX, startY, mPaint);

示例:

public class MyTextView1 extends View {

    private int baseline;

    public MyTextView1(Context context) {
        this(context, null);
    }

    public MyTextView1(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyTextView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);


        String testString = "测试居中";
        Paint mPaint = new Paint();
        mPaint.setTextSize(60);
        mPaint.setColor(Color.RED);
        mPaint.setTextAlign(Paint.Align.LEFT);
        Rect mBound = new Rect();
        mPaint.getTextBounds(testString, 0, testString.length(), mBound);

        // X=控件宽度/2 - 文本宽度/2;
        int x = getWidth() / 2 - mBound.width() / 2;
        Log.e("111", "x1===" + x);//
        // Y=控件高度/2 + 文本宽度/2;
        baseline = getHeight() / 2 + mBound.height() / 2;
        Log.e("111", "baseline1===" + baseline);//
        // 绘制文字
        canvas.drawText(testString, x, baseline, mPaint);

        // 绘制基线
        mPaint.setColor(Color.YELLOW);
        mPaint.setStrokeWidth(5);
        canvas.drawLine(0, baseline, getWidth(), baseline, mPaint);
        canvas.drawLine(x, 0, x, getHeight(), mPaint);

        // 绘制中线,做对比
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(5);
        canvas.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2, mPaint);
        canvas.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight(), mPaint);

    }
}

文字宽度计算改进,这是比较精确的测量文字宽度的方式

int value = (int)mPaint.measureText(mText);//准确的文本宽度

修正计算 Y 值的计算公式:

FontMetricsInt fm = mPaint.getFontMetricsInt();

//方式一:见MyTextView (推荐)
int startY = getHeight() / 2 - fm.descent + (fm.bottom - fm.top) / 2;

//方式二:见MyTextView2 : descent+ascent(忽略了音标)
int startY = getHeight() / 2 - fm.descent + (fm.descent - fm.ascent)/ 2
           = getHeight() / 2 - (fm.descent  +  fm.ascent)/ 2; 

参考:用TextPaint来绘制文字

MyTextView (推荐方式):

public class MyTextView extends View {

    private int baseline;

    public MyTextView(Context context) {
        this(context, null);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);


        String testString = "测试居中";
        Paint mPaint = new Paint();
        mPaint.setTextSize(60);
        mPaint.setColor(Color.RED);
        Rect mBound = new Rect();
        mPaint.getTextBounds(testString, 0, testString.length(), mBound);
        mPaint.setTextAlign(Paint.Align.LEFT);

        int x = getWidth() / 2 - (int) mPaint.measureText(testString) / 2;
        Log.e("111", "x===" + x);//
        Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
        baseline = getHeight() / 2 - fontMetrics.descent +  
                        (fontMetrics.bottom - fontMetrics.top) / 2;
        Log.e("111", "baseline===" + baseline);//
        // 绘制文字
        canvas.drawText(testString, x, baseline, mPaint);

        // 绘制起点、基线
        mPaint.setColor(Color.YELLOW);
        mPaint.setStrokeWidth(5);
        canvas.drawLine(0, baseline, getWidth(), baseline, mPaint);
        canvas.drawLine(x, 0, x, getHeight(), mPaint);

        // 绘制View的中线,做对比
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(5);
        canvas.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2, mPaint);
        canvas.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight(), mPaint);
    }
}

MyTextView2

public class MyTextView2 extends View {

    private int baseline;

    public MyTextView2(Context context) {
        this(context, null);
    }

    public MyTextView2(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyTextView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);


        String testString = "测试居中";
        Paint mPaint = new Paint();
        mPaint.setTextSize(60);
        mPaint.setColor(Color.RED);
        Rect mBound = new Rect();
        mPaint.getTextBounds(testString, 0, testString.length(), mBound);
        mPaint.setTextAlign(Paint.Align.LEFT);

        int x = getWidth() / 2 - (int) mPaint.measureText(testString) / 2;
        Log.e("111", "x2===" + x);//
        Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
        baseline = getHeight() / 2 - fontMetrics.descent +  
                            (fontMetrics.descent - fontMetrics.ascent)/ 2;
        Log.e("111", "baseline2===" + baseline);//
        // 绘制文字
        canvas.drawText(testString, x, baseline, mPaint);

        // 绘制起点、基线
        mPaint.setColor(Color.YELLOW);
        mPaint.setStrokeWidth(5);
        canvas.drawLine(0, baseline, getWidth(), baseline, mPaint);
        canvas.drawLine(x, 0, x, getHeight(), mPaint);

        // 绘制View的中线,做对比
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(5);
        canvas.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2, mPaint);
        canvas.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight(), mPaint);
    }
}

作参考的TextView:CutomTextView

@SuppressLint("AppCompatCustomView")
public class CutomTextView  extends TextView{
    public CutomTextView(Context context) {
        super(context);
    }

    public CutomTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public CutomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 绘制View的中线,做对比
        Paint mPaint = new Paint();
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(5);
        canvas.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2, mPaint);
        canvas.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight(), mPaint);
    }
}

布局一:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ff4081"
    android:orientation="vertical">

    <com.android.imooc.MyTextView
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="#ffffff" />

    <com.android.imooc.MyTextView2
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="#00ff00" />

    <com.android.imooc.CutomTextView
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="#e9e9e9"
        android:gravity="center"
        android:text="测试文本居中"
        android:textColor="#ff0000"
        android:textSize="100px" />

    <com.android.imooc.MyTextView1
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="#00ff00" />

</LinearLayout>

效果图一:

这里写图片描述

布局二:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#e6e6e6"
    android:orientation="horizontal">

    <com.android.imooc.MyTextView
        android:layout_width="0dp"
        android:layout_height="100dp"
        android:layout_weight="1"
        android:background="#ffffff" />

    <com.android.imooc.MyTextView2
        android:layout_width="0dp"
        android:layout_height="100dp"
        android:layout_weight="1"
        android:background="#00ff00" />

    <com.android.imooc.CutomTextView
        android:layout_width="0dp"
        android:layout_height="100dp"
        android:layout_weight="1"
        android:gravity="center"
        android:text="测试居中"
        android:textColor="#ff0000"
        android:textSize="60px" />

    <com.android.imooc.MyTextView1
        android:layout_width="0dp"
        android:layout_height="100dp"
        android:layout_weight="1"
        android:background="#00ff00" />
</LinearLayout>

效果图二:

这里写图片描述

通过对比可见:

1(MyTextView)与 3(CutomTextView即:TextView)最接近,

以下绘制文字居中和原生TextView最接近,适合做居中:

Paint mPaint = new Paint();
int x = getWidth() / 2 - (int) mPaint.measureText(testString) / 2;
Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
baseline = getHeight() / 2 - fontMetrics.descent + (fontMetrics.bottom - fontMetrics.top) / 2;
// 绘制文字
canvas.drawText(testString, x, baseline, mPaint);

或者

// 计算Baseline绘制的起点X轴坐标 ,计算方式:画布宽度的一半 - 文字宽度的一半
int baseX = (int) (canvas.getWidth() / 2 - mTextPaint.measureText(TEXT) / 2);

// 计算Baseline绘制的Y坐标 ,计算方式:画布高度的一半 - 文字总高度的一半
int baseY = (int) ((canvas.getHeight() / 2) - ((mTextPaint.descent() + mTextPaint.ascent()) / 2));

注意:

mTextPaint.ascent()为负数
mTextPaint.descent()为正数

ascent 和 descent 这两个值还可以通过 Paint.ascent() 和 Paint.descent() 来快捷获取。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值