关于自定义View思考与实践1

自定义view可继承自View,也可以继承自View的一些子类,如TextView、EditView等,编写一个自定义View,首先需要实现构造方法:XXX(Context context)是必须的,如需要在xml中引用的方式,则需要生成XXX(Context context, AttributeSet attrs)构造方法,原因是android自带的属性如layout_width、layout_height、id、margin等需要用AttributeSet类型的签名来解析。

具体绘画过程在OnDraw(Canvas canvas)去做,canvas相当于画布,Paint相当于画笔,画笔中有个属性是ANTI_ALIAS_FLAG,表示抗锯齿,重点关注下。如果想重绘View,Android提供了invalidate()方法,当然这个方法必须运行在UI线程,如果需要在线程(非UI线程)中刷新View,那你就需要调用postInvalidate()

自定义View中如果想控制控件的大小,就需要重载onMeasure(int widthMeasureSpec, int heightMeasureSpec),如果没有,该控件将按照根视图的大小布局(即全屏)。实现该方法必须用到的MeasureSpec类不得不说,有三个常量,其中UNSPECIFIED表示未指定,父容器不对子容器做任何限制,想要多大就多大;EXACTLY表示完全的,父容器已经给你指定好了你该多大了;AT_MOST表示至多,父容器设置了个最大值,子容器不能超过此值。

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

    int resultWidth = 0;
    int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
    int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);

    if (modeWidth == MeasureSpec.EXACTLY) {
        resultWidth = sizeWidth;
    } else {
        resultWidth = mBitmap.getWidth();

        if (modeWidth == MeasureSpec.AT_MOST) {
            resultWidth = Math.min(resultWidth, sizeWidth);
        }
    }

    int resultHeight = 0;
    int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
    int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);

    if (modeHeight == MeasureSpec.EXACTLY) {
        resultHeight = sizeHeight;
    } else {
        resultHeight = mBitmap.getHeight();
        if (modeHeight == MeasureSpec.AT_MOST) {
            resultHeight = Math.min(resultHeight, sizeHeight);
        }
    }

    setMeasuredDimension(resultWidth, resultHeight);
}

Android字体绘制是从Baseline处开始绘制的,Baseline往上至字符最高处的距离我们称之为ascent(上坡度),Baseline往下至字符最底处的距离我们称之为descent(下坡度),而leading(行间距)则表示上一行字符的descent到该行字符的ascent之间的距离。Baseline上方的值为负,下方的值为正。具体看图,两张图说明一切:
图例1
ViewGroup是一个View的容器,所谓容器,就是包含多个View,例如一个LinearLayout布局中包含了一个自定义的CustomView,一个Button,一个TextView,那我们就需要继承自ViewGroup了,继承此类必须实现一个方法,即onLayout(boolean changed, int l, int t, int r, int b),用来确定各个子View显示的位置,当然onMeasure()方法也是必须有的,用来确定各个子View的大小。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        /*
         * 如果有子元素
         */
        if (getChildCount() > 0) {
            // 那么对子元素进行测量
            measureChildren(widthMeasureSpec, heightMeasureSpec);
        }
    }
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {

    /*
     * 如果有子元素
     */
    if (getChildCount() > 0) {
        // 声明一个临时变量存储高度倍增值
        int mutilHeight = 0;

        // 那么遍历子元素并对其进行定位布局
        for (int i = 0; i < getChildCount(); i++) {
            // 获取一个子元素
            View child = getChildAt(i);

            // 通知子元素进行布局
            child.layout(0, mutilHeight, child.getMeasuredWidth(), child.getMeasuredHeight() + mutilHeight);

            // 改变高度倍增值
            mutilHeight += child.getMeasuredHeight();
        }
    }
}

至此,一个不完善的自定义View完成,能显示一个简单的纵向的LinearLayout布局。但是尚未考虑到子元素在定位时候受到父容器内边距的影响(即Padding)。改进方案如下面代码所示:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int parentPaddingLeft = getPaddingLeft();
    int parentPaddingTop = getPaddingTop();

    if (getChildCount() > 0) {
        ...
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            child.layout(parentPaddingLeft, parentPaddingTop, child.getMeasuredWidth() + parentPaddingLeft, child.getMeasuredHeight() + parentPaddingTop);
            ...
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值