Android自定义带标题边框的Layout

本文介绍了如何在Android中自定义一个带有标题和边框的Layout。通过理解自定义View的步骤,包括在attrs.xml中添加属性、构造方法中获取属性值、重写onMeasure和onDraw方法,作者详细讲解了实现目标效果的全过程。在实践中遇到了Canvas.drawText()的使用技巧和长度单位认识的问题,并给出了解决子View覆盖边框的两种方法。

前言:Android提供了一些方便使用的组件:TextView等,但是很多时候,默认的组件不能满足需要,因此,必需掌握“自定义组件”的能力。对于程序员,或者其它职业,应当直面自己缺失的东西,通过这种方式,掌握新知识,新技能,这样子,才能在快速迭代的社会里,不被淘汰。


其实对于自定义View,现在我还停留在理解理论的层面,知道大概的步骤:

1、在value/attrs.xml里,添加自定义View需要用到的一些变量属性,可以在布局文件里和代码里,给这些属性赋值;

2、在自定义View的构造方法里,获取属性的赋值(方便后面步骤使用);

3、根据需要,在onMeasure方法里,重新测量View的一些尺寸;

4、重写onDraw;


于是,我找来一些自定义的博客,跟着敲代码,通过实操,深化自定义View的过程和期间遇到的问题。

下面是原文路径:http://blog.youkuaiyun.com/xuwei527/article/details/11993103


期间,遇到的问题有:

1、Canvas.drawText()绘制文字的注意事项;

2、长度单位的认识;


那么开始进入正题。

目标实现的效果:给Layout添加外边框,和标题。



步骤:

1、添加属性

这里直接使用了上面博主的,其中"titleTextColor"属性,因与系统定义的冲突,所以改名为“titleTextColor1”

<declare-styleable name="TitleBorderLayout">
        <!-- The title of BorderTitleLayout. -->
        <attr name="title" format="string" />
        <!-- The size of title. -->
        <attr name="titleTextSize" format="dimension" />
        <!-- The title start postion. -->
        <attr name="titlePosition" format="dimension" />
        <!-- The color of title. -->
        <attr name="titleTextColor1" format="reference|color" />
        <!-- The size of border. -->
        <attr name="borderSize" format="dimension" />
        <!-- The color of border. -->
        <attr name="borderColor" format="reference|color" />
</declare-styleable>

2、在构造方法里,加载属性赋值

加载属性赋值时,需要根据属性值的格式,选择取值的方法,需要注意的是单位。

public TitleBorderLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        setWillNotDraw(false);

        mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        Resources res = getResources();
        mTextPaint.density = res.getDisplayMetrics().density;

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TitleBorderLayout);

        mTitle = a.getText(R.styleable.TitleBorderLayout_title);
        int titleColor = a.getColor(R.styleable.TitleBorderLayout_titleTextColor1, DEFAULT_TITLE_COLOR);
        mTextPaint.setColor(titleColor);

        titleTextSize = a.getDimension(R.styleable.TitleBorderLayout_titleTextSize, 0);
        if (titleTextSize > 0) {
            mTextPaint.setTextSize(titleTextSize);
        }

        mTitlePosition = a.getDimensionPixelSize(R.styleable.TitleBorderLayout_titlePosition, -1);

        mBorderSize = a.getDimensionPixelSize(R.styleable.TitleBorderLayout_borderSize, DEFAULT_BORDER_SIZE);

        int borderColor = a.getColor(R.styleable.TitleBorderLayout_borderColor, DEFAULT_BORDER_COLOR);
        mBorderPaint.setColor(borderColor);

        a.recycle();
}


3、重写onDraw

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

        Paint.FontMetrics fm = mTextPaint.getFontMetrics();
        final float titleHeight = fm.bottom - fm.top;

        final CharSequence titleText = (mTitle == null) ? "" : mTitle;
        final float titleWidth = Layout.getDesiredWidth(titleText, mTextPaint);

        final int width = getWidth();
        final int height = getHeight();


        if (mTitlePosition <= 0 || mTitlePosition + titleWidth > width) {
            mTitlePosition = (int) (DEFAULT_TITLE_POSITION_SCALE * width);
        }

        final float topBorderStartY = titleHeight / 2f - mBorderSize / 2f;

        mBorderPaneHeight = (int) Math.ceil(height - topBorderStartY);
        /* 画标题边框*/
        // 上
        canvas.drawRect(0, topBorderStartY, mTitlePosition, topBorderStartY + mBorderSize, mBorderPaint);
        canvas.drawText(titleText.toString(), mTitlePosition, - fm.top , mTextPaint);

        canvas.drawRect(mTitlePosition + titleWidth, topBorderStartY, width, topBorderStartY + mBorderSize, mBorderPaint);
        // 左
        canvas.drawRect(0, topBorderStartY + mBorderSize, mBorderSize, height, mBorderPaint);
        // 右
        canvas.drawRect(width - mBorderSize, topBorderStartY + mBorderSize, width, height, mBorderPaint);
        // 下
        canvas.drawRect(mBorderSize, height - mBorderSize, width - mBorderSize, height, mBorderPaint);
}

