Android 視圖 View (一)

View 的生命週期

瞭解生命週期在咱們自定義View的時候發揮着很大的做用java

  • 在Activity啓動時獲取View的寬高,可是在onCreate、onStart和onResume均沒法獲取正確的結果。這是由於在Activity的這些方法裏,Viewed繪製可能尚未完成,咱們能夠在View的生命週期方法裏獲取,如onSizeChanged()。
  • 在Activity生命週期發生變化時,View也要作響應的處理,典型的有VideoView保存進度和恢復進度。
@Override
protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
    super.onVisibilityChanged(changedView, visibility);
    //TODO do something if activity lifecycle changed if necessary
    //Activity onResume()
    if(visibility == VISIBLE){
        
    }
    //Activity onPause()
    else {
        
    }
}

@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
    super.onWindowFocusChanged(hasWindowFocus);

    //TODO do something if activity lifecycle changed if necessary
    //Activity onResume()
    if (hasWindowFocus) {
    }
    //Activity onPause()
    else {
    }
}
複製代碼
  • 釋放線程、資源
@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    //TODO release resources, thread, animation
}
複製代碼

View 的測量流程

view大小android

View的大小有兩對值來表示,getMeasuredWidth()/getMeasureHeight(),這組值表示了他在父View裏指望的大小,在measure()方法完成後可得到markdown

View內邊距ide

View內邊距:View的內邊距用padding來表示,它表示View的內容距離View邊緣的距離。經過getPaddingXXX()方法獲取。須要注意的是咱們在自定義View的時候須要單獨處理佈局

padding,不然它不會生效.優化

View外邊距this

View外邊距:View的外邊距用margin來表示,它表示View的邊緣離它相鄰的View的距離。spa

在作測量時,measure() 方法會被父View調用,在measure()中作一些準備和優化工做後,調用onMeasure()來進行實際的自我測量。對於onMeasure(),View和ViewGroup有所區別:線程

  • View:View 在 onMeasure() 中會計算出本身的尺寸而後保存;
  • ViewGroup:ViewGroup在onMeasure()中會調用全部子View的measure()讓它們進行自我測量,並根據子View計算出的指望尺寸來計算出它們的實際尺寸和位置而後保存。同時,它也會

根據子View的尺寸和位置來計算出本身的尺寸而後保存.3d

View measure過程 ViewGroup 首先經過getChildMeasureSpec() 獲取child的MeasureSpec(childWidthMeasureSpec, childHeightMeasureSpec)

  • getChildMeasureSpec(ViewGroup的spec,ViewGroup padding,view 寬高)
最大size = 父view大小-padding > 0? ..:0
父view mode = exactly
   子 viewsize 爲具體>0數值,則子viewsize 肯定 , spec_mode = exactly
   子 View 爲 match_parent,則子view大小 = 最大size,spec_mode = exactly
   子 View 爲 wrap_content,則子view大小 = 最大size, spec_mode = AT_MOST
父view mode = AT_MOST
   子 viewsize 爲具體>0數值,則子viewsize 肯定 , spec_mode = exactly
   子 View 爲 match_parent,則子view大小 = 最大size,spec_mode = AT_MOST
   子 View 爲 wrap_content,則子view大小 = 最大size, spec_mode = AT_MOST
父view mode = UNSPECIFIED
   子 viewsize 爲具體>0數值,則子viewsize 肯定 , spec_mode = exactly
   子 View 爲 match_parent,則子view大小 = 0,spec_mode = UNSPECIFIED
   子 View 爲 wrap_content,則子view大小 = 0, spec_mode = UNSPECIFIED
複製代碼
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    
     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);
        }
        
}
複製代碼

childWidthMeasureSpec,childHeightMeasureSpec的測量參數由個ViewGroup實現類本身實現或者直接集成ViewGroup的測量方法如measureChild 和 measureChildWithMargins指定的測量參數

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);
    }

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);
    }
複製代碼

View 調用子View的measure(childWidthMeasureSpec,childHeightMeasureSpec),實際的測量工做是由View的onMeasure()來完成的。咱們來看看 onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法的實現

public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
    
       protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
           setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                   getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
       }

       //設置View寬高的測量值
       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);
       
       //measureSpec指的是View測量後的大小
       public static int getDefaultSize(int size, int measureSpec) {
           int result = size;
           int specMode = MeasureSpec.getMode(measureSpec);
           int specSize =  MeasureSpec.getSize(measureSpec);
   
           switch (specMode) {
           //MeasureSpec.UNSPECIFIED通常用來系統的內部測量流程
           case MeasureSpec.UNSPECIFIED:
               result = size;
               break;
           //咱們主要關注着兩種狀況,它們返回的是View測量後的大小
           case MeasureSpec.AT_MOST:
           case MeasureSpec.EXACTLY:
               result = specSize;
               break;
           }
           return result;
       }
       
       //若是View沒有設置背景,那麼返回android:minWidth這個屬性的值,這個值能夠爲0
       //若是View設置了背景,那麼返回android:minWidth和背景最小寬度二者中的最大值。
       protected int getSuggestedMinimumHeight() {
           int suggestedMinHeight = mMinHeight;
   
           if (mBGDrawable != null) {
               final int bgMinHeight = mBGDrawable.getMinimumHeight();
               if (suggestedMinHeight < bgMinHeight) {
                   suggestedMinHeight = bgMinHeight;
               }
           }
   
           return suggestedMinHeight;
       }
}
複製代碼

若是咱們直接繼承View來自定義View時,須要重寫onMeasure()方法,並設置wrap_content時的大小。
經過上面的描述咱們知道,當LayoutParams爲wrap_content時,SpecMode爲AT_MOST,而在 關於getDefaultSize(int size, int measureSpec) 方法須要說明一下,經過上面的描述咱們知道getDefaultSize()方法中AT_MOST與EXACTLY模式下,返回的 都是specSize,這個specSize是父View當前能夠使用的大小,若是不處理,那wrap_content就至關於match_parent。

@Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      Log.d(TAG, "widthMeasureSpec = " + widthMeasureSpec + " heightMeasureSpec = " + heightMeasureSpec);

      //指定一組默認寬高,至於具體的值是多少,這就要看你但願在wrap_cotent模式下
      //控件的大小應該設置多大了
      int mWidth = 200;
      int mHeight = 200;

      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 && heightMeasureSpec == MeasureSpec.AT_MOST) {
          setMeasuredDimension(mWidth, mHeight);
      } else if (widthSpecMode == MeasureSpec.AT_MOST) {
          setMeasuredDimension(mWidth, heightSpecSize);
      } else if (heightSpecMode == MeasureSpec.AT_MOST) {
          setMeasuredDimension(widthSpecSize, mHeight);
      }
  }
複製代碼

們再來看看ViewGroup的measure過程。ViewGroup繼承於View,是一個抽象類,它並無重寫onMeasure()方法,由於不一樣佈局類型的測量 流程各不相同,所以onMeasure()方法由它的子類來實現。

如下是FrameLayout的onMeasure()實現,頂層的DecorVeiw也是集成了FrameLayout

protected void onMeasurre(in widthMeasureSpec, int height MeasureSpec){
    final int cont = getChildCount()
    int maxHeight = 0;
    int maxWidth = 0;
    
    for( int i=0; i<count; i++){
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
                   measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                   maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
                   maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
               }
    }
    
     // Account for padding too
   maxWidth += mPaddingLeft + mPaddingRight + mForegroundPaddingLeft + mForegroundPaddingRight;
   maxHeight += mPaddingTop + mPaddingBottom + mForegroundPaddingTop + mForegroundPaddingBottom;

   // Check against our minimum height and width
   maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());//min_height屬性或者background的minheight屬性的最大值
   maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
   
   final Drawable drawable = getForeground();
   if (drawable != null) {
       maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
       maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
   }

   setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec),
           resolveSize(maxHeight, heightMeasureSpec));
}
public static int resolveSize(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: //Framelayout 寬高爲wrap_content 或者match_parent,specSize爲父View的大小,因此不能取size
       result = Math.min(size, specSize);
       break;
   case MeasureSpec.EXACTLY:
       result = specSize;
       break;
   }
   return result;
}
複製代碼
  1. 調用measureChildWithMargins()去測量每個子View的大小,找到最大高度和寬度保存在maxWidth/maxHeigth中。
  2. 將上一步計算的maxWidth/maxHeigth加上padding值,mPaddingLeft,mPaddingRight,mPaddingTop ,mPaddingBottom表示當前內容區域的左右上下四條邊分別到當前視圖的左右上下四條邊的距離,

mForegroundPaddingLeft ,mForegroundPaddingRight,mForegroundPaddingTop ,mForegroundPaddingBottom表示當前視圖的各個子視圖所圍成的區域的左右上下四條邊到當前視圖前景區域的 左右上下四條邊的距離,通過計算得到最終寬高。 3. 當前視圖是否設置有最小寬度和高度。若是設置有的話,而且它們比前面計算獲得的寬度maxWidth和高度maxHeight還要大,那麼就將它們做爲當前視圖的寬度和高度值。 4. 當前視圖是否設置有前景圖。若是設置有的話,而且它們比前面計算獲得的寬度maxWidth和高度maxHeight還要大,那麼就將它們做爲當前視圖的寬度和高度值。 5. 通過以上的計算,就獲得了正確的寬高,先調用resolveSize()方法,獲取MeasureSpec,接着調用父類的setMeasuredDimension()方法將它們做爲當前視圖的大小。

相關文章
相關標籤/搜索