android TextView 自定义View时 文字居中的方式

public class MyTextView1 extends View {
    private final static String TAG = "MyTextView";
    //文字
    private String mText;
    //文字的颜色
    private int mTextColor;
    //文字的大小
    private int mTextSize;
    //绘制的范围
    private Rect mBound;
    private Paint mPaint;
    private int start = 0;
    int width;
    int height;
    private int textWidth;
    private int textHeight;

    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);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyTextView);
        try {
            mText = a.getString(
                    R.styleable.MyTextView_text);
            mTextColor = a.getColor(
                    R.styleable.MyTextView_textColor, 0xffc6c6c6);
            mTextSize = (int) a.getDimensionPixelSize(R.styleable.MyTextView_textSize, (int) TypedValue.applyDimension(
                    TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
            Log.d("MyTextView",  "--textSize--" + mTextSize);
        } finally {
            a.recycle();
        }
        init();
    }

    /**
     * 初始化数据
     */
    private void init() {
        Log.i(TAG, "init :" );
        //初始化Paint数据
        mPaint = new Paint();
        mPaint.setColor(mTextColor);
        mPaint.setTextSize(mTextSize);
        //获取绘制的宽高

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.i(TAG, "onMeasure :" );
        mBound = new Rect();
        mPaint.getTextBounds(mText, 0, mText.length(), mBound);
        width = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        height = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(widthMeasureSpec);
//        Log.d("MyTextView", "------mBound.height-----" + mBound.height() + "---paint width-----" + mPaint.measureText(mText));
        if (widthMode == MeasureSpec.AT_MOST) {
            textWidth = (int) (getPaddingLeft() + getPaddingRight() +mPaint.measureText(mText));
        } else {
            textWidth = width;
        }
        if (heightMode == MeasureSpec.AT_MOST) {

            Paint.FontMetrics fm = mPaint.getFontMetrics();

            textHeight = (int) (getPaddingBottom() + getPaddingTop() + fm.bottom-fm.top);
//            Log.d("MyTextView", "------fm.ascent+" + fm.ascent + "---paint height+++" + fm.descent);
        } else {
            textHeight = height;
        }
        setMeasuredDimension(textWidth, textHeight);

//        Log.d("MyTextView", "------textWidth-----" + textWidth + "---textHeight-----" + textHeight);
    }

    @Override
    protected void onDraw(Canvas canvas) {
//        Log.i(TAG, "onDraw :"  + getPaddingTop());
        String subText = mText.substring(0, start);
        String subTextColor = mText.substring(start, mText.length());
        Paint.FontMetrics fm = mPaint.getFontMetrics();
        //绘制文字
        canvas.drawText(subText,0,getHeight() / 2 -fm.descent + (fm.bottom - fm.top)/2 , mPaint);

        canvas.save();
        mPaint.setColor(mTextColor);
        canvas.translate( mPaint.measureText(subText), 0);

        canvas.drawText(subTextColor, 0, getHeight() / 2 -fm.descent + (fm.bottom - fm.top)/2  , mPaint);
        canvas.restore();
        //注意一下我们这里的getWidth()和getHeight()是获取的px
        Log.i(TAG, "fm():" +(fm.bottom - fm.top));
    }


    public void setmText(String text) {
        this.mText = text;
//        invalidate();
    }

    public void setStart(int start) {
        this.start = start;
//        invalidate();
    }

    public void setTextColor(int color) {
        this.mTextColor = color;
        Log.i(TAG, "onDraw :setTextColor" );
//        requestLayout();
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        Log.i(TAG, "onFinishInflate :" );

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        Log.i(TAG, "onSizeChanged :" );
    }
}
 
 

好了,根据那些老鸟的方法写出来了,那么运行一下看看结果。 为了更好的查看效果,加上原生TextView做对比

这里写图片描述

很明显可以看出自定义的宽度小了,高度也不够,宽高文字都不能完整的绘制。

