您的位置:首页 > 其它

个人进阶之路——自定义控件(2)

2016-02-24 00:55 309 查看
今天继续昨天没有完成的内容。

昨天写得到怎么定义并使用

今天要去别人那里总结一下了。

**onMeasure()决定——View本身大小多少

onLayout()决定——View在ViewGroup中的位置如何

onDraw()决定——如何绘制该View**

首先是onMeasure方法,我们知道,系统帮我们测量的宽高都为match_parent,如果没有明确宽高时,系统帮我们测量的结果就是咱设置的结果,当我们设置为wrap_content之后或者match_parent时,系统帮我们测量的结果就是match_parent的长度。所以当我们设置为wrap_content时,要自己进行测量,即重写onMeasure方法。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

}

重写onMeasure(),并调用父类onMeasure()时

自定义View的layout_width以及layout_height属性值 match_parent 或者 wrap_content显示大小由其父容器控件决定。

自定义View设置为固定的值,就显示该设定的值

MeasureSpec的specModed三种类型:

**EXACTLY:设置明确值 or MATCH_PARENT

AT_MOST:子布局限制在一个最大值内,一般为WARP_CONTENT

UNSPECIFIED:子布局想多大就多大,很少使用**

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);

int width;
int height;
...
if (widthMode == MeasureSpec.EXACTLY) {
// Parent has told us how big to be. So be it.
width = widthSize;
} else {
if (mLayout != null && mEllipsize == null) {
des = desired(mLayout);
}
...
setMeasuredDimension(width, height); //此前做的所有操作都是为了服务于它
}


· 理解这一段,widthMeasureSpec和 heightMeasureSpec两参数是从vg中传入,因为onMeasure()由包含该View的具体ViewGroup所调用,子类View的这两参数,由vg中的width,height和padding以及View自身margin共同决定。weight也需考虑(较复杂)

· 两参数作用:取heightMeasureSpec作说明,该值由高32位(specMode,可由MeasureSpec.getMode()获取)和低16位(specSize,可由MeasureSpec.getSize获取)组成。

· 所有的View的onMeasure()最后一行都会调用setMeasureDimension()函数——该函数调用中传进去的值是View最终的视图大小,之前所有工作为了最后这一句话服务的。

vg中,给View分配的空间大小不确定,可能随具体变化而变化,变化条件是传到specMode中决定

specMode有三种可能:

**·**EXACTLY:父视图希望子视图的大小应该是specSize中指定的。

**·**AT_MOST:子视图的大小最多是specSize中指定的值,不建议子视图的大小超过specSize中给定的值。

**·**UNSPECIFIED:可随意指定视图的大小。

**·**specMode能根据vg中具体能够提供的空间大小来指定子View的视图大小。

·视图最终的大小由父视图,子视图以及程序员根据需要决定

·良好的设计一般会根据子视图的measureSpec设置合适的布局大小。

·重写onMeasure()方法是为了自定义View尺寸的规则

·如果你的自定义View的尺寸是根据父控件行为一致,就不需要重写onMeasure()方法

举例几个重写onMeasure来看看

(1)例子1

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
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(mTitleTextSize);
mPaint.getTextBounds(mTitle, 0, mTitle.length(), mBounds);
float textWidth = mBounds.width();
int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
width = desired;
}

if (heightMode == MeasureSpec.EXACTLY){
height = heightSize;
} else{
mPaint.setTextSize(mTitleTextSize);
mPaint.getTextBounds(mTitle, 0, mTitle.length(), mBounds);
float textHeight = mBounds.height();
int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
height = desired;
}
setMeasuredDimension(width, height);
}


(2)例子2

//widthMeasureSpec 和 heightMeasureSpec的值 由父容器决定

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width  = measureDimension(DEFAULT_WIDTH, widthMeasureSpec);
int height = measureDimension(DEFAULT_HEIGHT, heightMeasureSpec);
setMeasuredDimension(width, height);
}


定义一个方法处理 widthMeasureSpec,heightMeasureSpec的值

private int measureHanlder(int measureSpec){
int result = defaultSize;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(defaultSize, specSize);
} else {
result = defaultSize;
}
return result;
}


说明:

MeasureSpec.getSize()会解析MeasureSpec值得到父容器width或者height。

MeasureSpec.getMode()会得到三个int类型的值分别为:EXACTLY ,AT_MOST,UNSPECIFIED。

MeasureSpec.UNSPECIFIED 未指定,故可以设置任意大小。

MeasureSpec.AT_MOST 自定义View可以为任意大小,但是有一个上限。

(3)例子3

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


private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else {
// Measure the text
result = (int) mTextPaint.measureText(mText) + getPaddingLeft()
+ getPaddingRight();
if (specMode == MeasureSpec.AT_MOST) {
// Respect AT_MOST value if that was what is called for by measureSpec
result = Math.min(result, specSize);
}
}
return result;
}


private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

mAscent = (int) mTextPaint.ascent();
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else {
// Measure the text (beware: ascent is a negative number)
result = (int) (-mAscent + mTextPaint.descent()) + getPaddingTop()
+ getPaddingBottom();
if (specMode == MeasureSpec.AT_MOST) {
// Respect AT_MOST value if that was what is called for by measureSpec
result = Math.min(result, specSize);
}
}
return result;
}


(4)例子4

//负责设置子控件的测量模式和大小 根据所有子控件设置自己的宽和高

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);      //获得父容器,设置测量模式和大小
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
// 如果是warp_content情况下,记录宽和高
int width = 0;
int height = 0;
int lineWidth = 0;//记录每一行的宽度,width不断取最大宽度
int lineHeight = 0;// 每一行的高度,累加至height
int cCount = getChildCount();
for (int i = 0; i < cCount; i++){// 遍历每个子元素
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);// 测量每一个child的宽和高
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();// 得到child的lp
// 当前子空间实际占据的宽高
int childWidth = child.getMeasuredWidth() + lp.leftMargin+ lp.rightMargin;
int childHeight = child.getMeasuredHeight() + lp.topMargin+ lp.bottomMargin;
// 如果加入当前child,则超出最大宽度,则的到目前最大宽度给width,类加height 然后开启新行
if (lineWidth + childWidth > sizeWidth) {
width = Math.max(lineWidth, childWidth);// 取最大的
lineWidth = childWidth;    // 重新开启新行,开始记录
height += lineHeight;      // 叠加当前高度
lineHeight = childHeight;  // 开启记录下一行的高度
} else{                        // 否则累加值lineWidth,lineHeight取最大高度
lineWidth += childWidth;
lineHeight = Math.max(lineHeight, childHeight);
}
// 如果是最后一个,则将当前记录的最大宽度和当前lineWidth做比较
if (i == cCount - 1){
width = Math.max(width, lineWidth);
height += lineHeight;
}

}
setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ? sizeWidth: width,
(modeHeight == MeasureSpec.EXACTLY) ? sizeHeight: height);
}


(5)例子5

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 设置宽度
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
if (specMode == MeasureSpec.EXACTLY){
mWidth = specSize;
} else{
int desireByImg = getPaddingLeft() + getPaddingRight() + mImage.getWidth();//图片决定宽
int desireByTitle = getPaddingLeft() + getPaddingRight() + mTextBound.width();//字体决定宽
if (specMode == MeasureSpec.AT_MOST) {
int desire = Math.max(desireByImg, desireByTitle);
mWidth = Math.min(desire, specSize);
}
}
// 设置高度
specMode = MeasureSpec.getMode(heightMeasureSpec);
specSize = MeasureSpec.getSize(heightMeasureSpec);
if (specMode == MeasureSpec.EXACTLY){
mHeight = specSize;
} else{
int desire = getPaddingTop() + getPaddingBottom()+
mImage.getHeight() + mTextBound.height();
if (specMode == MeasureSpec.AT_MOST){
mHeight = Math.min(desire, specSize);
}
}
setMeasuredDimension(mWidth, mHeight);
}


(6)例子6

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)   {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = Math.min(getMeasuredWidth(), getMeasuredHeight());
mRadius = width - getPaddingLeft() - getPaddingRight();          // 获取圆形的直径
mPadding = getPaddingLeft();                                    // padding值
mCenter = width / 2;                                             // 中心点
setMeasuredDimension(width, width);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: