常见的概念
-
xml:采用序列化,反射,通过 setContenView()方法绑定布局
-
LayoutPrames是什么?
解析后的布局参数 -
MearSureScap是什么?
是View中的内部类,基本都是二进制。由于int是32位的,用高两位表示mode
低30位表示size,MODE_SHIFT=30的作用是移位3种情况:
MearSureScap 的 mode | size含义 |
---|---|
UNSPECIFIED | 不对View大小做限制,系统使用 |
EXACTLY | 切确的大小,如100dp |
AT_MOST | 大小不超过某个值,如不超过 父容器 |
看上面的图不好理接,看下面的图你就懂这三种模式:
如图:
具体说明:
对于应用层 View ,其 MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 来共同决定 对于不同的父容器和view本身不同的LayoutParams,view就可以有多种MeasureSpec。
-
当view采用固定宽 高的时候,不管父容器的MeasureSpec是什么,view的MeasureSpec都是精确模式并且其大小遵循
Layoutparams中的大小; -
当view的宽高是match_parent时,这个时候如果父容器的模式是精准模式,那么
view也是精准模式并且其大小是父容器的剩余空间,如果父容器是最大模式,那么view也是最大模式并且其大 小不会超过父容器的剩余空间; -
当view的宽高是wrap_content时,不管父容器的模式是精准还是最大化,
view的模式总是最大化并且大小不能超过父容器的剩余空间。 -
Unspecified模式,这个模式主要用于系统内
部多次measure的情况下,一般来说,我们不需要关注此模式(这里注意自定义View放到ScrollView的情况 需要 处理)。
具体实现
- onMeasure()测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
initMeasureParams();//初始化每一行(子View和行高)
//度量子view的大小
//度量所有的子View
int childCount = getChildCount();
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
int selfWidth = MeasureSpec.getSize(widthMeasureSpec); //ViewGroup解析的宽度
int selfHeight = MeasureSpec.getSize(heightMeasureSpec); // ViewGroup解析的高度
List<View> lineViews = new ArrayList<>(); //保存一行中的所有的view
int lineWidthUsed = 0; //记录这行已经使用了多宽的size
int lineHeight = 0; // 一行的行高
int parentNeededWidth = 0; // measure过程中,子View要求的父ViewGroup的宽
int parentNeededHeight = 0; // measure过程中,子View要求的父ViewGroup的高
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
LayoutParams childLP = childView.getLayoutParams();
//将layoutParams转变为measureSpec
int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight,
childLP.width);
int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom,
childLP.height);
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
//获取子view的宽高
int childMesauredWidth = childView.getMeasuredWidth();
int childMeasuredHeight = childView.getMeasuredHeight();
//通过宽度来判断是否需要换行,通过换行后的每行的行高来获取整个viewGroup的行高
//如果需要换行
if (childMesauredWidth + lineWidthUsed + mHorizontalSpacing > selfWidth) {
allLines.add(lineViews);
lineHeights.add(lineHeight);
//注意重置不同生命周期的子view
//一旦换行,我们就可以判断当前行需要的宽和高了,所以此时要记录下来
parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;
parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing);
lineViews = new ArrayList<>();
lineWidthUsed = 0;
lineHeight = 0;
}
// view 是分行layout的,所以要记录每一行有哪些view,这样可以方便layout布局
lineViews.add(childView);
//每行都会有自己的宽和高
lineWidthUsed = lineWidthUsed + childMesauredWidth + mHorizontalSpacing;
lineHeight = Math.max(lineHeight, childMeasuredHeight);//以一行的最高为行高
//如果当前childView是最后一行的最后一个
if (i == childCount - 1) {//最后一行
lineHeights.add(lineHeight);
allLines.add(lineViews);
parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed);
parentNeededHeight += lineHeight;
}
}
//根据子View的度量结果,来重新度量自己ViewGroup
// 作为一个ViewGroup,它自己也是一个View,它的大小也需要根据它的父亲给它提供的宽高来度量
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth: parentNeededWidth;
int realHeight = (heightMode == MeasureSpec.EXACTLY) ?selfHeight: parentNeededHeight;
setMeasuredDimension(realWidth,realHeight);
}
- onLayout()测量
/**
将所有的子View布局到屏幕上,同时,由于FlowLayout自己没有特殊要求所以不需要对自己布局
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int lineCount = allLines.size();//有多少行
/**
* //左边界
*/
int curL = getPaddingLeft();
Log.d(TAG, "onLayout: getLeft(): " + getLeft());
/**
* 上边界
*/
int curT = getPaddingTop();
Log.d(TAG, "onLayout: getTop(): " + getTop());
for (int i = 0; i < lineCount; i++) {
List<View> lineViews = allLines.get(i);//一行有多少view
int lineHeight = lineHeights.get(i);
for (int j = 0; j < lineViews.size(); j++) {
View view = lineViews.get(j);
int left = curL;//左边界
int top = curT;//右边界
/**
* getHeight() 在layout()结束之后才能获取到—通过视图右边的坐标减去做百年的左边计算出来的
*/
// int bottom = top + view.getHeight();
// int right = left + view.getWidth();
/**
* getMeasureHeight()在onmesure()过程结束后就能获取到—通过setMeasureDimension()方法来进行设置的
*/
int bottom = top + view.getMeasuredHeight();
int right = left + view.getMeasuredWidth();
view.layout(left,top, right,bottom);//对应上面的childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
curL = right + mHorizontalSpacing;
}
curL = getPaddingLeft();
curT = curT + lineHeight + mVerticalSpacing;
}
}
ViewPager的实现原理
- 后续的自定义实战请看下一篇博客: