自定义View中文本居中显示

在开发中遇到一个这样的需求,在选择星期时设计狮想要这个这样的效果:


自定义View中文本居中显示_第1张图片


看到效果,想当然应该是用CheckBox实现吧,选中时背景蓝色文字白色,未选中时背景透明文字黑色。
那最方便的实现方式应该是直接给切图,然而这种用切图,除了因为要做国际化图片量会很大外,另一个原因就是显示效果也不是非常好。

另外一种就给CheckBox定义样式,实现思路应该是下面这个样子(样式代码见底部):
1,设置背景drawable,用 selector.xml来控制它的选中状态
2,文字用/color文件夹下的drawable来实现,设置文字的选择状态的颜色

-----------------------------------------------------------------------------------------------------------------------------------

以上比较瞎扯,最终我是选择用自定义View继承CheckBox实现的,然而遇到的效果是这的:

自定义View中文本居中显示_第2张图片

“一”怎么了,为什么是在上面显示并没有对齐,而且旁边的“日”也没有居中显示。关键代码实现如下:

        if (isChecked()) {
            // 绘制背景圆
            canvas.drawCircle(centerX, centerY, centerX-mPadding, mBgPaint);
            // 设置文字画笔颜色
            mFontPaint.setColor(mFontCheckColor);
            // 获取文字TEXT,以及文字的Rect,来得到文字的高度和宽度
            String text = getText().toString();
            Rect fontRect = new Rect();
            mFontPaint.getTextBounds(text, 0, text.length(), fontRect);
            // 设置文字左下角的起始点坐标,绘制文字
            canvas.drawText(text, centerX-fontRect.width()/2, centerY+fontRect.height()/2, mFontPaint);
        }


由此猜测应该是 getTextBounds()获取文字的RectA与实际的drawText时文字的RectB不是一样的,先看看RectA的大小,
给出三个String,“日”,“一”,“二一”,如下图:
自定义View中文本居中显示_第3张图片

得到如下信息:
1,“日”的Baseline与RectA.bottom基准线不一致, drawText()方法要设置的Y轴坐标是绘制文本Baseline的坐标,并不是文字最底部的坐标。
自定义View中文本居中显示_第4张图片

2,中间的“一”文本之所以没有显示,就可以在第三个文本“二一”中找到:
从“二一”中可以看出,“一”的底部距离Baseline有比较大的偏差,所以中间的会显示为空白。也就明白了最上面“一”显示偏上的问题。
绘制getTextBounds()获取Text的Rect的代码如下:

    @Override
    protected void onDraw(Canvas canvas) {
        // 设置文字画笔颜色
        mFontPaint.setColor(mFontCheckColor);
        // 获取文字TEXT,以及文字的Rect,来得到文字的高度和宽度
        String text = getText().toString();
        Rect fontRect = new Rect();
        mFontPaint.getTextBounds(text, 0, text.length(), fontRect);


        fontRect.offset(0, -fontRect.top);
        canvas.drawText(text, 0, fontRect.bottom, mFontPaint);

        Paint linePaint = new Paint();
        linePaint.setStyle(Paint.Style.STROKE);
        linePaint.setColor(Color.RED);
        linePaint.setStrokeWidth(2);
        canvas.drawRect(fontRect, linePaint);
    }


参考: http://stackoverflow.com/questions/7549182/android-paint-measuretext-vs-gettextbounds

-----------------------------------------------------------------------------------------------------------------------------------
要想让文本居中显示,使用 getFontMetrics()方法获取要绘制文字的高度。

1,getFontMetrics(),getFontMetricsInt()

getFontMetrics(),getFontMetricsInt()用于返回字符串的测量,而两个方法的区别就是返回值得类型。返回值一共有五个属性:

  • Top : baseline到文本顶部的最大的距离
  • Ascent : baseline到文本顶部的推荐距离
  • Descent : baseline到文本底部的推荐距离
  • Bottom : baseline到文本底部的最大距离
  • Baseline : 两行文本之间推荐的额外距离,一般为0

自定义View中文本居中显示_第5张图片


在Android的坐标系之中,向下为Y轴正方向,向上位Y轴负方向,所以baseline之上的Top与Ascent都是负数,而baselin之下的Descent、Bottom都是正数。如果要让字符串在垂直方向上居中,则需要在纵坐标上增加Asecent绝对值与descent的差。 

2、setTextAlign

setTextAlign可以设置画笔绘制文本的对齐方式,选择center即可完成文本的水平居中。

mPaint.setTextAlign(Paint.Align.CENTER);

3、多行文本居中,代码如下:

private void textCenter(String[] strings, Paint paint, Canvas canvas, Point point,Paint.Align align){
    mPaint.setTextAlign(align);
    Paint.FontMetrics fontMetrics= mPaint.getFontMetrics();
    float top = fontMetrics.top;
    float bottom = fontMetrics.bottom;
    int length = strings.length;
    float total = (length-1)*(-top+bottom)+(-fontMetrics.ascent+fontMetrics.descent);
    float offset = total/2-bottom;
    for (int i=0; i

  • 1、drawText每次只能绘制一行,所以我们有个for循环
  • 2、更具上图,假设字符串数组大于1,每个字符串的高度应该为-top+bottom,总高度就为length*(-top+bottom);
  • 3、偏移量就等于length*(-top+bottom)/2-bottom;
  • 4、如果顺序向上(即Y负方向)排列的话,那么第i个字符串的高度就是-(length-i-1)*(-top+bottom);
  • 5、每个字符串高度的减去偏移量,就应该是每个字符串的baseline的Y坐标 


原文链接:http://gold.xitu.io/entry/574d53d1128fe10055f1eccd


-----------------------------------------------------------------------------------------------------------------------------------

最终实现星期选择效果的代码如下:

public class FontBgCheckBox extends CheckBox {

    private Paint mBgPaint;
    private Paint mFontPaint;

    private int mFontCheckColor, mFontUncheckColor, mBgCheckColor;
    private int mPadding;

    public FontBgCheckBox(Context context, AttributeSet attrs) {
        super(context, attrs);

        mFontCheckColor = 0xFFFFFFFF;
        mFontUncheckColor = Color.parseColor("#FF616161");
        mBgCheckColor = Color.parseColor("#FF50BDE7");
        mPadding = dp2px(6, getContext());

        mBgPaint = new Paint();
        mBgPaint.setColor(mBgCheckColor);
        mBgPaint.setAntiAlias(true);

        mFontPaint = new Paint();
        mFontPaint.setTextSize(dp2px(16, getContext()));
        mFontPaint.setAntiAlias(true);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;
        if (isChecked()) {
            canvas.drawCircle(centerX, centerY, centerX - mPadding, mBgPaint);
            mFontPaint.setColor(mFontCheckColor);
            String text = getText().toString();
            textCenter(new String[]{text}, mFontPaint, canvas, centerX, centerY, Align.CENTER);
        } else {
            mFontPaint.setColor(mFontUncheckColor);
            String text = getText().toString();
            textCenter(new String[]{text}, mFontPaint, canvas, centerX, centerY, Align.CENTER);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(widthMeasureSpec, widthMeasureSpec);
    }

    public static int dp2px(float value, Context context) {
        final float scale = context.getResources().getDisplayMetrics().densityDpi;
        return (int) (value * (scale / 160) + 0.5f);
    }
}

xml布局文件引用示例如下:



        
        
        ……
        


使用样式的方式完成如上效果的代码如下:







    
    




    
        
            
            
        
    





你可能感兴趣的:(Android)