自定義View(二)

自定義view有三個(gè)重要的方法,onMeasure债沮,onLayout,onDraw本鸣。今天先從onMeasure開始疫衩。


View層次

首先,先從最簡單的看起永高。我們最常用到的設(shè)置布局就是

setContentView(R.layout.activity_main);```

  在Activity中找到它的源碼:

public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}

public Window getWindow() {

return mWindow;

}```
所以可以看出調(diào)用了Window里的setContentView方法。而Window類是一個(gè)抽象類提针,其唯一實(shí)現(xiàn)類是PhoneWindow命爬,看看PhoneWindow類的setContentView方法。

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }```

首先判斷mContentParent是否為空辐脖,mContentParent是一個(gè)ViewGroup對(duì)象饲宛,即判定是否第一次調(diào)setContentView方法,如果是就調(diào)用installDecor方法嗜价,如果不是就清除ViewGroup里的View艇抠。

private void installDecor() {
//只展示核心代碼
    if (mDecor == null) {
        mDecor = generateDecor();
        mDecor.setIsRootNamespace(true);
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);
}
}
 

protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
public DecorView(Context context, int featureId) {
super(context);
mFeatureId = featureId;
}
}```

mDecor是DecorView的一個(gè)對(duì)象幕庐,DecorView是PhoneWindow里的一個(gè)內(nèi)部類。首先判斷mDecor是否為空家淤,如果為空就創(chuàng)建一個(gè)DecorView對(duì)象异剥,接下來判斷mContentParent是否為空,如果是絮重,調(diào)用generateLayout冤寿。

protected ViewGroup generateLayout(DecorView decor)
        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        return contentParent;
    }

contentParent和mDecor創(chuàng)建完畢后,通過PhoneWindow里的setContentView的mLayoutInflater.inflate(layoutResID,mContentParent)把xml文件傳遞到屏幕青伤。

通過Hierarchy View得到的View層次結(jié)構(gòu):


Hierarchy View

整個(gè)屏幕是一個(gè)FrameLayout督怜,里面包含一個(gè)LinearLayout和兩個(gè)View,分別是statusBar和nagavitionBar狠角,狀態(tài)欄和導(dǎo)航欄号杠,LinearLayout里是一個(gè)FrameLayout,F(xiàn)rameLayout里又嵌套一個(gè)ViewGroup(普遍當(dāng)成是LinearLayout)丰歌,ViewGroup里有兩個(gè)FrameLayout姨蟋,一個(gè)是content,一個(gè)是title动遭。


view

至此view的層次介紹完畢芬探。接下來看一下自定義View的onMeasure方法。


onMeasure

    @Override  
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
    }

重寫onMeasure方法厘惦,直接調(diào)用super.onMeasure()調(diào)用父類方法偷仿,或者直接調(diào)用setMeasuredDimension設(shè)置view的寬高。里面有兩個(gè)很重要的參數(shù)宵蕉,widthMeasureSpec和heightMeasureSpec酝静,這兩個(gè)參數(shù)。至于這兩個(gè)參數(shù)的來源羡玛,就要找到view的繪制方法别智。view的繪制是在ViewRootImpl的performTraversals方法實(shí)現(xiàn)的

    private void performTraversals() {
        // cache mView since it is used so much below...
        final View host = mView;
        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    
    }
   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;
    }
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

getRootMeasureSpec傳入兩個(gè)參數(shù),mWidth和lp.width稼稿,lp.width和lp.height的值為MATCH_PARENT薄榛。然后把得到的measureSpec傳給performMeasure,performMeasure調(diào)用view的measure方法,measure方法又調(diào)用onMeasure方法让歼,這樣就完成了widthMeasureSpec和heightMeasureSpec從ViewRootImpl到onMeasure的傳遞敞恋。

在這里用到了一個(gè)特殊的類MeasureSpec。

    public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }
    public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
    public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

MeasureSpec中mode為高兩位谋右,size為低30位硬猫。MODE_MASK為11 000...(30個(gè)0),&的規(guī)則是遇0變0,遇1不變啸蜜。所以getMode就取得了高兩位坑雅,getSize對(duì)MODE_MASK取反,變成了00 11111...(30個(gè)1)取得低30位衬横。

在onMeasure方法中裹粤,調(diào)用如下代碼:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
    protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
    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;
    }

ViewGroup沒有重寫view的onMeasure方法,但是提供了一個(gè)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);
            }
        }
    }
    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);
    }

可以看到冕香,measureChildren方法遍歷view并調(diào)用measureChild方法蛹尝,measureChild方法根據(jù)父類的MeasureSpec和自身的LayoutParam構(gòu)成自身的measureSpec并調(diào)用自身的measure方法測(cè)量。這就是我們常說的view的大小是由父類的大小和自身共同決定的悉尾。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末突那,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子构眯,更是在濱河造成了極大的恐慌愕难,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惫霸,死亡現(xiàn)場離奇詭異猫缭,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)壹店,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門猜丹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人硅卢,你說我怎么就攤上這事射窒。” “怎么了将塑?”我有些...
    開封第一講書人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵脉顿,是天一觀的道長。 經(jīng)常有香客問我点寥,道長艾疟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任敢辩,我火速辦了婚禮蔽莱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘戚长。我一直安慰自己盗冷,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開白布历葛。 她就那樣靜靜地躺著正塌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪恤溶。 梳的紋絲不亂的頭發(fā)上乓诽,一...
    開封第一講書人閱讀 51,258評(píng)論 1 300
  • 那天,我揣著相機(jī)與錄音咒程,去河邊找鬼鸠天。 笑死,一個(gè)胖子當(dāng)著我的面吹牛帐姻,可吹牛的內(nèi)容都是我干的稠集。 我是一名探鬼主播,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼饥瓷,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼剥纷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起呢铆,我...
    開封第一講書人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤晦鞋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后棺克,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悠垛,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年娜谊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了确买。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡纱皆,死狀恐怖湾趾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情抹剩,我是刑警寧澤撑帖,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站澳眷,受9級(jí)特大地震影響胡嘿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜钳踊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一衷敌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧拓瞪,春花似錦缴罗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽兵钮。三九已至,卻和暖如春舌界,著一層夾襖步出監(jiān)牢的瞬間掘譬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來泰國打工呻拌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留葱轩,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓藐握,卻偏偏與公主長得像靴拱,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子猾普,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容