1. 程式人生 > >直接繼承View來自定義控制元件時,需要重寫onMeasure()方法並設定wrap_content時的大小 原理分析

直接繼承View來自定義控制元件時,需要重寫onMeasure()方法並設定wrap_content時的大小 原理分析

        之前在校學習的時候,一直沒有在網上找到比較靠譜的解釋,現在畢業了,程式設計能力也比之前有了不小的提高,就讀了一些原始碼,加上一些書上的解釋,現在算是大體知道原因了吧!如果哪裡說的不對,歡迎批評指正。

       在開始本篇的正文之前,請允許我先粗略的解釋一下MeasureSpec的作用,對本篇的理解會有幫助,但是關於View繪製的流程,本篇暫時不多做介紹了,對View的繪製流程還不是很熟悉的同學,請先通過一些書籍或者其他的部落格瞭解一下View繪製的流程。後期有時間的時候,我會整理一下View繪製的流程,然後發一篇部落格,雖然現在網上相關的內容也有不少,但是看了許多之後,自身感受就是要麼部落格寫的千篇一律,要麼就是涵蓋不全(雖然我是個渣渣,但是寶寶會努力的委屈

),當然也有很多大神級的人物寫的部落格,還是很好的。(真心感謝一些大神的部落格,收穫頗豐)。下面開始:

      MeasureSpec可以理解成是View測量的說明書吧,一個View的MeasureSpce受到本身的LayoutParams以及父View的MeasureSpec的影響。 MeasureSpce裡有兩個比較重要的屬性SpecMode和SpecSize,SpecMode可以理解成是View的測量模式,SpecSize代表的是View的測量大小。MeasureSpec很大程度上影響了一個View尺寸。

其中SpecMode有三類測量模式:

(1)UNSPECIFIED: 這個模式平時用的貌似不太多,查了一下資料,表示父容器不對View有任何限制,一般用於系統內部,其他的也就不再多提了。

(2)EXACTLY:精確模式,此時View的大小就是SpecSize的值,對應match_parent和具體的數值這兩種。

(3)AT_MOST:最大化模式,此時View的大小不能超過SpecSize的大小,對應於wrap_content。

好了下面開始正文:為什麼在直接繼承View來自定義控制元件時,需要重寫onMeasure()方法並設定wrap_content的大小奮鬥

      我們應該都會了解過,View繪製的流程是從父View傳遞到子View的,對於ViewGroup來說,除了完成自己的measure過程之外,還要完成其所有子View的measure過程,因為對於不同的子View來說,測量的細節也是不相同的,ViewGroup不能夠針對不同的子View做統一的measure,所以ViewGroup是一個抽象的類,把測量的過程交給了子View本身去測量,在ViewGroup中有一個measureChildren()方法來遍歷所有的子View,執行子View的measure來進行子View的測量。以下是measureChildren()方法的程式碼:

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

我們可以看到在measureChildren()方法中,去遍歷了每一個子View,並通過measureChild()方法來進行對子View的測量,以下是measureChild()方法的程式碼:

protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

我們可以看到,在measureChild()方法中,呼叫了getChildMeasureSpec()方法來獲取width和height對應的MeasureSpec(稍後會再對getChildMeasureSpec()方法做介紹,在這個地方請先忽略,只知道是獲取MeasureSpec的方法就行了。),然後作為引數,呼叫子View的measure()方法,即child.measure(childWidthMeasureSpec , childHeightMeasureSpec)方法來進行子View的measure過程,以下是該方法的程式碼:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }

        // Suppress sign extension for the low bytes
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

        // Optimize layout by avoiding an extra EXACTLY pass when the view is
        // already measured as the correct size. In API 23 and below, this
        // extra pass is required to make LinearLayout re-distribute weight.
        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
                || heightMeasureSpec != mOldHeightMeasureSpec;
        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("View with id " + getId() + ": "
                        + getClass().getName() + "#onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;

        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }

該程式碼相對比較多,我們只看比較重要的部分,由以上程式碼我們可以看出measure()方法為final型別的方法,因此不被重寫,我們可以看到在以上程式碼的第38行,呼叫了onMeasure()方法來進行View的測量,下面是onMeasure()方法的相關程式碼:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }


可以看到,在onMeasure()方法中呼叫了setMeasureDimension()方法,看這個方法的名字就就可以知道,setMeasureDimension()方法的作用是設定View測量後width和height的大小,這個方法的程式碼就不貼出來了,我們可以看到該方法的有兩個引數,兩個引數分別對代表View測量的width和height,兩個引數的值都是getDefaultSize()方法的返回值,下面我們來看下getDefaultSize()方法的原始碼:

public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

在getDefaultSize()中,首先根據傳遞過來的MeasureSpec,來獲取對應的SpecMode和SpecSize,我們重點看下SpecMode的AT_MOST和EXACTLY模式,當View的SpecMode處於AT_MOST和EXACTLY這兩種模式下的時候,其返回值result都是SpecSize的值。(結論1)

到這裡,我先總結一下以上的流程:ViewGroup會以自身的MeasureSpec為引數,呼叫measureChildren()方法,遍歷每一個子View,並以子View和自身的MeasureSpec為引數,呼叫measureChild()方法,在measureChild()中,會以自身的MeasureSpec為引數,呼叫getChildMeasureSpec()方法來獲取子View的MeasureSpec,然後以獲得的子View的MeasureSpec為引數,呼叫子View的measure()方法來進行子View的測量。在子View的measure()方法中,會去呼叫onMeasure()進行View的測量,在onMeasure()中會呼叫setMeasureDimension()方法來設定View測量的寬和高,該寬和高的值為getDefaultSize()方法的返回值,在getDefaultSize()方法中,當子View的MeasureSpec的specMode為AT_MOST和EXACTLY時,getDefaultSize()方法的返回值是子View的MeasureSpec中SpecSize的值。
下面我們來看一下上文中提到但是沒有解釋的getChildMeasureSpec()方法。以下是該方法的程式碼:

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;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

注意,該方法的第一個引數spec,即MeasureSpec,是父View的MeasureSpec,最後一個引數,childDimension對應的是wrap_content或者是match_parent或者是固定的值。

在該方法中,首先會獲得父View的MeasureSpec中對應的specMode和specSize,然後對父View的specMode做判斷,根據程式碼我們可以得出結論:不論父View的specMode是EXACTLY模式,還是AT_MOST模式,當childDimension == WRAP_CONTENT時,即我們設定View的layout_width或者layout_height為wrap_content時,最後得到的resultMode的結果都是AT_MOST模式,resultSize的值等於size的值,而size的值,請看上述程式碼的第5行,size = Math.max(0 , specSize - padding),即size的值是父View的specSize的值減去padding,也就是說,size的值是父View去掉padding之後剩餘空間的值,總結一下就是:當我們對View設定wrap_content的時候,最終獲得的View的MeasureSpec的值中,SpecMode的值是AT_MOST,SpecSize值是父View剩餘去掉padding之後剩餘空間的值,而從上述程式碼我們也可以看出,當childDimension == MATCH_PARENT時,最終獲得的SpecMode的值可能是AT_MOST,也可能是EXACTLY,而SpecSize的值都是size,也就是父View去掉padding之後剩餘空間的值也就是說,不管我們對View設定為wrap_content還是match_parent,最終我們把子View的MeasureSpec傳遞到getDefaultSize()中之後,得到最後的測量後的大小,都是父View去掉padding之後剩餘空間的值,因為不管是wrap_content還是match_parent,最終得到的SpecMode不是AT_MOST就是EXACTLY。(結論2)(在這個地方有點暈的話請看結論1)。

    因此,現在我們可以得出結論,在我們直接繼承View來自定義控制元件時,如果不重新onMeasure()方法,不對wrap_content做處理的話,最終對控制元件設定wrap_content和match_parent後,得到效果是一樣的。所以我們需要重寫onMeasure)()方法,設定wrap_content時的預設大小。

相關推薦

直接繼承View來自定義控制元件需要重寫onMeasure()方法設定wrap_content大小 原理分析

        之前在校學習的時候,一直沒有在網上找到比較靠譜的解釋,現在畢業了,程式設計能力也比之前有了不小的提高,就讀了一些原始碼,加上一些書上的解釋,現在算是大體知道原因了吧!如果哪裡說的不對,歡迎批評指正。        在開始本篇的正文之前,請允許我先粗略的解釋一

Android自定義控制元件系列:詳解onMeasure()方法中如何測量一個控制元件尺寸(一)

轉載請註明出處:http://blog.csdn.net/cyp331203/article/details/45027641 今天的任務就是詳細研究一下protected void onMeasure(int widthMeasureSpec, int he

繼承式自定義控制元件——滑動ScrollView標題顏色漸變

