重溫View測量之MeasureSpec


需求

 



這個是手機QQ吃喝玩樂里面的,選擇城市界面


這個是手機QQ吃喝玩樂里面的,選擇城市界面。就是一個ListView里面嵌套着不同規格的GridView/ListView,在比如電商里面的,物品分類界面,這種需求很常見,當然解決的辦法也有很多。下面根據自己的工作經驗介紹種很常用的方法。

 

理解MeasureSpec

 



以前剛剛接觸android的時候,就感覺這還不簡單,直接,在ListView的適配器中,根據gridview的行數乘以gridView的每個item,然后去設置gridView的LayoutParams,這樣做不是不行,而是太浪費性能了,我們知道,在Adapter的getView方法里面不能做太多的工作,要做工作也要異步去完成,比如圖片的加載。否則會出現卡頓,我們這里第一步要計算,第二部要去設置LayoutParams,在重新繪制,GridView,然而我們之前已經繪制GridView過一次了。可想而知,這不是一種很好的方法。

 

在介紹常規的方法之前,我們先復習下MeasureSpec,這是個很重要的知識點。因為android的View在測量的時候離不開他,也可以說,是根據他來測量View的大小的。MeasureSpec是一個32位的int值,最高的兩位代表了SpecMode(測量模式),后面的低30位代表了SpecSize(規格大小)。SpecMode有三個值,分別是UNSPECIFIED、EXACTLY、AT_MOST。

   /** * Figures out the measure spec for the root view in a window based on it's * layout params. * * @param windowSize * The available width or height of the window * * @param rootDimension * The layout params for one dimension (width or height) of the * window. * * @return The measure spec to use to measure the root view. */
    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }

這是從ViewRootImpl(頂級View)中截取的代碼段,根據布局的參數生成了我們所需要的MeasureSpec。如果我們的頂級View設置成了match_parent,那么我們只能給當前窗口的大小了,如果不是match_parent和wrap_content的話,我們只能給他所需要的大小了,這些都屬於精確測量,比如50dp的大小。而如果頂級View是wrap_content模式的話,我們只能用最大模式At_most了,告訴下層的View,最大的空間也只有windowsize這么大了,你看着搞把。這樣,我們在繪制子View的時候就會根據父View的MeasureSpec和自身的LayoutParams來測量和繪制自身了。


    /** * 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. * * The goal is to combine information from our MeasureSpec with the * LayoutParams of the child to get the best possible results. For example, * if the this view knows its size (because its MeasureSpec has a mode of * EXACTLY), and the child has indicated in its LayoutParams that it wants * to be the same size as the parent, the parent should ask the child to * layout given an exact size. * * @param spec The requirements for this view * @param padding The padding of this view for the current dimension and * margins, if applicable * @param childDimension How big the child wants to be in the current * dimension * @return a MeasureSpec integer for the child */
    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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

這是ViewGroup在繪制子View時,獲取子View的MeasureSpec的代碼,可以看到,從父View得到的不同的測量模式分別為精確測量,最大化測量和不准確測量,都有不同的結果。


實現

 



按照上面的源碼分析,我們可以嘗試把我之前的工作簡單化處理,像計算和測量都讓他繪制的時候自己去處理,這樣效率會好很多。那么我們重新繼承GridView,重寫它的onMeasure方法:

 

public class MyGridView extends GridView{

    public MyGridView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, expandSpec);
    }

}

每次測量高度的時候,將GridView的高度改成AT_MOST模式測量,那么根據上面的分析,如果parent是AT_MOST,而子控件的高度是准確的話,那么子控件的規格就是 SpecMode :EXACTLY,SpecSize:childSize。

   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;

這樣我們就完美的解決了我們的需求,在也不用自己測量,然后重新繪制了…


代碼

 



仿QQ吃喝玩樂選擇城市列表

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM