什么情况下会onMeasure会执行?
进入View的measure方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void measure(){ boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT; boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec; boolean isSepcExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY; boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec); final boolean needsLayout = specChanged && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize); if(forceLayout || needLayout){ int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) { onMeasure(widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } else { long value = mMeasureCache.valueAt(cacheIndex); setMeasuredDimensionRaw((int) (value >> 32), (int) value); mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } } }
|
什么时候forceLayout=true:
- 调用
requestLayout
- 调用
forceRequestLayout
什么时候needsLayout=true:
- 当长宽发生改变
什么时候调用了onMeasure>方法:
forceLayouy=true
- 或者
mMeasureCache没有当前的缓存
所以总结:当调用了requestLayout一定会测发重测过程.当forceLayout=false的时候会去判断mMeasureCache值.现在研究下这个mMeasureCache
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class View{ LongSparseLongArray mMeasureCache; void measure(widthSpec,heightSpec){ --- long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL; int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key); if(cacheIndex<0){ onMeasure(widthSpec,heightSpec); } mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; mMeasureCache.put(key,widhSpec|heightSpec); --- } }
|
这里可以看到oldWidthMeasureSpec和mMeasureCache都是缓存上一次的值,那他们有什么不同呢?不同点就是,oldWidthMeasureSpec>不仅仅缓存了测量的spec模式而且缓存了size.但是mMeasureCache只缓存了size.从这行代码可以看出:
1
| long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
|
这里一同运算就为了排除掉spec造成的影响.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class Test { public static void main(String[] args) { long widthMeasureSpec = makeMeasureSpec(10,0); long heightMeasureSpec = makeMeasureSpec(20,0); long ss = widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL; System.out.println("=========="+ss); }
private static final int MODE_MASK = 0x3 << 30;
public static int makeMeasureSpec(int size, int mode) { return (size & ~MODE_MASK) | (mode & MODE_MASK); } }
|
什么时候mPrivateFlags会被赋值PFLAG_FORCE_LAYOUT.
在view viewGrouup的构造函数里面会主动赋值一次,然后在ViewGroup.addView时候会给当前View的mProvateFlags赋值PFLAG_FORCE_LAYOUT.
为什么onMeasure会被执行两次?
1 2 3 4 5 6 7 8 9 10 11 12 13
| void measure(int widthMeasureSpec,int heightMeasureSpec){ ---- boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT; if(forceLayout | needsLayout){ onMeasure() } ---- } public void layout(int l, int t, int r, int b){ --- mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; --- }
|
在第一次触发到measure方法时,forceLayoyt=true needsLayout=true,但是layout方法还没触发到.
在第二次触发到measure>方法时,forceLayout=true needsLayout=false,所以还是会进入onMeasure方法.这次会执行layout方法.然后我们在下次的时候forceLayout就等于false了.上面的这一段分析是分析的measure内部如何防止多次调用onMeasure.
现在分析外部是如何多次调用measure方法的:
在Activity执行到onResume生命周期的时候,会执行WindowManager.addView操作,WindowManager的具体实现类是WindowManagerImpl然后addView操作交给了代理类WindowManagerGlobal,然后在WindowManagerGlobal的addView里面执行了ViewRootImpl.setView操作(ViewRootImpl对象也是在这个时候创建的),在ViewRootImpl会主动调用一次requestLayout,也就开启了第一次的视图 测量 布局 绘制.
在setView的时候主动调用了一次ViewRootImpl.requestLayout,注意这个requestLayout是ViewRootImpl的内部方法,和view viewGroup那些requestLayout不一样.在ViewRootImpl.requestLayout内部调用了performTraversals方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class ViewRootImpl{ void performTraversals(){ if(layoutResuested){ windowSizeMayChanged |= measureHierarchy(host,lp,res,desiredWindowWidth,desiredWindowHeight); } performMeasure() performLayout() } void measureHierarchy(){ performMeasure() } }
|
从ViewRootImpl的执行逻辑你可以看出,在执行performLayout之前,他自己就已经调用了两次performMeasure方法.所以你现在就知道为啥了.