MainActivity.java public class MainActivity extends AppCompatActivity { private ImageView mIvDetail; private ObservableScr

android 自定義控制元件邊框顏色線條圓滑程度

1,在drawable資料夾中右鍵,new->drawableresource file,彈出一個視窗。 2,將selector改為shape,輸入,該xml的名字table_shape,點選確定,接下來就將原來的控制元件變成圓滑控制元件。(drawable/ tab

winform自定義控制元件之ComboBox簡單重寫

由於專案需要,現有的ComboBox控制元件滿足不了需求,需要重寫做一些小小的改變。要求ComboBox每一項前增加圖片顯示,使邊框顏色修改,及禁用滑鼠滾輪修改當前選項。 定義ComboBox選擇項類 using System; using System.Collectio

徹底搞懂自定義控制元件中的四個構造方法

    在上一篇部落格動手實現餅圖控制元件寫完以後,有些小夥伴說講得不夠細,建議從最基本開始講起,比如建構函式都是什麼?我覺得說得很有道理,正好自己也不夠了解自定義控制元件中的4個構造方法的具體呼叫時機和它們各自的引數作用,今天終於有時間把這部分內容進行學習整理,順便分享給那

ASP.NET (VB) 載入使用者自定義控制元件 (ascx)提交會消失的解決方法

在ASP.NET裡動態新增自定義控制元件(ascx),按了Button控制元件,會消失;雖然用LoadControl放在IsPostBack外面,可以解決消失問題,但是要按2次Button,提交2次,才能把ascx裡的資料提交出去。 經過網上搜索,找到最終解決方法。 在

Android自定義控制元件BannerLayout實現廣告輪播

Android自定義廣告輪播圖 自定義的BannerLayout,通過ViewPager來實現,配合Glide 實現本地以及網路圖片的載入。 效果: 專案結構: 在build.gradle中新增對Glide (圖片載入框架)的引用: compile 'c

[C#] (原創)一步一步教你自定義控制元件——02ScrollBar(滾動條)

一、前言 技術沒有先進與落後,只有合適與不合適。 本篇的自定義控制元件是:滾動條(ScollBar)。 我們可以在網上看到很多自定義的滾動條控制元件,它們大都是使用UserControl去做,即至少使用一個Panel或其它控制元件作滑塊,使用UserControl本身或另一個控制元件作為背景條,而有的複雜的還

[C#] (原創)一步一步教你自定義控制元件——03SwitchButton(開關按鈕)

一、前言 技術沒有先進與落後,只有合適與不合適。 本篇的自定義控制元件是:開關按鈕(SwitchButton)。 開關按鈕非常簡單,實現方式也多種多樣,比如常見的:使用兩張不同的按鈕圖片,代表開和關,然後在點選時切換這兩張圖片。 而本篇和前兩篇一脈相承,都是繼承Control,使用GDI+去實現。因為都是相同

[C#] (原創)一步一步教你自定義控制元件——04ProgressBar(進度條)

一、前言 技術沒有先進與落後,只有合適與不合適。 本篇的自定義控制元件是:進度條(ProgressBar)。 進度條的實現方式多種多樣,主流的方式有:使用多張圖片去實現、使用1個或2個Panel放到UserControl上去實現、過載系統進度條去實現等等。 本次所實現的進度條仍是使用GDI+去實現。當然,如果

[C#] (原創)一步一步教你自定義控制元件——05Label(原生控制元件

一、前言 技術沒有先進與落後,只有合適與不合適。 自定義控制元件可以分為三類: 一類是“無中生有”。就如之前文章中的的那些控制元件,都是繼承基類Control,來實現特定的功能效果; 一類是“有則改之”。是對原生控制元件的改造,以達到特定的功能效果; 一類是“使用者控制元件”。是將多個控制元件進行組合,以實現

[C#] (原創)一步一步教你自定義控制元件——06MaskLayer(遮罩層)

一、前言 技術沒有先進與落後,只有合適與不合適。 本篇的自定義控制元件是:遮罩層(MaskLayer)。 遮罩層對軟體的美觀與易用性上的提高是很大的,在日常使用過程中也會經常看到各種遮罩層,雖然WinForm本身沒有原生的遮罩層控制元件,但實現起來並不麻煩。 遮罩層的實現方式一般有兩種:一種是基於自定義控制元

解讀Google官方SwipeRefreshLayout控制元件原始碼帶你揭祕Android下拉重新整理的實現原理

前言 想必大家也發現,時下的很多App都應用了這個Google出品的SwipeRefreshLayout下拉重新整理控制元件,它以Material Design風格、適用場景廣泛,簡單易用等特性而獨步江湖。但在我們使用的過程中,不可避免地會發現一些bug,或者

object物件重寫equals方法為什麼需要重寫hashCode方法

在Java語言中,equals方法在使用時:     針對包裝物件,比較的是物件的值(包括 boolean,byte,char,short,int,long,float,double)     針對String物件,比較的也是String的值(因為String內部重寫了e

Android 自定義控制元件繼承view

一.自定義控制元件的型別:            1.繼承view(自繪檢視:view中的內容是我們自己繪製出來的,需要重寫onDraw方法)            2.繼承已有原生控制元件            3.自定義組合控制元件(將系統原生的控制元件組合到一起) 本

繼承View的自定義控制元件的warp_content和padding屬性處理

繼承自view的自定義控制元件的wrap_content和padding兩個屬性不會生效,解決方法/** * 自定義一個圓形 * @author luoshen * 2016年11月29日下午1:28:04 */ public class CircleView ex

Android自定義View--翻書控制元件(一)

0.前言 最近重看了一遍封神演義,感覺QQ閱讀那個翻書的效果挺好的,準備做一個。上週五下午用了兩個小時只寫了一部分功能,以後有時間再完善 1.分析 先看效果圖 這個空間,說簡單也簡單,說難也難,簡單就在於這個效果主要就是依賴canvas的clippath才見到部分canvas,難就難在裁

定義控制元件01---簡單view的實現

對於每一個應用來說幾乎都會有一個Topbar,並且基本都是類似的那麼假如應用有好多個頁面的話,就要寫好多遍,可以在Topbar整合為一個控制元件來使用,針對於這個的學習,總結如下: 1 atts自定義屬性的定義 res–values-atts.xml <?xml versi

定義控制元件View

先在佈局中寫 <com.luyao.dell.yuan.YurnTableView android:layout_width="match_parent" android:layout_height="match_parent" /> 然後在定義一個cl