onLayout
从源码看起:在performTraversals方法中首先调用了performMeasure,接下来便调用了performLayout。
performLayout(lp, mWidth, mHeight);```
在performLayout方法中调用了layout方法:
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());```
host就是一个View对象,进入View源码,找到layout方法:
public void layout(int l, int t, int r, int b) {
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}```
setFrame方法确定了子view在父view中的上下左右四个位置。在View里onLayou方法是一个空的实现。因为View的位置是由其父View决定的,所以onLayout方法应该在ViewGroup里实现。
@Override
protected abstract void onLayout(boolean changed,int l, int t, int r, int b);
可以看到,在ViewGroup方法里onLayout方法是一个抽象类,这是因为有各种不同的ViewGroup,比如LinearLayout,RelativeLayout等,layout规则不一样,所以需要具体不同的布局自己实现。这是LinearLayout的实现。
//伪代码
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
void layoutVertical(int left, int top, int right, int bottom) {
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}```
最后setChildFrame又调用view的layout方法。
实战
自定义一个ViewGroup
public class NewViewGroup extends ViewGroup {
public NewViewGroup(Context context) {
super(context);
}
public NewViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec,heightMeasureSpec);
}
@Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
int childCount = getChildCount();
int mTop = 0;
for (int a=0;a<childCount;a++){
View view = getChildAt(a);
int height = view.getMeasuredHeight();
int widht = view.getMeasuredWidth();
view.layout(0,mleft,widht,height+mTop);
mTop += height;
}
}
}
重写onMeasure,测量子View大小,否则不显示,在onLayout方法里遍历子view并为每一个view设置布局。布局里有三个button。显示结果如下:
getMeasureWidth,getMeasureHeight与getWidth,getHeight的区别。
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
public final int getMeasuredWidth() {
return mMeasuredWidth;
}
public final int getMeasuredHeight() {
return mMeasuredHeight;
}```
getMeasureXXX和getXXX的区别在于,getMeasureXXX是在onMeasure之后确定的,而getXXX是在onLayout之后确定的。一般情况下的到的值是一样样的,但重写了layout方法,并修改了layout里的值的情况除外,比如:
//这里没有调用onLayout是因为在View中onLayout是一个空实现,关键代码在layout实现
@Override
public void layout(int l, int t, int r, int b) {
super.layout(l, t+10, r, b);
}```
这个时候getMeasureHeight和getHeight方法得到的数值不一样。
在Activity中获取view宽高时得到的数据为0
因为Activity的生命周期和view的创建周期不一致,在onCreate方法中调用getMeasureXXX或getXXX可能view还没有绘制完成。
解决方案:
onWindowFocusChanged
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus){
Log.d("-----------",mNewView.getHeight()+"");
Log.d("***********",mNewView.getMeasuredHeight()+"");
}
}```
* view.post()
mNewView.post(new Runnable() {
@Override
public void run() {
Log.d("-----------",mNewView.getHeight()+"");
Log.d("***********",mNewView.getMeasuredHeight()+"");
}
});
* ViewTreeObserver
ViewTreeObserver observer = mNewView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
@Override
public void onGlobalLayout() {
//只兼容api16以上的 mNewView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
Log.d("-----------", mNewView.getHeight() + "");
Log.d("***********", mNewView.getMeasuredHeight() + "");
}
});
* view.measure
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec,heightMeasureSpec);
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
不能用于match_parent。