privatefunhackInfo(): Pair<Int, Int> { var w = 0 var h = 0 try { val clazz = this.javaClass val getViewRootImpl = clazz.getMethod("getViewRootImpl") val result = getViewRootImpl.invoke(parent.parent) val mWidth = result.javaClass.getDeclaredField("mWidth") val mHeight = result.javaClass.getDeclaredField("mHeight") mWidth.isAccessible = true mHeight.isAccessible = true w = mWidth.get(result) asInt h = mHeight.get(result) asInt
mInLayout = false; int numViewsRequestingLayout = mLayoutRequesters.size(); if (numViewsRequestingLayout > 0) { // requestLayout() was called during layout. // If no layout-request flags are set on the requesting views, there is no problem. // If some requests are still pending, then we need to clear those flags and do // a full request/measure/layout pass to handle this situation. ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, false); if (validLayoutRequesters != null) { // Set this flag to indicate that any further requests are happening during // the second pass, which may result in posting those requests to the next // frame instead mHandlingLayoutInLayoutRequest = true;
// Process fresh layout requests, then measure and layout int numValidRequests = validLayoutRequesters.size(); for (int i = 0; i < numValidRequests; ++i) { final View view = validLayoutRequesters.get(i); Log.w("View", "requestLayout() improperly called by " + view + " during layout: running second layout pass"); view.requestLayout(); } measureHierarchy(host, lp, mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight); mInLayout = true; host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mHandlingLayoutInLayoutRequest = false;
// Check the valid requests again, this time without checking/clearing the // layout flags, since requests happening during the second pass get noop'd validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true); if (validLayoutRequesters != null) { final ArrayList<View> finalRequesters = validLayoutRequesters; // Post second-pass requests to the next frame getRunQueue().post(new Runnable() { @Override publicvoidrun(){ int numValidRequests = finalRequesters.size(); for (int i = 0; i < numValidRequests; ++i) { final View view = finalRequesters.get(i); Log.w("View", "requestLayout() improperly called by " + view + " during second layout pass: posting in next frame"); view.requestLayout(); } } }); } }
publicvoidlayout(int l, int t, int r, int b){ // ① 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;
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; }
if (sizeChanged) { sizeChange(newWidth, newHeight, oldWidth, oldHeight); }
if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) { // If we are visible, force the DRAWN bit to on so that // this invalidate will go through (at least to our parent). // This is because someone may have invalidated this view // before this call to setFrame came in, thereby clearing // the DRAWN bit. mPrivateFlags |= PFLAG_DRAWN; invalidate(sizeChanged); // parent display list may need to be recreated based on a change in the bounds // of any child invalidateParentCaches(); }
// Reset drawn bit to original value (invalidate turns it off) mPrivateFlags |= drawn;
```java @Override protectedvoidonLayout(boolean changed, int left, int top, int right, int bottom){ layoutChildren(left, top, right, bottom, false/* no force left gravity */); }
voidlayoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity){ finalint count = getChildCount();
finalint parentLeft = getPaddingLeftWithForeground(); // 这里 parentRight 的含义,可以理解为 FrameLayout 剩余的宽度 finalint parentRight = right - left - getPaddingRightWithForeground();
for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//获得宽高的测量模式和测量值 val widthMode = MeasureSpec.getMode(widthMeasureSpec) var widthSize = MeasureSpec.getSize(widthMeasureSpec) var heightSize = MeasureSpec.getSize(heightMeasureSpec) val heightMode = MeasureSpec.getMode(heightMeasureSpec)
var perLineWidth = 0// 每一行总宽度 var perLineMaxH = 0// 每一行最多高度 var maxH = 0// 当前 ViewGroup 总高度
for (i in0 until childCount) { val childView: View = getChildAt(i)
measureChild(childView, widthMeasureSpec, heightMeasureSpec) val marginLp = childView.layoutParams as MarginLayoutParams val childWidth = childView.measuredWidth + marginLp.leftMargin + marginLp.rightMargin; val childHeight = childView.measuredHeight + marginLp.topMargin + marginLp.bottomMargin
var lineWidth = 0// 每一行已经占用的宽度 var lineMaxH = 0// 每一行最大的高度
for (i in0 until childCount) { val childView = getChildAt(i) val marginLp = childView.layoutParams as MarginLayoutParams val childWidth = childView.measuredWidth + marginLp.leftMargin + marginLp.rightMargin val childHeight = childView.measuredHeight + marginLp.topMargin + marginLp.bottomMargin
if(lineWidth + childWidth > width) { // 累计宽度超过一行的宽度 // 已累计的一行添加到列表中 mAllViews.add(mLineViews) mPerLineMaxHeight.add(lineMaxH) // 重置累计变量 lineWidth = 0 lineMaxH = 0 // 这里一定要创建新的,不能用 clean mLineViews = ArrayList() } // 在同一行中的元素做累计 lineWidth += childWidth lineMaxH = lineMaxH.coerceAtLeast(childHeight) mLineViews.add(childView) } // 最后一行特殊处理 mAllViews.add(mLineViews) mPerLineMaxHeight.add(lineMaxH) // 遍历集合中的 view var childLeft = 0 var childTop = 0 for ( i in0 until mAllViews.size) { val perLineViews = mAllViews[i] // 每一行的所有 views val perLineH = mPerLineMaxHeight[i] // 每一行的最大高度 for (j in perLineViews.indices) { val childView = perLineViews[j] val lp = childView.layoutParams as MarginLayoutParams val l = childLeft + lp.leftMargin val t = childTop + lp.topMargin val r = l + childView.measuredWidth val b = t + childView.measuredHeight Log.e("MyFlowLayout","l=$l,t=$t,r=$r,b=$b") childView.layout(l,t,r,b) childLeft += lp.leftMargin + childView.measuredWidth + lp.rightMargin } childLeft = 0 childTop += perLineH } }
这里的实现方式是用 list 记录每一行可以添加的 view ,然后再用一个 list 记录所有的行。 然后在布局子 view 的时候,就可以展开这个 list ,依次对内部的子view 做 layout 即可。完整源码参考CustomViewPlayGround。