仿系统TextView自定义View

自定义控件是开发中不可忽视的一项技能,现在从最基本的自定义View出发,熟悉从attrs.xml中取自定义的值和测量布局宽高以及绘制。

先看效果,仿TextView的效果:

这里写图片描述

声明属性:

<!--声明属性的引用-->
<attr name="Text" format="string"/>
<attr name="TextColor" format="color"/>
<attr name="TextSize" format="dimension"/>

<!--定义需要使用的属性-->
<declare-styleable name="CustomView">
    <attr name="Text" />
    <attr name="TextColor"/>
    <attr name="TextSize"/>
</declare-styleable>

继承自View:

class SelfView extends View

在构造方法中获取声明的属性:

 //在这里取出attrs中定义的值
    TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CustomView);

    mText = ta.getString(R.styleable.CustomView_Text);
    mColor = ta.getColor(R.styleable.CustomView_TextColor, 0);
    mTextSize = ta.getDimension(R.styleable.CustomView_TextSize, 20);
    //回收
    ta.recycle();

在构造方法中测量文本内容的宽高:

//主要是为了获取文本的尺寸
    mPaint = new Paint();
    mPaint.setTextSize(mTextSize);
    //存放尺寸的盒子
    mRect = new Rect();
    mPaint.getTextBounds(mText,0,mText.length(),mRect);

在onDraw方法中进行内容的绘制:

//先画最下面的一层
    mPaint.setColor(Color.GRAY);
    /**
     * 左边距,上边距,右边距和下边距,画笔
     * 右边距就是宽,调用系统的测量方法
     * 下边距就是高,调用系统侧测量方法
     */
    canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),mPaint);

    //画上面的文字
    mPaint.setColor(mColor);
    /**
     * getWidth和getHeight获取的而是自定义的这个View的宽度和高度
     * mRect.width()和mRect.height()获取的是文本的宽和高
     */
    canvas.drawText(mText,getWidth()/2-mRect.width()/2,getHeight()/2+mRect.height()/2,mPaint);

在布局代码中进行使用:

<com.example.administrator.selfviewdemo01.views.SelfView
    android:layout_width="200dp"
    android:layout_height="140dp"
    android:id="@+id/selfView"
    custom:TextColor="#111fff"
    custom:TextSize="24sp"
    custom:Text="5437"/>

但是在上面的自定义控件中如果宽高是写的具体值和match_parent都没问题,但是熟悉如果是wrap_content就会绘制出match_parent的效果,导致控制宽高失控,这个时候就需要重写onMeasure这个方法了。

/**
     * 如果不自己测量,在设置属性为wrap_content时就会出现铺满父控件的现象,失控了
     *
     EXACTLY:一般是设置了明确的值或者是MATCH_PARENT
     AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT
     UNSPECIFIED:表示子布局想要多大就多大,很少使用
     */
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);

    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int width;
    int height;

    if (widthMode==MeasureSpec.EXACTLY){
        width=widthSize;
    }else {
        //测量出字体的宽度
        mPaint.setTextSize(mTextSize);
        mPaint.getTextBounds(mText,0,mText.length(),mRect);
        int textWidth = mRect.width();
        //获取两侧的内边距
        width=getPaddingLeft()+textWidth+getPaddingRight();
    }

    if (heightMode==MeasureSpec.EXACTLY){
        height=heightSize;
    }else {
        //测量出字体的高度
        mPaint.setTextSize(mTextSize);
        mPaint.getTextBounds(mText,0,mText.length(),mRect);
        int textHeight = mRect.height();
        //获取上下的内边距
        height=getPaddingTop()+textHeight+getPaddingBottom();
    }
    //使用测量好的宽高
    setMeasuredDimension(width,height);

为什么不重写onMeasure方法会在设置wrap_content时就会失控了呢?

在默认实现中,AT_MOST和EXACTLY两种模式都会被设置成specSize。我们也知道AT_MOST对应于wrap_content,而EXACTLY对应于match_parent和具体数值情况。也就说默认情况下wrap_content和match_parent是具有相同的效果的。那有人会问:wrap_content和match_parent具有相同的效果,为什么是填充父容器的效果呢?

下面讲一下View的绘制过程:

View的绘制首先起于ViewRootImpl,并且View的三个流程也是通过ViewRootImpl来完成的,在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建viewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。之后,View的绘制过程从ViewRootImpl的performTraversals方法开始,它经过mwasure、layout、draw三个过程最终将一个View绘制出来,由于DecorView是Android的一个页面的顶级View,所以绘制过程首先会从DecorView开始,又因为DecorView是一个ViewGroup,它会遍历绘制所有的子View,我们注意到在protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法中会有两个参数,其实这两个参数是在ViewGroup中传入进去的,最初是在DecorView中赋值的,且其值就是屏幕的宽度和高度。

源码下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值