1.1.1 requestLayout
下面的文章对于requestLayout的基本流程讲解得比较清楚,本文结合最新的代码做进一步的细化分析:
http://www.xuebuyuan.com/2134865.html
1)
当一个View调用requestLayout的时候,会给当前的View设置一个FORCE_LAYOUT标记。由此向ViewParent请求布局。这样从这个View开始向上一直requestLayout。最终到达ViewRootImpl。ViewParent 就是当前的传输链。【参见职责链设计模式】
public void requestLayout() {
if(mMeasureCache != null) mMeasureCache.clear();
if(mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
//Only trigger request-during-layout logic if this is the view requesting it,
//not the views in its parent hierarchy
ViewRootImpl viewRoot = getViewRootImpl();
if(viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
mPrivateFlags |=PFLAG_FORCE_LAYOUT;
mPrivateFlags|= PFLAG_INVALIDATED;
if(mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
if(mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
[ 注] mParent
protectedViewParent mParent; 这是一个接口,由public final classViewRootImpl implements ViewParent,实现接口。
mParent的赋值在:
View.java(frameworks\base\core\java\android\view): void assignParent(ViewParent parent) {
ViewGroup.java(frameworks\base\core\java\android\view): child.assignParent(this);
ViewRootImpl.java(frameworks\base\core\java\android\view): view.assignParent(this);
ViewRootImpl.java(frameworks\base\core\java\android\view): mView.assignParent(null);
WindowManagerGlobal.java(frameworks\base\core\java\android\view): view.assignParent(null);
主要由ViewGroup. addViewInner进行赋值,即在添加View的时候将父View作为Parent赋值给childView,
private void addViewInner(View child, intindex, LayoutParams params,
boolean preventRequestLayout) {
…
// tellour children
if(preventRequestLayout) {
child.assignParent(this);
} else {
child.mParent = this;
}
public abstract class ViewGroup extends View implements ViewParent,ViewManager,可以看出VG是支持该类型的,可以对其赋值。虽然VG本身没有实现requestLayout(),但View实现了requestLayout(),但View却没有implements ViewParent,真是奇怪的关系和使用方法。
这种关系的结果,就是一直向上找根View,并requestLayout,根View就要分析DecorView的加载过程了。见其他章节。
2)
ViewRootImpl发现请求了布局。那么就会调用measure方法。
measure方法确认当前View是否有FORCE_LAYOUT标记。
如果有,那么就会进行重新measure。并且设置标记LAYOUT_REQUIRED。
public finalvoid measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
…
if(forceLayout || needsLayout) {
//first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 :mMeasureCache.indexOfKey(key);
if(cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
}
Android N相对于M,在onMeasure()部分做了优化,增加了判断条件,避免不需要的测量,同时会引起一些在N之前的不规范代码出现界面上的显示问题,如控件看不见,位置偏移等,这类问题我解决过多个。
【注】这里还有一个比较明显的问题,ViewRootImpl是如何发现布局请求的,如前,VG执行的是View的requestLayout,那么ViewRootImpl的requestLayout是在这个条件下是如何触发的呢?(后续分析)
3)
在随后的layout方法中,会判断这个标记。如果这个标记为true。
那么就一定会调用onLayout.
onLayout调用后清理LAYOUT_REQUIRED标记。
layout调用之后,会清理掉FORCE_LAYOUT标记。
public void layout(int l, int t, int r, intb) {
if((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL= mLeft;
int oldT= mTop;
int oldB= mBottom;
int oldR= mRight;
booleanchanged = 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
(ArrayList
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;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
当然在上述过程中,影响到了兄弟或者是父亲View的大小, 那么也兄弟或者是父亲View也会调用layout/onLayout。不管其是否已经调用requestLayout。如果说指定的MeasureSpec为此也发生了变化,那么measure/onMeasure也会被调用。
通过上述分析发现,只要调用了requestlayout, 那么measure和onMeasure,以及layout,onlayout,draw onDraw都会被调用。
在很多情况下,requestLayout是不需要被调用的。例如,我们把一个AbsoluteLayout里面的childView挪动一下位置。我们仅仅需要调用的可能就是重新布局当前AbsoluteLayout,然后调用invalidate方法进行重绘。而不是从当前View向上的整个View树形结构都要重新layout,onLayout,measure,onMeasure一次。
这个时候,怎么办?
一种方法是,直接调用onLayout。然后调用invalidate进行重绘。很明显可以提升绘制效率。由于父View的layout实现中对会通知布局的listener。但是由于无法得到listener,因此调用onlayout的时候无法对其进行通知,这也是这种实现的缺陷。