这里实现了标题和边框的绘制。其中重点说明一下文字的绘制。

Canvas提供drawText方法来绘制文字,具体的文档说明如下:


参数“text”是将要绘制的文字;参数“paint”是绘制文字所使用的画笔,一些文字属性如尺寸,颜色,对齐方式,都可能调用paint的方法来实现;

参数“x”是文字开始绘制的x坐标,最后参数“y”是最重要的,它并不是文字的开始绘制的y坐标,而是定义为baseline的y坐标值。

如果想在矩形中心绘制文字的话,有些人的做法是:


①获取文字所在的外框矩形Bound;②绘制文字时,使用Bound的高度计算“y”的参数值

可是得到的实际效果是:文字的位置偏低了。


我们来画图分析一下:



Baseline位于Hello的底部边缘的地方,其下面区域(Baseline到Descent之间)可能还存在字符其他的部份,如:jpg字符。

在文字的坐标系统时,BaseLine的值为0,Descent为正数值,Ascent为负数值,如果想得到文字的高度,应该为Descent - Ascent。


文字画笔提供了获取这些数值的方法:

Paint.FontMetrics fm = mTextPaint.getFontMetrics();
final float titleHeight = fm.descent - fm.ascent;


查看FontMetrics的代码,可以看到,其实在Descent下面还有bottom,在Ascent的上面还有top,关于这两个参数的说明,我不是很理解,大概是边缘吧,所以在获取文字的高度时,我是这么做的:

Paint.FontMetrics fm = mTextPaint.getFontMetrics();
final float titleHeight = fm.bottom - fm.top;


那么针对上面的,在矩形的中心绘制文字时,y参数该如何取值呢?



(rectHeight - titleHeight) / 2 - top.

需要注意top为负数值.


4、修改构造方法

当我跟着博主的步骤时发现,自定义的View并没有对添加标题,边框后带来的影响做处理?

就是如下图的:



存在的问题:①自定义的View,其子View覆盖在开始的地方;②子View覆盖掉边框


解决方法有两种:①在布局文件里,给自定义的View设置与titleTextSize相同大小的paddingTop,与borderSize相同大小的paddingLeft,paddingRightt和paddingBottom; 

②在自定义View的文件里,重新测量padding。


如果使用①的方法,每次使用的时候,都添加这些属性,这显得有些繁琐,编写代码,不就是为了偷懒么!!这并非首选方案。

下面贴上代码:

<pre style="font-family: 宋体; font-size: 9pt; background-color: rgb(255, 255, 255);">
<pre name="code" class="java">public TitleBorderLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        setWillNotDraw(false);

        mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        Resources res = getResources();
        mTextPaint.density = res.getDisplayMetrics().density;

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TitleBorderLayout);

        mTitle = a.getText(R.styleable.TitleBorderLayout_title);
        int titleColor = a.getColor(R.styleable.TitleBorderLayout_titleTextColor1, DEFAULT_TITLE_COLOR);
        mTextPaint.setColor(titleColor);

        titleTextSize = a.getDimension(R.styleable.TitleBorderLayout_titleTextSize, 0);
        if (titleTextSize > 0) {
            mTextPaint.setTextSize(titleTextSize);
        }

        mTitlePosition = a.getDimensionPixelSize(R.styleable.TitleBorderLayout_titlePosition, -1);

        mBorderSize = a.getDimensionPixelSize(R.styleable.TitleBorderLayout_borderSize, DEFAULT_BORDER_SIZE);

        int borderColor = a.getColor(R.styleable.TitleBorderLayout_borderColor, DEFAULT_BORDER_COLOR);
        mBorderPaint.setColor(borderColor);

        a.recycle();
        int paddingStart = getPaddingStart();
        int paddingTop = getPaddingTop();
        int paddingEnd = getPaddingEnd();
        int paddingBottom = getPaddingBottom();
        int titleHeightPixel = (int) ((mTextPaint.getFontMetrics().bottom - mTextPaint.getFontMetrics().top));
        paddingTop = paddingTop +  titleHeightPixel;
        paddingStart = paddingStart + mBorderSize;
        paddingEnd = paddingEnd + mBorderSize;
        paddingBottom = paddingBottom + mBorderSize;
        setPadding(paddingStart, paddingTop, paddingEnd, paddingBottom);
}





                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值