Android源码解析:UI绘制流程之测量.md
生活随笔
收集整理的這篇文章主要介紹了
Android源码解析:UI绘制流程之测量.md
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
帶著問題看源碼
書接上文,做安卓開發都知道只要我們在xml布局中填寫控件,并設置寬高大小與位置,安卓系統就會將我們想要的布局展示出來,但是這一步是系統是如何做到的呢?這就是上文講到的UI繪制過程,他一共分為三步:測量(主要是計算出控件的大小),布局(控件的擺放),最后是繪制(使用Paint畫筆在Canves畫布上繪制出最終效果),下面我們就通過源碼來看看其內部是如何實現的.
測量,從上文分析的performMeasure()方法開始
public void performTraversals(){//...if (!mStopped || mReportNextDraw) {//...//獲取參數childWidthMeasureSpec 與 childHeightMeasureSpecint childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);// Ask host how big it wants to beperformMeasure(childWidthMeasureSpec, childHeightMeasureSpec);}}private static int getRootMeasureSpec(int windowSize, int rootDimension) {int measureSpec;switch (rootDimension) {case ViewGroup.LayoutParams.MATCH_PARENT:// Window can't resize. Force root view to be windowSize.measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);break;case ViewGroup.LayoutParams.WRAP_CONTENT:// Window can resize. Set max size for root view.measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);break;default:// Window wants to be an exact size. Force root view to be that size.measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);break;}return measureSpec;}private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {try {mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);} finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}}//發現最終調用的是View.measure(),繼續查看 復制代碼在getRootMeasureSpec()方法中,又調用了MeasureSpec.makeMeasureSpec()方法
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,@MeasureSpecMode int mode) {if (sUseBrokenMakeMeasureSpec) {return size + mode;} else {return (size & ~MODE_MASK) | (mode & MODE_MASK);}} 該方法是將測量模式與size打包計算后返回:其計算規則為 測量模式:EXACTLY ,ATMOST ,UNSPECIFIED這三個模式的本質是0,1,2的左位移30位, 那么其實我們先能理解為我們實際上在傳遞值得過程當中將顯示模式+size打包一起交給measure方法 而里面的數據結構其實實際上是一個32位的數值, 我們可以明顯看到幾個模式的值在后面進行了左位移操作了30位 用MODE_SHIFT 操作之后,實際表明一個32位的值30前兩位作為MODE 而MODE_MASK 表示是后30位 復制代碼View.measure()
//調用該方法是為了確定View的大小,兩個參數為容器的測量參數public final void measure(int widthMeasureSpec, int heightMeasureSpec) {boolean optical = isLayoutModeOptical(this);//...if (cacheIndex < 0 || sIgnoreMeasureCache) {// measure ourselves, this should set the measured dimension flag backonMeasure(widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;} //...}//measure方法是final修飾,子類不可繼承,其內部調用了onMeasure方法protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}//最終都會調用setMeasuredDimension()方法確定寬高 復制代碼通過上面的分析,控件想確認容器的大小需重寫onMeasure()方法,在內部實現自己的邏輯,我們以FragmentLayout為例,分析其onMeasure()方法的實現邏輯
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//獲取子控件數量int count = getChildCount();//判斷當前布局的模式是否是確定值final boolean measureMatchParentChildren =MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;mMatchParentChildren.clear();int maxHeight = 0;int maxWidth = 0;int childState = 0;for (int i = 0; i < count; i++) {final View child = getChildAt(i);if (mMeasureAllChildren || child.getVisibility() != GONE) {//1.遍歷子控件并進行測量measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);final LayoutParams lp = (LayoutParams) child.getLayoutParams();//因為FrameLayout的大小由最大子控件決定,所以需要比較求出最大寬高maxWidth = Math.max(maxWidth,child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);maxHeight = Math.max(maxHeight,child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);childState = combineMeasuredStates(childState, child.getMeasuredState());if (measureMatchParentChildren) {if (lp.width == LayoutParams.MATCH_PARENT ||lp.height == LayoutParams.MATCH_PARENT) {mMatchParentChildren.add(child);}}}}// Account for padding toomaxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();// Check against our minimum height and widthmaxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());// Check against our foreground's minimum height and widthfinal Drawable drawable = getForeground();if (drawable != null) {maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());}//2.調用setMeasuredDimension()方法確定最終大小setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),resolveSizeAndState(maxHeight, heightMeasureSpec,childState << MEASURED_HEIGHT_STATE_SHIFT));//3.當FrameLayotu設置為Wrap_content,而子View為Match_patent時,需要重新測量count = mMatchParentChildren.size();if (count > 1) {for (int i = 0; i < count; i++) {final View child = mMatchParentChildren.get(i);final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();final int childWidthMeasureSpec;/*** 如果子View的寬度是match_parent屬性,那么對當前FrameLayout的MeasureSpec修改:* 把widthMeasureSpec的寬度規格修改為:總寬度 - padding - margin,這樣做的意思是:* 對于子Viw來說,如果要match_parent,那么它可以覆蓋的范圍是FrameLayout的測量寬度* 減去padding和margin后剩下的空間。** 以下兩點的結論,可以查看getChildMeasureSpec()方法:** 如果子View的寬度是一個確定的值,比如50dp,那么FrameLayout的widthMeasureSpec的寬度規格修改為:* SpecSize為子View的寬度,即50dp,SpecMode為EXACTLY模式* * 如果子View的寬度是wrap_content屬性,那么FrameLayout的widthMeasureSpec的寬度規格修改為:* SpecSize為子View的寬度減去padding減去margin,SpecMode為AT_MOST模式*/if (lp.width == LayoutParams.MATCH_PARENT) {final int width = Math.max(0, getMeasuredWidth()- getPaddingLeftWithForeground() - getPaddingRightWithForeground()- lp.leftMargin - lp.rightMargin);childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);} else {//當子View設置為wrap_contentchildWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,getPaddingLeftWithForeground() + getPaddingRightWithForeground() +lp.leftMargin + lp.rightMargin,lp.width);}//...高度同理child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}}}protected void measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed,int parentHeightMeasureSpec, int heightUsed) {final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin+ widthUsed, lp.width);final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin+ heightUsed, lp.height);child.measure(childWidthMeasureSpec, childHeightMeasureSpec);} 復制代碼getChildMeasureSpec()方法:通過父容器spec的MeasureSpec和子View的MeasureSpec-childDimension計算出最終子View的MeasureSpec的值.
/*** @param spec The requirements for this view* @param padding The padding of this view for the current dimension and* margins, if applicable* @param childDimension How big the child wants to be in the current* dimension* @return a MeasureSpec integer for the child*/public static int getChildMeasureSpec(int spec, int padding, int childDimension) {int specMode = MeasureSpec.getMode(spec);int specSize = MeasureSpec.getSize(spec);int size = Math.max(0, specSize - padding);int resultSize = 0;int resultMode = 0;switch (specMode) {// Parent has imposed an exact size on uscase MeasureSpec.EXACTLY:if (childDimension >= 0) {resultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size. So be it.resultSize = size;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// Child wants to determine its own size. It can't be// bigger than us.resultSize = size;resultMode = MeasureSpec.AT_MOST;}break;// Parent has imposed a maximum size on uscase MeasureSpec.AT_MOST:if (childDimension >= 0) {// Child wants a specific size... so be itresultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size, but our size is not fixed.// Constrain child to not be bigger than us.resultSize = size;resultMode = MeasureSpec.AT_MOST;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// Child wants to determine its own size. It can't be// bigger than us.resultSize = size;resultMode = MeasureSpec.AT_MOST;}break;// Parent asked to see how big we want to becase MeasureSpec.UNSPECIFIED:if (childDimension >= 0) {// Child wants a specific size... let him have itresultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size... find out how big it should// beresultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;resultMode = MeasureSpec.UNSPECIFIED;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// Child wants to determine its own size.... find out how// big it should beresultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;resultMode = MeasureSpec.UNSPECIFIED;}break;}//noinspection ResourceTypereturn MeasureSpec.makeMeasureSpec(resultSize, resultMode);} 復制代碼處理邏輯圖為:
新人創作打卡挑戰賽發博客就能抽獎!定制產品紅包拿不停!總結
以上是生活随笔為你收集整理的Android源码解析:UI绘制流程之测量.md的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【leetcode】423. Recon
- 下一篇: 项目研发流程及管理之我见