关于onLayout的学习,也是在基于View视图树的递归调用实现。
本篇想说明的是,不去深究View源码关于onLayout,以及layout方法的实现原理。知道大概,目的是在会用。但是需要了解并掌握View内部关于onLayout方法的相关API使用。
在ViewGroup中,onLayout是一个抽象方法,所以如果继承了ViewGroup类,除了添加构造方法外,还必须要重写onLayout方法。
/**
* 当这个view和其子view被分配一个大小和位置时,被layout调用。
* @param changed 当前View的大小和位置改变了
* @param left 左部位置(相对于父视图)
* @param top 顶部位置(相对于父视图)
* @param right 右部位置(相对于父视图)
* @param bottom 底部位置(相对于父视图)
*/
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
此方法是在ViewGroup中的layout方法所调用:
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
而ViewGroup中的layout方法,是继承自View类当中的layout方法,然后在看一下
View类中layout的方法实现。
/**
* 给View和其所有子View分配大小和位置
*
* 这是布局的第二个阶段(第一个阶段是测量)。在这个阶段中,每个父视图需要去调用layout去为他所有的子视图确定位置
* 派生的子类不应该重写layout方法,应该重写onLayout方法,在onlayout方法中应该去调用每一个view的layout
*/
public void layout(int l, int t, int r, int b) {
// 将当前视图的左上右下记录为old值(参数中传入的为新的l,t,r,b值)
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
// setFrame方法的作用就是将新传入的ltrb属性赋值给View,然后判断当前View大小和位置是否发生了变化并返回
boolean changed = setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
// 调用onLayout回调方法,具体实现由重写了onLayout方法的ViewGroup的子类去实现(后面详细说明)
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
// 调用所有重写了onLayoutChange监听的方法,通知View大小和位置发生了改变
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);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
}
在这里边也有onLayout()方法,在View中的onLayout();中是一个实现了个空方法。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
关于layout,onLayout在View与ViewGroup中就这四个。
我们在ViewGroup中重写onLayout的目的:
就是设置当前View与其所有的子View,在ViewGroup(或其继承ViewGroup的Layout)父布局当中的位置。
在onLayout方法中,我们可以获取当前布局,在父布局当中的位置:
getLeft();//子View左边界到父view左边界的距离 getTop();//子View上边界到父view上边界的距离 getRight();//子View右边界到父View左边界距离 getBottom();//子View下边界到父View上边界距离。
还可以在onMeasure()方法执行后在onLayout方法中,测量出当前布局View,在父布局中的
宽度:getWidth();//在onLayout()方法中取得相对在父布局中的宽、高
高度:getHeight();//取值在getMeasuredHeight()方法之后。
此处要注意区分子View的
childView.getMeasuredWidth();//在onMeasure()方法之后取得View的实际宽、高
childView.getMeasuredHeight();
两个方法取值的区别就在如果View的大小没有超过父布局View的大小,二者是相等的。getWidth() == childView.getMeasuredWidth();
如果childView宽、高大于了父布局的宽、高 则:getWidth() + 超出区域 = childView.getMeasuredWidth();
来源:官方文档。详见jafsldkfj博客。
而计算viewChild在父布局中的位置,可以通过:
viewChild.layout();方法实现View的布局定位。
在ViewGroup中重写onLayout方法,在onLayout方法中对子View,viewChild.layout();布局定位,而layout中又调用了onLayout()方法
所以实现了View视图树递归调用的原理。跟onMeasure类似。
(在ViewGroup中,layout()方法是个final类型,所以不能被子类调用。在ViewGroup中,没有measure,onMeasure方法。)
(在View中,measure()方法是个final类型,也不能被子类调用。但是layout是一个普通方法,onLayout是一个实现了空方法。)
添加点一Tip:
① 在代码注释文档中/**{@hide}*/ 表示该方法不向子类开发,不提供外部使用。顺便可以学习下Java doc注释。
最终的学习,还是为了在项目中灵活运用,实践。