在以前的文章中 , 咱們學習了Android View的 Measure的流程, 本篇文章來學習一下View的 Layout
的過程。 學完了這一篇文章後,咱們能夠嘗試本身去自定義一個本身的Layout。php
我對於Layout過程的理解:Layout的過程就是給Child安家的過程java
Layout的過程主要是放在 ViewGroup
中的,ViewGroup不只須要定位本身,還須要定位Child。android
Layout流程的起點也是在 ViewRootImpl 中的 performTraversals
方法中。git
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { mLayoutRequested = false; mScrollMayChange = true; mInLayout = true; final View host = mView; if (host == null) { return; } try { //首先調用了host的layout方法 host = mView = DecorView host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); //.... }
咱們接着來看View的layout方法github
public void layout(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; //調用 setOpticalFrame或者 setFrame 來肯定本身的位置 boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {、 //調用onLayout onLayout(changed, l, t, r, b); //..... } protected boolean setFrame(int left, int top, int right, int bottom) { boolean changed = false; if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) { changed = true; // Remember our drawn bit int drawn = mPrivateFlags & PFLAG_DRAWN; int oldWidth = mRight - mLeft; int oldHeight = mBottom - mTop; int newWidth = right - left; int newHeight = bottom - top; boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight); // Invalidate our old position invalidate(sizeChanged); //肯定4個點 //這四個點一旦肯定了那麼View在ViewGroup中的位置也就肯定了 mLeft = left; mTop = top; mRight = right; mBottom = bottom; mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); //......
View的 layout
方法主要是作了:ide
setFrame
肯定了本身的位置,一篇Left,Top,Right,Bottom這幾個值肯定了,那麼View的位置也就肯定了。onLayout
方法。View是不須要實現onLayout方法的,只用ViewGroup才須要實現。因爲各類ViewGroup的佈局方式的不一樣,沒法統一,因此ViewGroup也並無實現onLayout
. 而是將onLayout的過程放到了子類中。咱們仍是經過 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); } }
onLayout根據 Orientation 屬性來調用 layoutVertical
或者 layoutHorizontal
學習
void layoutVertical(int left, int top, int right, int bottom) { final int paddingLeft = mPaddingLeft; int childTop; int childLeft; // Where right end of child should go final int width = right - left; int childRight = width - mPaddingRight; // Space available for child //獲取Child可用的空間 int childSpace = width - paddingLeft - mPaddingRight; //Child的Group final int count = getVirtualChildCount(); final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; //根據Gravity肯定初始的childTop switch (majorGravity) { case Gravity.BOTTOM: // mTotalLength contains the padding already childTop = mPaddingTop + bottom - top - mTotalLength; break; // mTotalLength contains the padding already case Gravity.CENTER_VERTICAL: childTop = mPaddingTop + (bottom - top - mTotalLength) / 2; break; case Gravity.TOP: default: childTop = mPaddingTop; break; } //LayoutView //遍歷 for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); if (child == null) { childTop += measureNullChild(i); } else if (child.getVisibility() != GONE) {//過濾Child的Visibility是GONE的狀況 //獲取Child的大小 final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); //獲取LP final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); int gravity = lp.gravity; if (gravity < 0) { gravity = minorGravity; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); //處理Child的Gravity switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { //水平居中 case Gravity.CENTER_HORIZONTAL: childLeft = paddingLeft + ((childSpace - childWidth) / 2) + lp.leftMargin - lp.rightMargin; break; //居右 case Gravity.RIGHT: childLeft = childRight - childWidth - lp.rightMargin; break; case Gravity.LEFT: default: childLeft = paddingLeft + lp.leftMargin; break; } //childTop 加上 Divider if (hasDividerBeforeChildAt(i)) { childTop += mDividerHeight; } //加上Margin childTop += lp.topMargin; //調用Child的layout方法 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); }
上面的代碼仍是比較清晰的,首先是有一個childTop變量,來肯定child與ViewGroup頂部的距離,經過不斷的遍歷Child而後不斷增長childTop的值,這樣就實現了LinearLayout的垂直佈局的效果。固然其中也有處理LinearLayout和Child的Gravity的過程。this
經過學習LinearLayout的Layout的過程,發現其實Layout的過程就是肯定View的Left,Top,Right,Bottom 4個值的過程,學習了 Measure
和 Layout
的過程之後,咱們就已經能夠着手作一個本身的Layout了,這裏我選的是模仿 hongyang大神的FlowLayout。效果圖以下:code
package layout import android.annotation.SuppressLint import android.content.Context import android.util.AttributeSet import android.view.View import android.view.ViewGroup /** * Created by ShyCoder on 2019/1/16. */ class MyFlowLayout(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : ViewGroup(context, attrs, defStyleAttr) { constructor(context: Context?, attributeSet: AttributeSet?) : this(context, attributeSet, 0) constructor(context: Context?) : this(context, null) init { } /** * 存貯全部的View根據行來存貯 * */ private val mAllViews = mutableListOf<List<View>>() /** * 存貯每一行View的高度 * */ private val mHeightList = mutableListOf<Int>() override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { //從MeasureSpec獲取Mode和Size val widthMode = MeasureSpec.getMode(widthMeasureSpec) val widthSize = MeasureSpec.getMode(widthMeasureSpec) val heightMode = MeasureSpec.getMode(heightMeasureSpec) val heightSize = MeasureSpec.getMode(heightMeasureSpec) //計算Wrap_content的狀況 var totalHeight = 0//總高度 var totalWidth = 0//總寬度 var lineWidth = 0//當前行的寬度 var lineHeight = 0//當前行的高度 val viewCount = childCount for (i in 0.until(viewCount)) { val child = getChildAt(i) //若是是GONE狀態不用測量 if (child.visibility == View.GONE) { continue } //測量Child measureChild(child, widthMeasureSpec, heightMeasureSpec) val lp = child.layoutParams as MarginLayoutParams //計算child的width = 測量後寬度+左右的兩個Margin val childWidth = child.measuredWidth + lp.leftMargin + lp.rightMargin //計算child的Height = 測量後高度 + 上下兩個Margin val childHeight = child.measuredHeight + lp.bottomMargin + lp.topMargin //須要換行時候的處理方式 //若是已有的行寬+當前child的寬度> FlowLayout的寬度(減去左右的Padding) if (childWidth + lineWidth > widthSize - this.paddingLeft + this.paddingRight) { totalWidth = Math.max(totalWidth, lineWidth) lineWidth = childWidth totalHeight += lineHeight lineHeight = childHeight } else {//若是不須要換行增長行寬 lineWidth += childWidth //獲取最大高度 lineHeight = Math.max(lineHeight, childHeight) } //最後一個View if (i == viewCount - 1) { totalWidth = Math.max(totalWidth, lineWidth) totalHeight += lineHeight } } this.setMeasuredDimension( //若是MeasureSpec的Mode是EXACTLY 的話,測量後大小等會傳進來的測量大小, //不然則是咱們本身計算的大小 if (widthMode == MeasureSpec.EXACTLY) widthSize else totalWidth, if (heightMode == MeasureSpec.EXACTLY) heightSize else totalHeight ) } override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { this.mAllViews.clear() this.mHeightList.clear() val viewCount = this.childCount var lineHeight = 0 var lineWidth = 0 var lineViews = mutableListOf<View>() for (i in 0.until(viewCount)) { val child = this.getChildAt(i) if (child.visibility == View.GONE) { continue } val lp = child.layoutParams as MarginLayoutParams val childWidth = child.measuredWidth + lp.leftMargin + lp.rightMargin val childHeight = child.measuredHeight + lp.topMargin + lp.bottomMargin //行上沒法繼續放置View if (childWidth + lineWidth > this.width - this.paddingLeft - this.paddingRight) { //添加line height mHeightList.add(lineHeight) lineWidth = 0 //添加正行的View到集合中 this.mAllViews.add(lineViews) lineViews = mutableListOf() } //區當前行的View的最大高度 lineHeight = StrictMath.max(lineHeight, childHeight) lineWidth += childWidth //向行上添加View lineViews.add(child) } //進行Layout var left = this.paddingLeft var top = this.paddingTop for (i in 0.until(this.mAllViews.size)) { val lineViews = mAllViews[i] val lineHeight = mHeightList[i] left = this.paddingLeft for (j in 0.until(lineViews.size)) { val child = lineViews[j] val lp = child.layoutParams as MarginLayoutParams //view的四個邊 val l = left + lp.leftMargin val t = top + lp.topMargin val r = l + child.measuredWidth val b = t + child.measuredHeight //調用Child的Layout方法 child.layout(l, t, r, b) left += child.measuredWidth + lp.leftMargin + lp.rightMargin } top += lineHeight } } }
在onMeasure對wrap_content的狀況進行了處理,計算出所須要的大小。
在onLayout方法中,首先計算出每一行的高度並存儲和獲取每一行的View的List進行存儲,當這些都計算完以後,進行layout操做。
在layout操做的時候,首先是在同一行上的View的top是統一的,每當這一行的View處理完成以後,就執行換行的操做,即增長view的大小。
本篇文章就到此結束了,Layout的過程仍是相對簡單的,在下一篇文章呢,咱們將會學習View的最後一個流程 Draw
流程。