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

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

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

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

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

在早期的博客的裏面,很多時候,見到有如下的介紹:

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

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

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

  1. match_parent / wrap_conent一定對應EXACTLY/ AT_MOST 嗎 ?
  2. 測量模式到底是由哪些因素確定的?
  3. UNSPECIFIED 真的不常見嗎?

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

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

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

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

最後

今天就講到這裏,我的Android核心技術學習大綱,獲取相關內容來我的GitHub一起玩耍:https://github.com/Meng997998/AndroidJX

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

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

分享我的Android學習PDF大全

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

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

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章