Android學習“易錯” 系列:老司機都掉的坑,你進去了嗎?

yilian發表於2020-03-31

之前分享了很多面試題,蠻多都自帶正確答案,來幾期易錯系列,大家一起醒醒神。

Android學習“易錯” 系列:老司機都掉的坑,你進去了嗎?

今天來個簡單的開胃一下。

這個知識點,我定義為在面試過程中 答對不加分,答錯扣分的題目,不過在我以前面試經歷中,能完整說上來的同學不多。

我們一起來看看大家對這個知識的掌握程度吧。

在早期的部落格的裡面,很多時候,見到有如下的介紹:

  • 如果你的  View設定了  match_parent,則在 onMeasure 中得到的測量模式為: EXACTLY;
  • 如果設定了 wrap_conent,則對應測量模式為: AT_MOST
  • 還剩下一個  UNSPECIFIED大家不用管,不常用;

上述描述每句話都可以認為是錯的。

那麼今天我要搞清楚幾個問題:

  1. match_parent / wrap_conent一定對應 EXACTLY/ AT_MOST 嗎 ?
  2. 測量模式到底是由哪些因素確定的?
  3. UNSPECIFIED 真的不常見嗎?
Android學習“易錯” 系列:老司機都掉的坑,你進去了嗎?

1.  match_parent和wrap_content就一定對應 MeasureSpec.EXACTLYMeasureSpec.AT_MOST嗎?

肯定不是。

為什麼呢?

因為 Viewmeasure時,它的寬高 MeasureSpec完全是取決於父容器,父容器傳的是什麼它收到的就是什麼。

如果這個父容器的 onMeasure方法裡面寫死了每個子 ViewMeasureSpecModeUNSPECIFIED的話,那麼無論你在 xml佈局或者 LayoutParams中怎麼設定寬高都好,最終子 ViewonMeasure收到的也是 UNSPECIFIED

好吧,故意手動指定的不算。

就以正常的角度來看:

我們都知道,自定義 ViewGroup過程中,需要在 onMeasure裡面對子 View進行測量。

在測量子 View時,往往會透過 measureChildmeasureChildWithMargins方法來完成(比如 FrameLayoutLinearLayoutCoordinatorLayoutViewPager2)。

或者呼叫 ViewGroup的靜態方法 getChildMeasureSpec來直接獲取目標子 ViewMeasureSpec,然後手動 measure(比如 ScrollViewNestedScrollViewDrawerLayoutTabLayoutConstraintLayout)。

其實, measureChildmeasureChildWithMargins裡面也是會透過 getChildMeasureSpec方法來獲取 MeasureSpec的,也就是說,上面提到的這些容器,在測量它們的子 View之前,都是先透過 getChildMeasureSpec方法來獲取子 View的寬高 MeasureSpec,然後傳給子 Viewmeasure方法的。

好,那我們現在來看看 getChildMeasureSpec方法裡面做了什麼:

  public static int getChildMeasureSpec(int spec, int padding, int childDimension) {      int specMode = MeasureSpec.getMode(spec);
      ......      switch (specMode) {          case MeasureSpec.EXACTLY:              if (childDimension >= 0) {
                  ......
                  resultMode = MeasureSpec.EXACTLY;
              } else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) {
                  ......
                  resultMode = MeasureSpec.EXACTLY;
              } else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {
                  ......
                  resultMode = MeasureSpec.AT_MOST;
              }              break;          case MeasureSpec.AT_MOST:              if (childDimension >= 0) {
                  ......
                  resultMode = MeasureSpec.EXACTLY;
              } else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) {
                  ......
                  resultMode = MeasureSpec.AT_MOST;
              } else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {
                  ......
                  resultMode = MeasureSpec.AT_MOST;
              }              break;          case MeasureSpec.UNSPECIFIED:              if (childDimension >= 0) {
                  ......
                  resultMode = MeasureSpec.EXACTLY;
              } else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) {
                  ......
                  resultMode = MeasureSpec.UNSPECIFIED;
              } else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {
                  ......
                  resultMode = MeasureSpec.UNSPECIFIED;
              }              break;
      }      return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
  }

可以看到:

父容器的 specMode為EXACTLY時,一切正常(子 View尺寸指定為 match_parent或精確的 dimen值時, Mode = EXACTLY,尺寸指定為 wrap_contentMode = AT_MOST);

父容器 specMode為AT_MOST的時候,呵呵,可以看到,除了指定了 dimen值之外,無論設定為 match_parentwrap_contentMode最終都是會變成 AT_MOST

如果 父容器 specModeUNSPECIFIED的話,跟上面的邏輯差不多,都是會變成 UNSPECIFIED的,除非指定了精確的 dimen值;

所以, ViewonMeasure方法中收到的寬高 MeasureSpec,不完全是由 xml佈局中設定的寬高或 LayoutParams的寬高值決定的。

2. 有哪些因素影響著MeasureSpec的mode?

從剛剛的getChildMeasureSpec方法中可以看出,影響著View測量模式的因素主要是該View所屬容器的測量模式。

也就是說,正常情況下(不是故意亂設定),View的測量模式是由:

**它自身的 LayoutParams設定的值 **+  父容器的測量模式來決定的。

為什麼大家都說 MeasureSpec.UNSPECIFIED不常見呢?

大家都覺得這個模式不常見,很可能就是因為在編寫佈局時, View的寬高只能選擇 match_parentwrap_content或者直接指定一個精確的尺寸,相對來說, MeasureSpec.UNSPECIFIED就顯得不太透明瞭,因為在日常開發中,如不需定製 View的話,基本上不會直接接觸到。

3. MeasureSpec.UNSPECIFIED是不是真的不常見?

在日常定製 View時,確實很少會專門針對這個模式去做特殊處理,大多數情況下,都會把它當成 MeasureSpec.AT_MOST一樣看待,就比如最最常用的 TextView,它在測量時也是不會區分 UNSPECIFIED和AT_MOST的。

不過,雖說這個模式比較少直接接觸到, 但很多場景下,我們已經在不知不覺中用上了,比如 RecyclerViewItem,如果 Item的寬/高是 wrap_content且列表可滾動的話,那麼 Item的寬/高的測量模式就會是 UNSPECIFIED

還有就是 NestedScrollViewScrollView,因為它們都是擴充套件自 FrameLayout,所以它們的子 View會測量兩次,第一次測量時,子 ViewheightMeasureSpec的模式是寫死為 UNSPECIFIED的。

我們在自定義 ViewGroup過程中,如果允許子 View的尺寸比 ViewGroup大的話,在測量子 View時就可以把 Mode指定為 UNSPECIFIED

好了,希望這次你徹底弄明白了自定義控制元件的測量模式相關知識。

另外也有人給我發了個圖,說這個圖就能說明白了,其實這個圖也有一點點小問題:

Android學習“易錯” 系列:老司機都掉的坑,你進去了嗎?

我畫圈的地方,這個值 不一定是 0, 不過大多情況下  UNSPECIFIED這個模式一般不在乎這個 size

最後

今天就講到這裡,我的Android核心技術學習大綱,獲取相關內容來我的GitHub一起玩耍:

對於進階這條路而言,學習是會有回報的!

你把你的時間投資在學習上,就意味著你可以收穫技能,更有機會增加收入。

分享我的 Android學習PDF大全
這份Android學習PDF大全真的包含了方方面面了,內含Java基礎知識點、Android基礎、Android進階延伸、演算法合集等等

Android學習“易錯” 系列:老司機都掉的坑,你進去了嗎?

我的這份學習合集,可以有效的幫助大家掌握知識點。

總之也是在這裡幫助大家學習提升進階,也節省大家在網上搜尋資料的時間來學習,也可以分享給身邊好友一起學習

Android學習PDF大全關注我看個人介紹,或者私信我獲取


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69952849/viewspace-2683658/,如需轉載,請註明出處,否則將追究法律責任。

相關文章