Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> [例證]淺談getWidth()和getMeasureWidth()區別

[例證]淺談getWidth()和getMeasureWidth()區別

編輯:關於Android編程

一個簡單的例子:

重寫自定義View的onDraw()代碼:

 

	oval.left=getMeasuredWidth()/2-radius;                              //左邊
        oval.top=getMeasuredHeight()/2 -radius;                                   //上邊
        oval.right=getMeasuredWidth()/2 +radius;                             //右邊
        oval.bottom=getMeasuredHeight()/2 +radius;
        canvas.drawArc(oval,0,360,true,mPaint);

 

得到效果圖如下:

\

這麼做肯定沒問題。

有這麼個疑問:

為什麼我不用getwidth,getheight方法?

大多數情況下,getwidth和getMeasureWidth方法得到的結果都是一樣的。

回到這個getWidth()方法和getMeasureWidth()的區別這個問題上。

網上說法很多,我決定自己一點點從源碼裡面扣。然後舉例說明。

View#getMeasuredWidth():

 

public final int getMeasuredWidth() {
        return mMeasuredWidth & MEASURED_SIZE_MASK;
    }

得到的是最近一次調用measure()方法測量後得到的是View的寬度。

 

大概跟一下源碼知道:

平時我們自定義View會重寫onMeasure方法:(什麼情況下寫onMeasure?後續會有解答)

View#onMeasure源碼如下:

 

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
View#onMeasure方法會調用View#setMeasuredDimension方法:

 

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }
View#setMeasuredDimension方法會調用View#setMeasuredDimensionRaw方法:

 

 

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }
看到這個方法代碼第一行第二行有個賦值

 

mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
這就是我們的getMeasuredWidth()方法的值。

 

所以當我們重寫onMeasure方法時,如果對setMeasuredDimension()這個方法參數直接自定義,如setMeasuredDimension(200,300),那麼getMeasuredWidth()的值必然就是200,getMeasuredHeight()的值必然就是300。
當然,我們一般情況下,不會使用直接這種方式寫死參數,一般還是對onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法的參數進行處理,再傳入setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);

什麼情況下我們需要對onMeasure方法重寫呢?

因為我們自定義view對於屬性為wrap_content這種情況,如果不做處理其實是與match_parent是一樣效果的。原因是什麼呢?參考如下:任玉剛的書中表格:

\

當父容器的specmode為EXACTLY和AT_MOST時子view不管是wrap_content還是match_parent,它的默認大小都是父容器大小parentSize。

不信?舉個栗子:
直接先上一個自定義view為match_parent時的效果圖:

\

再上一個自定義view為wrap_content時的圖:

\

好吧,沒有比較就沒有傷害。上一個自定義view為40dp的寬高的圖。

 

\
這回效果很明顯了吧。除非是精確值,否則大小都等於父布局的大小。

那麼這我當然不能接受。我wrap_content需要有所變化,需要一個默認值大小200*200。

於是有了

 
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int speSize = MeasureSpec.getSize(heightMeasureSpec); int speMode = MeasureSpec.getMode(heightMeasureSpec); Log.d("MyView", "---speSize = " + speSize + ""); Log.d("MyView", "---speMode = " + speMode + ""); if(speMode == MeasureSpec.AT_MOST){ Log.d("MyView", "---AT_MOST---"); } if(speMode == MeasureSpec.EXACTLY){ Log.d("MyView", "---EXACTLY---"); } if(speMode == MeasureSpec.UNSPECIFIED){ Log.d("MyView", "---UNSPECIFIED---"); } if(speMode==MeasureSpec.AT_MOST){ setMeasuredDimension(100, 100); }else{ setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); } }

 


再看效果圖:

\

所以由此類推,我們之所以重寫onMeasure也就是為了wrap_content時能自動按照需求改變。回到原本的話題的第二塊:getWidth()方法:

View#getWidth()

源碼如下

 

 @ViewDebug.ExportedProperty(category = "layout")
    public final int getWidth() {
        return mRight - mLeft;
    }
這裡的mRight和mLeft到底是什麼呢?其實它是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 listenersCopy = (ArrayList)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; }

有兩種測量setOpticalFrame、setFrame,最終都會在其中調用了setFrame方法,它的源碼如下:

 

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;  
              
           //.... 
  
            mLeft = left;  
            mTop = top;  
            mRight = right;  
            mBottom = bottom;  
              
           //......
              
        }  
        return changed;  
    }  
這樣就有了mLeft和mRight兩個值了。

 

當然,光知道來處還的會用。我們平時自定義view繼承自view時是不會對onlayout方法重寫的。只有當重寫布局viewGroup時才會對onlayout重寫。

什麼時候會遇到getWidth()和getMeasureWidth()不一致?

當繼承布局viewGroup時,重寫onlayout方法。對子view的childView.layout(0,0,200,200);

我們平時重寫onlayout()方法主要是為了對子布局自定義,比如瀑布流,比如放不下換行顯示子view這種操作。

舉個栗子:

當繼承布局viewGroup時,重寫onlayout方法,碼如下:

 

 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int mViewGroupWidth  = getMeasuredWidth();  //當前ViewGroup的總寬度
        int mPainterPosX = l;  //當前繪圖光標橫坐標位置
        int mPainterPosY = t;  //當前繪圖光標縱坐標位置
        int childCount = getChildCount();
        for ( int i = 0; i < childCount; i++ ) {

            View childView = getChildAt(i);

            int width  = childView.getMeasuredWidth();
            int height = childView.getMeasuredHeight();

            //如果剩余的空間不夠,則移到下一行開始位置
            if( mPainterPosX + width > mViewGroupWidth ) {
                mPainterPosX = l;
                mPainterPosY += height;
            }

            //執行ChildView的繪制
//            childView.layout(mPainterPosX,mPainterPosY,mPainterPosX+width, mPainterPosY+height);
            childView.layout(0,0,100, 200);

            //記錄當前已經繪制到的橫坐標位置
            mPainterPosX += width;
        }
    }
這裡對子view布局使用固定值childView.layout(0,0,100, 200);

 

看下布局文件:

 


    
        
    
在子view種打個log:發現
\

 

現在子 view的getWidth和getMeasuredWidth不一樣了。

什麼時候用getWidth?什麼時候用getMeasureWidth()?

 

由view繪制流程我們知道:順序是:onMeasure()--》onLayout()--》onDraw();(見源碼ViewRootImpl#performTraversals() 方法,下一篇打算講這個內容)

所以再onMeasure之後可以getMeasuredWidth,在Onlayout()之後 可以用getWitdth().

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved