小試牛刀-onMeasure方法

??onMeasure()方法從字面意思理解就是測量茸歧,還有個方法名很相似的方法measure()罗标。其中measure()是View測量的入口,它是一個被final修飾的方法获枝,這意味著無法被子類重寫蠢正,實質性的工作就是測量出一個View的實際大小,而真正實現(xiàn)測量工作的方法是onMeasure()來完成的省店,所以具體實現(xiàn)自定義View的測量工作我們是要去實現(xiàn)onMeasure()方法來完成的嚣崭。

先看下測量的基本流程

關于view的繪制流程,這就要從ViewRoot和DecorView的概念說起懦傍。

??《Android藝術開發(fā)探索》中介紹雹舀,ViewRoot對應于ViewRootImpl類,是連接WindowManager和DecorView的紐帶粗俱,View的三大繪制流程都是通過ViewRoot來完成的说榆。在ActivityThread中,當Activity被創(chuàng)建時寸认,會將DecorView添加到Window中签财,同時創(chuàng)建一個ViewRootImpl對象,病假ViewRootImpl對象和DecorView對象建立關聯(lián)偏塞。

??在開發(fā)中不能再子線程進行更新UI的操作荠卷,其實質性原因是ViewRootImpl是在UI線程中創(chuàng)建的,而ViewRootImpl在View中對應的是mParent變量烛愧,即ViewParent接口油宜。在調用其requestFitSystemWindows, requestLayout, invalidateChildInParent時候,都會調用measure方法怜姿。

那么measure到底是用來做什么的呢慎冤?

??從View的源碼中可以知道View的實際大小寬對應的值是mMeasuredWidth,高對應的值是mMeasuredHeight沧卢。而measure就是用來計算著兩個值用的蚁堤。

  public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
//先判斷是否有設置為opticalBounds的屬性
        if (optical != isLayoutModeOptical(mParent)) {
//如果當前的opticalBounds的屬性和父類的不一致,就重新計算wdithMeasureSpec和heightMeasureSpec但狭。
            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);
      //窗體需要強制刷新的時候披诗,如果是不用measure cache撬即,那么需要調用onMeasure方法進行進行view的width和height。如果有measure cache呈队,那么只需要拿到measure cache里的值進行更新就可以了剥槐。
        if (forceLayout || needsLayout) {
            // 首先清除測量的尺寸標記。
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                //測量我們自己宪摧,這應該設置測量尺寸標志回來(重新調onMeasure)
                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;
//保存當前的值到measure cache里和重新記錄old值粒竖,key值是用 widthMeasureSpec和heightMeasureSpec拼湊的64位數
        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }

當然最主要的還是關注onMeasure方法

??onMeasure方法很簡單,只有兩個流程几于。先調用getDefaultSize方法獲取測View的寬高蕊苗,在調用setMeasuredDimension指定View的寬高。

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

  public static int getDefaultSize(int size, int measureSpec) {
    //傳遞過來的默認大小值
    int result = size;
    //獲取測量模式
    int specMode = View.MeasureSpec.getMode(measureSpec);
    //獲取測量大小
    int specSize = View.MeasureSpec.getSize(measureSpec);
    switch (specMode) {
      //使用默認大小
      case View.MeasureSpec.UNSPECIFIED:
        result = size;
        break;
        //使用測量后的大小
      case View.MeasureSpec.AT_MOST:
      case View.MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    //返回View的寬或高
    return result;
  }

    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;
            //為View設置寬高
            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

再看一下傳入的參數widthMeasureSpec和heightMeasureSpec

??這兩個參數實際上是由測量規(guī)則和大小組的沿彭,它們是一個32位的int值朽砰,其中高2位是測量規(guī)則,低30位才是測量大小喉刘。在ScrollerView雨ListView或者GridView沖突的時候我們通常是重新計算ScrollerView和ListView的寬高瞧柔。
Integer.MAX_VALUE >> 2 (即int值左移動2位得到最大的測量值,指定模式為AT_MOST)
int height = View.MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, View.MeasureSpec.AT_MOST);

SpecMode分為三類

模式 規(guī)則
EXACTLY 父容器已經檢測出 View 所需要的精確大小,這個時候 View 的最終大小就是 SpecSize 所指定的值饱搏,它對應于LayoutParams 中的 match_parent 和具體的數值這兩種模式
AT_MOST 父容器指定了一個可用大小即 SpecSize非剃,View 的大小不能大于這個值置逻,具體是什么值要看不同 View 的具體實現(xiàn)推沸。它對應于 LayoutParams 中的 wrap_content
UNSPECIFIED 父容器不對 View 有任何的限制,要多大給多大券坞,這種情況下一般用于系統(tǒng)內部鬓催,表示一種測量的狀態(tài) 。

??一般可以重寫onMeasure對自定義控件指定其大小即可恨锚。但是在布局中使用wrap_content屬性的時候會失效宇驾,只要參考自定義View的wrap_content屬性失效即可兰迫。

  @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int with = MeasureSpec.getSize(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);
    setMeasuredDimension(with, height);
  }

在 生命周期中稽莉,無法直接得到 View 的寬高信息。

??view的寬高尺寸谐宙,只有在測量之后才能得到他挎,也就是measure方法執(zhí)行之后筝尾。通常使用View.getWidth()和View.getHeight()方法可以返回view的寬和高對應的值,在Activity的生命周期中不是一開始就能得到這兩個值的办桨,因為這此時measure方法還沒有被執(zhí)行筹淫,測量還沒有完成。
??有以下幾種常見的解決辦法:

  1. 在 Activity的onWindowFocusChanged 回調中獲取寬高呢撞。
  2. view.post(runnable)损姜,在 runnable 中獲取寬高饰剥。
  3. ViewTreeObserver 添加 OnGlobalLayoutListener,在 onGlobalLayout 回調中獲取寬高摧阅。
  4. 先調用 view.measure(0,0)汰蓉,再通過 getMeasuredWidth 和 getMeasuredHeight 獲取寬高。傳入0,0本質上是用系統(tǒng)框架幫我們測量逸尖。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末古沥,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子娇跟,更是在濱河造成了極大的恐慌岩齿,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苞俘,死亡現(xiàn)場離奇詭異盹沈,居然都是意外死亡,警方通過查閱死者的電腦和手機吃谣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門乞封,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人岗憋,你說我怎么就攤上這事肃晚。” “怎么了仔戈?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵关串,是天一觀的道長。 經常有香客問我监徘,道長晋修,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任凰盔,我火速辦了婚禮墓卦,結果婚禮上,老公的妹妹穿的比我還像新娘户敬。我一直安慰自己落剪,他們只是感情好,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布尿庐。 她就那樣靜靜地躺著忠怖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪屁倔。 梳的紋絲不亂的頭發(fā)上脑又,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機與錄音,去河邊找鬼问麸。 笑死往衷,一個胖子當著我的面吹牛,可吹牛的內容都是我干的严卖。 我是一名探鬼主播席舍,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼哮笆!你這毒婦竟也來了来颤?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤稠肘,失蹤者是張志新(化名)和其女友劉穎福铅,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體项阴,經...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡滑黔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了环揽。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片略荡。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖歉胶,靈堂內的尸體忽然破棺而出汛兜,到底是詐尸還是另有隱情,我是刑警寧澤通今,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布粥谬,位于F島的核電站,受9級特大地震影響衡创,放射性物質發(fā)生泄漏帝嗡。R本人自食惡果不足惜晶通,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一璃氢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧狮辽,春花似錦一也、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至树叽,卻和暖如春舆蝴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工洁仗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留层皱,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓赠潦,卻偏偏與公主長得像叫胖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子她奥,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內容