流式佈局的實現-2-onMeasure的實現

在這一篇中,我們主要是來實現FlowLayout中的onMeasure函數。

先說一說onMeasure,可以說重載 onMeasure(),onLayout(),onDraw()三個函數構建了自定義View的外觀形象。再加上onTouchEvent()等重載視圖的行爲,可以構建任何我們需要的可感知到的自定義View。我們知道,不管是自定義View還是系統提供的TextView這些,它們都必須放置在 LinearLayout等一些ViewGroup中,因此理論上我們可以很好的理解onMeasure(),onLayout(),onDraw()這 三個函數:1.View本身大小多少,這由onMeasure()決定;2.View在ViewGroup中的位置如何,這由onLayout()決 定;3.繪製View,onDraw()定義瞭如何繪製這個View。

在TextView中onMeasure是:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
  
         int width;
         int height;
  
         ... 
         if (widthMode == MeasureSpec.EXACTLY) {  
           // Parent has told us how big to be. So be it.
             width = widthSize;          
         } 
         else {
           if (mLayout != null && mEllipsize == null) { 
                      des = desired(mLayout);             
            }  
                      ...    
         }       
         setMeasuredDimension(width, height);  
    }

首先我們要理解的是widthMeasureSpec, heightMeasureSpec這兩個參數是從哪裏來的?onMeasure()函數由包含這個View的具體的ViewGroup調用,因此值也是 從這個ViewGroup中傳入的。這裏我直接給出答案:子類View的這兩個參數,由ViewGroup中的 layout_width,layout_height和padding以及View自身的layout_margin共同決定。權值weight也是尤其需要考慮的因素,有它的存在情況可能會稍微複雜點。

瞭解了這兩個參數的來源,還要知道這兩個值的作用。我們只取 heightMeasureSpec作說明。這個值由高32位和低16位組成,高32位保存的值叫specMode,可以通過如代碼中所示的 MeasureSpec.getMode()獲取;低16位爲specSize,同樣可以由MeasureSpec.getSize()獲取。那麼 specMode和specSize的作用有是什麼呢?要想知道這一點,我們需要知道代碼中的最後一行,所有的View的onMeasure()的最後一行都會調用setMeasureDimension()函數的作用——這個函數調用中傳進去的值是View最終的視圖大小。也就是說 onMeasure()中之前所作的所有工作都是爲了最後這一句話服務的。

之後我們開始一片一片分析代碼:

第一部分:
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

分別獲得在widthMeasureSpec,heightMeasureSpec中的size與mode:

getSize,getMode和過程中涉及到的變量:

public static int getMode(int measureSpec) {
      return (measureSpec & MODE_MASK);
 }
public static int getSize(int measureSpec) {
       return (measureSpec & ~MODE_MASK);
 } 
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY     = 1 << MODE_SHIFT;
public static final int AT_MOST     = 2 << MODE_SHIFT;
第二部分:
        int width = 0;
        int height = 0;
 
        // 記錄每一行的寬度與高度
        int lineWidth = 0;
        int lineHeight = 0;
 
        // 得到內部元素的個數
        int cCount = getChildCount();
在註釋中都已說明,就不解釋了;
第三部分:
  for (int i = 0; i < cCount; i++)
        {
            View child = getChildAt(i);
            // 測量子View的寬和高
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            // 得到LayoutParams
            MarginLayoutParams lp = (MarginLayoutParams) child
                    .getLayoutParams();
 
            // 子View佔據的寬度
            int childWidth = child.getMeasuredWidth() + lp.leftMargin
                    + lp.rightMargin;
            // 子View佔據的高度
            int childHeight = child.getMeasuredHeight() + lp.topMargin
                    + lp.bottomMargin;
 
            // 換行
            if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight())
            {
                // 對比得到最大的寬度
                width = Math.max(width, lineWidth);
                // 重置lineWidth
                lineWidth = childWidth;
                // 記錄行高
                height += lineHeight;
                lineHeight = childHeight;
            } else
            // 未換行
            {
                // 疊加行寬
                lineWidth += childWidth;
                // 得到當前行最大的高度
                lineHeight = Math.max(lineHeight, childHeight);
            }
            // 最後一個控件
            if (i == cCount - 1)
            {
                width = Math.max(lineWidth, width);
                height += lineHeight;
            }
        }

先獲得子View,再獲得子View的layoutParams,因爲是有間距的,所以注意要子View的LayoutParams強制轉化爲MarginLayoutParams;

重新計算子View的寬:因爲有左右間距,所以新的寬是原寬加上左右間距;同理,高也是如此;

之後判斷要不要換行,即已計算的行寬加上新的子View的寬,如果大於FlowLayout的寬減去左右邊距說明剩餘的寬度,不夠加上新的子View,要把新的子View放到新的一行中去(其實有做過acm的話,可以用到線段樹來進行優化),否則加入到原行中。之後在這兩種情況下對行高,行寬,高和寬做不同的調整;

如果到了最後一個子View的話:如果要換行的話,高只是記錄到之前的部分,而對換行後新加的高度即最後一個子View的高沒有加上,寬的話,如果最後一個子View的寬很大超過之前的話,也沒有更新;如果不要換行的話,最後一行的高與寬就始終沒有加上;所以最後一個View要進行特判;因爲無論是換行還是不換行,行寬都更新過了,所以只要和最大的寬做比較即可;高則只要加上行高即可;

第四部分:
  setMeasuredDimension(
                //
                modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width + getPaddingLeft() + getPaddingRight(),
                modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop()+ getPaddingBottom()//
        );
判斷是不是MeasureSpec.EXACTLY即是不是內容鋪滿父容器,是的話,就直接將一開始調用getSize得到的寬高傳入即可,如果不是,則要將測得的寬高加上邊距傳入到setMeasuredDimension函數中,來確定View最終的視圖大小。


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