<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                在開發過程中我們經常要進行view的自定義。如果熟練掌握自定義技巧的話就能做出很多控件出來。這篇博客來講講view繪制背后發生的那些事。 ### 一, view的基礎知識 ## view的繪制概括 首先先說說view繪制的整體過程。? View繪制的源碼分析 ,它的三大流程都是在ViewRootImpl中完成的,從ViewRootImpl中的performTraversals開始,有三個方法performMeasure,performLayout,prformDraw分別對measure,layout,draw三個方法。在onMeasure對所有子元素進行measure過程 ,這時measure就從父容器傳遞到子元素。子元素重復父元素的過程。layout與draw類似,只是draw通過diapatchDraw來實現 。? measure完成后可以通過getMeasureWidth,getMeasureHeight分別獲取View測量后的寬高。在實際情況下幾乎所有情況它都等于最終寬高。layout過程決定view的四個頂點的坐標和實際view的寬高,完成之后可以通過getTop,getBottom,getLeft,getRight來拿 到view的四個頂點位置。并通過getWidth()和getHeight()來拿到最終寬高。draw決定了view的顯示,只有完成才能顯示在屏幕上。 ## MeasureSpec 在測量過程中系統會將View的LayoutParams根據容器所施加的規則轉換成對應的MeasureSpec,然后再根據這個測量出view。? Measure是一個32位的int,高2位代表SpecMode,低30位代表SpecSize。SpecMode表示測量模式,SpecSize指在某種測量模式下規格的大小。其代碼如下: ~~~ public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; public static final int UNSPECIFIED = 0 << MODE_SHIFT; public static final int EXACTLY = 1 << MODE_SHIFT; public static final int AT_MOST = 2 << MODE_SHIFT; public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK) ; } public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK) ; } ~~~ 其實MeasureSpec中源碼很值得我們學習。他用一個32位的int來表示模式和大小,節省了空間,也更直觀。MeasureSpec通過將specMode和specSize打包成一個int來避免過多的對象內存分配。以上是MeasureSpec的打包和解包過程。? specMode有三種狀態:UNSPECIFIED,EXACTLY(相當于match_parent和精確值這兩種模式),AT_MOST(wrap_content)。 ## LayoutParams 對于一般容器,它的MeasureSpec是由父容器的MeasureSpec和自身的LayoutParams共同決定的。上篇博客[LayoutInflater源碼解析](http://blog.csdn.net/u014486880/article/details/50707672)?我們己經介紹了android view的結構,PhoneWindow包了一層DecorView,DecorView里才是title和我們的content view。所以行分析DecorView。? 先來看下DecorView的產生源碼: ~~~ childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ~~~ 再看下getRootMeasureSpec方法: ~~~ 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; } ~~~ 這里很清楚,分別分MatchPraent和wrap_content和自定義來計算寬高。再來看下普通的view,在ViewGroup的measureChildWIthMargins中: ~~~ 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: ~~~ 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 us case 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 us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = 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 be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension ; resultMode = MeasureSpec. EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = 0; resultMode = MeasureSpec. UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = 0; resultMode = MeasureSpec. UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode) ; } ~~~ 以上表明,如果父是EXACTLY,parentSize,那么子如果是EXACTLY,? 1)具體的值size:那子的MeasureSpec就是EXACTLY,size;? 2)MATCH_PARENT:那子的MeasureSpec就是EXACTLY,parentSize;? 3)WRAP_CONTENT:那子的MeasureSpec就是AT_MOST,parentSize; 如果父是ATMOST,parentSize,那么子如果是EXACTLY,? 1)具體的值size:那子的MeasureSpec就是EXACTLY,size;? 2)MATCH_PARENT:那子的MeasureSpec就是AT_MOST,parentSize;? 3)WRAP_CONTENT:那子的MeasureSpec就是AT_MOST,parentSize;? 總結:對于普通View的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams來共同決定。 ## 二,三大過程源碼分析 ### OnMeasure * measure。如果是view,measure繪制其自身。如果是VIewGroup,measure繪制自身外,還要繪制其子元素。先看View的measure方法,measure是一個final方法,不能重寫: ~~~ if (cacheIndex < 0 |if (cacheIndex < 0 || sIgnoreMeasureCache ) { // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec , heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; }| sIgnoreMeasureCache ) { // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec , heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } ~~~ 調用了onMeasure(),來看下它的源碼: ~~~ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth() , widthMeasureSpec) , getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)) ; } ~~~ 看下getSuggestedMinimumWidth(),它就是獲取背景大小和mMinWidth的較大值: ~~~ protected int getSuggestedMinimumWidth () { return (mBackground == null) ? mMinWidth : max(mMinWidth , mBackground .getMinimumWidth()); } ~~~ 那么mMinWidth是什么呢,mMinWidth就是設置的android:minWidth的屬性,沒設置就等于0。不信,看如下代碼: ~~~ case R.styleable.View_minWidth: mMinWidth = a.getDimensionPixelSize(attr , 0) ; break; ~~~ getMinimumWidth()表示的是獲取背景圖大小,它位于Drawable下: ~~~ public int getMinimumHeight() { final int intrinsicHeight = getIntrinsicHeight() ; return intrinsicHeight > 0 ? intrinsicHeight : 0 ; } ~~~ 看下getDefaultSize方法: ~~~ public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec. getMode(measureSpec) ; int specSize = MeasureSpec. getSize(measureSpec) ; switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec. AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; } ~~~ 它返回了specSize,它是測量后的大小。由上面的分析可知,view的寬高由specSize決定,而如果直接繼承View的控件需要重寫onMeasure方法并設置wrap_content的自身大小。否則wrap_content就相當 于Match_parent了。一般一重寫方法如下: ~~~ protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); if (widthSpecMode == MeasureSpec. AT_MOST && heightSpecMode == MeasureSpec. AT_MOST) { setMeasuredDimension(200, 200); } else if (widthSpecMode == MeasureSpec. AT_MOST) { setMeasuredDimension(200, heightSpecSize); } else if (heightSpecMode == MeasureSpec. AT_MOST) { setMeasuredDimension(widthSpecSize, 200); } } ~~~ 上面的200是指定的一個默認寬高。 2.ViewGroup的measure過程,它沒有重寫onMeasure,它會調用measureChildren如下: ~~~ protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; for ( int i = 0 ; i < size; ++i) { final View child = children[i] ; if ((child. mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec , heightMeasureSpec); } } } ~~~ 分別繪制child,進入measureChild: ~~~ protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams() ; final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec , mPaddingLeft + mPaddingRight, lp.width) ; final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec , mPaddingTop + mPaddingBottom, lp.height) ; child.measure(childWidthMeasureSpec , childHeightMeasureSpec); } ~~~ 獲取LayoutParams,通過getChildMeasureSpec來創建子無素的MeasureSpec,調用child.measure,因為ViewGroup有不同的特性,所以無法實現統一的onMeasure。 ## Layout的過程 viewGroup會遍歷所有子元素并調用 其layout方法,layout方法來確定子元素的位置。viewgroup如下: ~~~ protected abstract void onLayout(boolean changed, int l , int t, int r, int b) ; ~~~ 需要子類自己實現。看下view的layout: ~~~ 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; boolean changed = 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<OnLayoutChangeListener> listenersCopy = (ArrayList<OnLayoutChangeListener>)li. mOnLayoutChangeListeners .clone(); 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; } ~~~ 在setFrame中確定了view的四個頂點坐標。mleft等。onLayout view也沒有具體實現,要看具體的。以LinearLayout為例: ~~~ 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); } } ~~~ 以layoutVertical為例: ~~~ 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 int childSpace = width - paddingLeft - mPaddingRight ; final int count = getVirtualChildCount() ; final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; 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; } for (int i = 0; i < count ; i++) { final View child = getVirtualChildAt(i) ; if (child == null) { childTop += measureNullChild(i); } else if (child.getVisibility() != GONE) { final int childWidth = child.getMeasuredWidth() ; final int childHeight = child.getMeasuredHeight(); 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) ; 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; } if (hasDividerBeforeChildAt(i)) { childTop += mDividerHeight; } childTop += lp.topMargin; setChildFrame(child , childLeft, childTop + getLocationOffset(child) , childWidth, childHeight); childTop += childHeight + lp. bottomMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child , i); } } } ~~~ 主要看以下代碼: ~~~ final int childWidth = child.getMeasuredWidth() ; final int childHeight = child.getMeasuredHeight() ; setChildFrame(child , childLeft, childTop + getLocationOffset(child) , childWidth , childHeight); childTop += childHeight + lp. bottomMargin + getNextLocationOffset(child); ~~~ top會逐漸增大,所以會往下排。setChildFrame僅僅是調用子元素的layout方法。 ~~~ private void setChildFrame(View child, int left, int top , int width, int height) { child.layout(left, top, left + width , top + height); } ~~~ 通過子元素的layout來確定自身。 ## draw過程 它有以下幾步: * 繪制背景,(canvas) * 繪制自己。(onDraw) * 繪制children(dispatchDraw) * 繪制裝飾(onDrawScrollBars)? 看下view的draw源碼: ~~~ public void draw(Canvas canvas) { final int privateFlags = mPrivateFlags; final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK ) == PFLAG_DIRTY_OPAQUE && (mAttachInfo == null || !mAttachInfo .mIgnoreDirtyState ); mPrivateFlags = (privateFlags & ~ PFLAG_DIRTY_MASK ) | PFLAG_DRAWN; /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1\. Draw the background * 2\. If necessary, save the canvas' layers to prepare for fading * 3\. Draw view's content * 4\. Draw children * 5\. If necessary, draw the fading edges and restore layers * 6\. Draw decorations (scrollbars for instance) */ // Step 1, draw the background, if needed int saveCount; if (!dirtyOpaque) { drawBackground(canvas); } // skip step 2 & 5 if possible (common case) final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL ) != 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL ) != 0; if (!verticalEdges && !horizontalEdges) { // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas) ; // Step 4, draw the children dispatchDraw(canvas) ; // Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas) ; if ( mOverlay != null && !mOverlay.isEmpty()) { mOverlay .getOverlayView().dispatchDraw(canvas) ; } // we're done... return; } ~~~ viewgroup中的dispatchDraw用于遍歷子view并調用子view的draw方法。這樣就一層層的傳下去。? 到此源碼分析就結束了。在繪制view的時候經常會在activity中獲得view的寬高,因為activity的生命周期和view不同步,在oncreate中無法獲取到view的寬高,接下來講講activity中如何獲取view。 ## 三,view寬高確定 * onWindowFocusChanged:view己經初始化完畢,寬高己經準備好。當Activity得到焦點和失去焦點均會被調用,所以它會調用多次。 * 通過view.post,將一個runnable投弟到消息隊列尾部,等待looper調用時,view己經初始化好。 ~~~ protected void onStart(){ super.onStart(); view.post(new Runnable(){ public void run(){ int width = view.getMeasuredWidth(); int height = new .getMeasuredHeight(); } }) } ~~~ * ViewTreeObserver:? 使用ViewTreeObserver眾多回調接口來完成,如OnGlobalLayoutListener,當view樹狀態發生改變時或內部view可見性發生改變時會回調。 ~~~ ViewObserver obserber = view.getViewObserver (); obserber.addOnGlobalLayoutListener(new OnGlobalLayoutListener(){ public void onGlobalLayout(){ obserber.removeOnGlobalLayoutListener(this); int width = view.getMeasuredWidth(); int height = new .getMeasuredHeight(); } }) ~~~ * 通過view進行measure來得到view的寬高。 ~~~ int width = MeasureSpec.makeMeasureSpec(100,Measure.EXACTLY);//確定值 int height= MeasureSpec.makeMeasureSpec(100,Measure.EXACTLY);//確定值 view.measure(width,height); 對于wrap_content: int width = MeasureSpec.makeMeasureSpec((1<<30)-1,Measure.AT_MOST);//wrap_content int height= MeasureSpec.makeMeasureSpec((1<<30)-1,Measure.AT_MOST);//wrap_content view.measure(width,height); ~~~ ## 四,自定義view中注意事項 自定義View需要注意的事項: * 如果是繼承view或者viewGroup,讓view支持wrap_content。 * 如果有必要,讓view支持padding。 * view中如果有動畫或者線程,要在onDetachedFromWindow中及時停止。當view的Activity退出或者當前view被remove時,調用它。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看