自定义控件是开发中不可忽视的一项技能,现在从最基本的自定义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的绘制过程: