安卓自定義view(二) - 測量

view的測量過程

之所以先講view的測量過程,是因為ViewGroup測量的時候是先把他的所有子view測量完成后才能測量viewgroup自身,view的measure是viewGroup來調(diào)用的厦幅。在view的測量過程中,parentView首先會調(diào)用getChildMeasureSpec(int spec, int padding, int childDimension)

/**
 * Does the hard part of measureChildren: figuring out the MeasureSpec to
 * pass to a particular child. This method figures out the right MeasureSpec
 * for one dimension (height or width) of one child view.
 *
 * 這個方法將根據(jù)父容器的MeasureSpec和子View LayoutParams中的寬/高
 * 為子View生成最合適的MeasureSpec
 *
 * @param spec 父容器的MeasureSpec
 * @param padding 父容器的內(nèi)間距(padding)加上子View的外間距(margin)
 * @param childDimension 子View的LayoutParams中封裝的width/height
 * @return 子View的MeasureSpec
 */
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    // ① 對父容器的MeasureSpec進行解包
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);

    // ② 減去間距,得到最大可用空間
    int size = Math.max(0, specSize - padding);

    // 記錄子View最終的大小和測量模式
    int resultSize = 0;
    int resultMode = 0;

    switch (specMode) {
    // ③ 父容器是精準(zhǔn)測量模式
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {               //如果子view在LayoutParam中指定了大小,那么子view的resultSize 就是該大小滑频,模式是EXACTLY
            resultSize = childDimension; 
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {       //如果子view中LayoutParams是MATCH_PARENT,則view的大小等于最大可用大小唤冈,測量模式是EXACTLY
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {   //如果子view中LayoutParams是WRAP_CONTENT峡迷,則view的大小等于最大可用大小,測量模式是AT_MOST

            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

     // ④ 父容器指定了一個最大可用的空間
    case MeasureSpec.AT_MOST:
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

    // ⑤ 父容器不對子View的大小作出限制
    case MeasureSpec.UNSPECIFIED:
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
    }
    // ⑥ 將最終的size和mode打包為子View需要的MeasureSpec
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

計算該view的WidthMeasureSpec和HeightMeasureSpec你虹,然后調(diào)用view的measure();

public final void measure(int widthMeasureSpec, int  heightMeasureSpec) {
...

        onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}

measure方法又調(diào)用了onMeasure方法凉当,view.OnMeasure()才是真正完成了view的測量。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 setMeasuredDimension(   //設(shè)置測量的結(jié)果
    getDefaultSize(getSuggestedMinimumWidth(),
                                widthMeasureSpec),
    getDefaultSize(getSuggestedMinimumHeight(),
                                 heightMeasureSpec));
}

在view.onMeasure中調(diào)用setMeasuredDimension來保存測量的結(jié)果售葡,那么測量結(jié)果是怎么來的呢?我們繼續(xù)看getDefaultSize()忠藤;

public static int getDefaultSize(int size, int measureSpec) {
  int result = size;
 //1挟伙、獲得MeasureSpec的mode
  int specMode = MeasureSpec.getMode(measureSpec);
 //2、獲得MeasureSpec的specSize
  int specSize = MeasureSpec.getSize(measureSpec);

  switch (specMode) {
  case MeasureSpec.UNSPECIFIED:
    //這個我們先不看他
      result = size;
      break;
  case MeasureSpec.AT_MOST:
  case MeasureSpec.EXACTLY:
  //3、可以看到尖阔,最終返回的size就是我們MeasureSpec中測量得到的size
      result = specSize;
      break;
  }
  return result;
}

在getDefaultSize中傳入了兩個參數(shù)贮缅,一個是最小大小,一個是MeasureSpec介却。首先獲得MeasureSpec中的測量模式和暫定大小谴供,然后根據(jù)測量模式來返回不同的測量結(jié)果。如果測量模式是UNSPECIFIED齿坷,則測量的結(jié)果就是最小的大小桂肌,如果測量模式是AT_MOST或者EXACTLY,測量的大小都是MeasureSpec中的大小永淌。
而MeasureSpec中的大小是viewgroup調(diào)用getChildMeasureSpec生成的崎场,查看getChildMeasureSpec的邏輯我們會看出,如果子view的layoutParem是warp_content遂蛀,那么測量結(jié)果的測量模式就是AT_MOST谭跨,測量大小就是父容器的最大可用空間,所以我們在繼承view自定義view的時候李滴,如果重寫onMeasure螃宙,那么設(shè)置LayoutParam設(shè)置warp_content是不能用的,大小始終是可用的最大大小所坯。

那么getDefaultSize的最小大小是怎么來的呢谆扎?是通過getSuggestedMinimumWidth()和getSuggestedMinimumHeight()得到,我們繼續(xù)看getSuggestedMinimumWidth包竹。

protected int getSuggestedMinimumWidth() {
  return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
  }

在getSuggestedMinimumWidth中燕酷,首先判斷該view有沒有背景,如果沒有背景周瞎,返回android:minWidth(如果沒有設(shè)置android:minWidth苗缩,就默認是0),如果有背景声诸,就返回背景大小和android:minWidth中的最大值酱讶。

最后,onMeasure講測量的結(jié)果調(diào)用

getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),

保存起來彼乌,這樣一個view就算測量完成了泻肯。
畫個圖來總結(jié)一下。
首先viewGroup會計算子view的MeasureSpec慰照,然后講所計算的MeasureSpec傳入子view的measure中灶挟,子view開始測量自身。measure又會調(diào)用onMeasure來完成真正的測量過程毒租。onMeasure會調(diào)用setMeasureDemension來保存測量的結(jié)果稚铣,測量結(jié)果是通過getDefaultSize來計算的,getDefaultSize通過比較最小的大小和MeasureSpec中的暫定大小,最終確定測量大小惕医。

view.png

viewGroup的測量過程

viewGroup中耕漱,首先會測量所有子view的大小,然后根據(jù)子view的大小來確定viewGroup的大小抬伺。由于不同的ViewGroup的測量結(jié)果不一樣(LinearLayout的height是所有view的height之和螟够,而FrameLayout的Height是所有子view的最大的height),所以viewGroup中沒有實現(xiàn)具體的onMeasure()峡钓;我們以FrameLayout來舉例妓笙。
首先還是measure()方法調(diào)用onMeaure方法,具體的測量實現(xiàn)我們看onMeasure()即可

//這里的widthMeasureSpec椒楣、heightMeasureSpec
//其實就是我們frameLayout可用的widthMeasureSpec 给郊、
//heightMeasureSpec
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  //1、獲得frameLayout下childView的個數(shù)
  int count = getChildCount();
//2捧灰、看這里的代碼我們可以根據(jù)前面的Measure圖來進行分析淆九,因為只要parent
//不是EXACTLY模式,以frameLayout為例毛俏,假設(shè)frameLayout本身還不是EXACTL模式炭庙,
 // 那么表示他的大小此時還是不確定的,從表得知煌寇,此時frameLayout的大小是根據(jù)
 //childView的最大值來設(shè)置的焕蹄,這樣就很好理解了,也就是childView測量好后還要再
//測量一次阀溶,因為此時frameLayout的值已經(jīng)可以算出來了腻脏,對于child為MATCH_PARENT
//的,child的大小也就確定了银锻,理解了這里永品,后面的代碼就很 容易看懂了
  final boolean measureMatchParentChildren =
          MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
          MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
   //3、清理存儲模式為MATCH_PARENT的child的隊列
  mMatchParentChildren.clear();
  //4击纬、下面三個值最終會用來設(shè)置frameLayout的大小
  int maxHeight = 0;
  int maxWidth = 0;
  int childState = 0;
  //5鼎姐、開始便利frameLayout下的所有child
  for (int i = 0; i < count; i++) {
      final View child = getChildAt(i);
      //6、小發(fā)現(xiàn)哦更振,只要mMeasureAllChildren是true炕桨,就算child是GONE也會被測量哦,
      if (mMeasureAllChildren || child.getVisibility() != GONE) {
          //7肯腕、開始測量childView 
          measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);

          //8献宫、下面代碼是獲取child中的width 和height的最大值,后面用來重新設(shè)置frameLayout实撒,有需要的話
          final LayoutParams lp = (LayoutParams) child.getLayoutParams();
          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());

        //9遵蚜、如果frameLayout不是EXACTLY帖池,
          if (measureMatchParentChildren) {
              if (lp.width == LayoutParams.MATCH_PARENT ||
                      lp.height == LayoutParams.MATCH_PARENT) {
//10、存儲LayoutParams.MATCH_PARENT的child吭净,因為現(xiàn)在還不知道frameLayout大小,
//也就無法設(shè)置child的大小肴甸,后面需重新測量
                  mMatchParentChildren.add(child);
              }
          }
      }
  }

    ....
  //11寂殉、這里開始設(shè)置frameLayout的大小
  setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
          resolveSizeAndState(maxHeight, heightMeasureSpec,
                  childState << MEASURED_HEIGHT_STATE_SHIFT));

//12、frameLayout大小確認了原在,我們就需要對寬或高為LayoutParams.MATCH_PARENTchild重新測量友扰,設(shè)置大小
  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;
          if (lp.width == LayoutParams.MATCH_PARENT) {
              final int width = Math.max(0, getMeasuredWidth()
                      - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                      - lp.leftMargin - lp.rightMargin);

  //13、注意這里庶柿,為child是EXACTLY類型的childWidthMeasureSpec村怪,
  //也就是大小已經(jīng)測量出來了不需要再測量了
  //通過MeasureSpec.makeMeasureSpec生成相應(yīng)的MeasureSpec
              childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                      width, MeasureSpec.EXACTLY);
          } else {

  //14、如果不是浮庐,說明此時的child的MeasureSpec是EXACTLY的甚负,直接獲取child的MeasureSpec,
              childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                      getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                      lp.leftMargin + lp.rightMargin,
                      lp.width);
          }

  // 這里是對高做處理审残,與寬類似
          final int childHeightMeasureSpec;
          if (lp.height == LayoutParams.MATCH_PARENT) {
              final int height = Math.max(0, getMeasuredHeight()
                      - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
                      - lp.topMargin - lp.bottomMargin);
              childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                      height, MeasureSpec.EXACTLY);
          } else {
              childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                      getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                      lp.topMargin + lp.bottomMargin,
                      lp.height);
          }

  //最終梭域,再次測量child
          child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
      }
  }
}
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {

        // 獲取子視圖的布局參數(shù)
        final LayoutParams lp = child.getLayoutParams();

        // 調(diào)用getChildMeasureSpec(),根據(jù)父視圖的MeasureSpec & 布局參數(shù)LayoutParams搅轿,計算單個子View的MeasureSpec
         // getChildMeasureSpec()請回看上面的解析
         // 獲取 ChildView 的 widthMeasureSpec
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        // 獲取 ChildView 的 heightMeasureSpec
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        // 將計算好的子View的MeasureSpec值傳入measure()病涨,進行最后的測量
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

從上述代碼我們可以將viewGroup的測量過程分為一下幾個步驟

1.初始化變量

獲取childCount,清理存儲模式為MATCH_PARENT的child的隊列璧坟,初始化最大寬度和最大高度既穆。

2.遍歷所有子view,獲取子view的MeasureSpec雀鹃,測量子view的大小幻工,獲得所有子view的最大寬度和最大高度

通過for循環(huán)遍歷所有的子view,對于每一個子view褐澎,調(diào)用measureChildWithMargins会钝,measureChildWithMargins中首先獲得view的MeasureSpec,然后調(diào)用子view的measure完成子view的測量工三。

3. 根據(jù)自身MeasureSpec和子view的最大寬度和最大高度迁酸,確定自身大小

4. 重新確定LayoutParam==MATCH_PARENT的子view

由于LayoutParam==MATCH_PARENT的子view的大小還沒有確定,根據(jù)已經(jīng)確定的Viewgroup的大小俭正,重新計算子view的精確大小奸鬓。

最后viewGroup和view的測量過程結(jié)合起來畫一張圖

viewGroup.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市掸读,隨后出現(xiàn)的幾起案子串远,更是在濱河造成了極大的恐慌宏多,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件澡罚,死亡現(xiàn)場離奇詭異伸但,居然都是意外死亡,警方通過查閱死者的電腦和手機留搔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門更胖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人隔显,你說我怎么就攤上這事却妨。” “怎么了括眠?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵彪标,是天一觀的道長。 經(jīng)常有香客問我掷豺,道長捞烟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任萌业,我火速辦了婚禮坷襟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘生年。我一直安慰自己婴程,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布抱婉。 她就那樣靜靜地躺著档叔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蒸绩。 梳的紋絲不亂的頭發(fā)上衙四,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機與錄音患亿,去河邊找鬼传蹈。 笑死,一個胖子當(dāng)著我的面吹牛步藕,可吹牛的內(nèi)容都是我干的惦界。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼咙冗,長吁一口氣:“原來是場噩夢啊……” “哼沾歪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起雾消,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤灾搏,失蹤者是張志新(化名)和其女友劉穎挫望,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狂窑,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡媳板,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了泉哈。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拷肌。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖旨巷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情添忘,我是刑警寧澤采呐,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站搁骑,受9級特大地震影響斧吐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜仲器,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一煤率、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧乏冀,春花似錦蝶糯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至肢扯,卻和暖如春妒茬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蔚晨。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工乍钻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人铭腕。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓银择,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谨履。 傳聞我的和親對象是個殘疾皇子欢摄,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容