获取很多人看到这个会觉得奇怪,以前没有发现这种效果,因为这里宽高设置为wrap_content,并且没有padding,如果设置了padding或许很难看出这些细微的效果,因此很多开发者以为这就是满意的效果了。

 
 

2.绘制水平,垂直居中文本

之前我也以为绘制文本嘛,再简单不过的啦,深入研究一下才发现,哎哟,有文章哦。

OK,说一下解决思路吧。上图所示,宽高都出现了问题,都偏小了。这里宽度问题比较容易解决,高度才比较麻烦。

2.1宽度偏小

宽度偏小是因为文字测量出现了误差, 
原始方式,这是一种粗略的文字宽度计算

value = mBound.width();
  • 1

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

value = mPaint.measureText(mText);
  • 1

开发者可以自行打印对比一下 mBound.width(); 和 mPaint.measureText(mText); 的值。

这里写图片描述

上图中,第1个是原生TextView,第2个是修改的过的,第三个是没有修改的,明显看到宽度已经和原生一样,

而且最后一个文字也完整绘制出来了。第三个可以

2.2高度偏小

高度偏小就比较麻烦了。不是一行代码可以解决的了 
先了解一下Android是怎么样绘制文字的,这里涉及到几个概念,分别是文本的top,bottom,ascent,descent,baseline。 
看下面的图(摘自网络):

这里写图片描述

解释一下这张图片。(摘自网络) 
Baseline是基线,在Android中,文字的绘制都是从Baseline处开始的,Baseline往上至字符“最高处”的距离我们称之为ascent(上坡度),Baseline往下至字符“最低处”的距离我们称之为descent(下坡度);

 leading(行间距)则表示上一行字符的descent到该行字符的ascent之间的距离; 
  
 top和bottom文档描述地很模糊,其实这里我们可以借鉴一下TextView对文本的绘制,TextView在绘制文本的时候总会在文本的最外层留出一些内边距,为什么要这样做?因为TextView在绘制文本的时候考虑到了类似读音符号,下图中的A上面的符号就是一个拉丁文的类似读音符号的东西:

这里写图片描述

Baseline是基线,Baseline以上是负值,以下是正值,因此 ascent,top是负值, descent和bottom是正值。 
OK,知道了这几个概念之后就开始想想要怎么修改了。

我们先修改高度偏小的问题 
原始代码,

value = mBound.height();
  • 1

修改后代码

FontMetrics fontMetrics = mPaint.getFontMetrics();
value = Math.abs((fontMetrics.bottom - fontMetrics.top));
  • 1
  • 2

结合图一,bottom和top相减的绝对值就是view的高度height。注意:Baseline以上是负值,以下是正值

这里写图片描述

OK,高度和宽度大小和原生的大小一样了,那么现在怎么使得文字垂直居中呢?

查阅了网上资料和测试了多次的结果得出一个计算 Y 值的计算公式:

FontMetricsInt fm = mPaint.getFontMetricsInt();

int startY = getHeight() / 2 - fm.descent + (fm.bottom - fm.top) / 2;

int startY = getHeight() / 2 - fm.descent + (fm.descent - fm.ascent)/ 2;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

getHeight():控件的高度

getHeight()/2-fm.descent:意思是将整个文字区域抬高至控件的1/2

+ (fm.bottom - fm.top) / 2:(fm.bottom - fm.top)其实就是文本的高度,意思就是将文本下沉文本高度的一半

  • 执行:getHeight()/2-fm.descent , 将整个文字区域抬高至控件的1/2

这里写图片描述

  • 执行: + (fm.bottom - fm.top) / 2 , 将文本下沉文本高度的一半

这里写图片描述

总结下 Canvas 的translate(x,y)方法  它是把画布的原点移动到点x,y  假设canvas是一个矩形 ,那就是画布的这个矩形的原点整体移动到x,y






猜你喜欢

转载自blog.csdn.net/xuyao625010693/article/details/80817607