ViewGroup

深入到ViewGroup內(nèi)部术辐,了解ViewGroup的工作,同時(shí)會(huì)闡述更多有關(guān)于View的相關(guān)知識(shí)。以便為以后能靈活的使用自定義空間打更近一步的基礎(chǔ)署隘。

ViewGroup定義

一個(gè)ViewGroup是一個(gè)可以包含子View的容器蘑秽,是布局文件和View容器的基類饺著。在這個(gè)類里定義了ViewGroup.LayoutParams類,這個(gè)類是布局參數(shù)的子類肠牲。
  其實(shí)ViewGroup也就是View的容器幼衰。通過(guò)ViewGroup.LayoutParams來(lái)指定子View的參數(shù)。

ViewGroup作為一個(gè)容器缀雳,為了制定這個(gè)容器應(yīng)有的標(biāo)準(zhǔn)所以為其指定了接口

public abstract class ViewGroup extends View implements ViewParent, ViewManager  

這兩個(gè)接口這里不研究渡嚣,如果涉及到的話會(huì)帶一下。ViewGroup有小4000行代碼肥印,下面我們一個(gè)模塊一個(gè)模塊分析识椰。

ViewGroup容器

ViewGroup是一個(gè)容器,其采用一個(gè)數(shù)組來(lái)存儲(chǔ)這些子View:

// Child views of this ViewGroup    
private View[] mChildren;  

由于是通過(guò)一個(gè)數(shù)組來(lái)存儲(chǔ)View數(shù)據(jù)的深碱,所以對(duì)于ViewGroup來(lái)說(shuō)其必須實(shí)現(xiàn)增腹鹉、刪、查的算法敷硅。下面我們就來(lái)看看其內(nèi)部實(shí)現(xiàn)功咒。

1. 添加View的算法

protected boolean addViewInLayout(View child, int index, LayoutParams params) {   
    return addViewInLayout(child, index, params, false);   
}   
protected boolean addViewInLayout(View child, int index, LayoutParams params,   
        boolean preventRequestLayout) {   
    child.mParent = null;   
    addViewInner(child, index, params, preventRequestLayout);   
    child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK) | DRAWN;   
    return true;   
}   
private void addViewInner(View child, int index, LayoutParams params,   
        boolean preventRequestLayout) {   
    ...   
    addInArray(child, index);   
    ...   
}   
private void addInArray(View child, int index) {   
...   
}  

上面四個(gè)方法就是添加View的核心算法的封裝,它們是層層調(diào)用的關(guān)系竞膳。而我們通常調(diào)用的addView就是最終通過(guò)上面那個(gè)來(lái)最終達(dá)到添加到ViewGroup中的航瞭。

1.1 addViewInner方法:

1. 首先是對(duì)子View是否已經(jīng)包含到一個(gè)父容器中,主要的防止添加一個(gè)已經(jīng)有父容器的View坦辟,因?yàn)樘砑右粋€(gè)擁有父容器的View時(shí)會(huì)碰到各種問(wèn)題刊侯。比如記錄本身父容器算法的問(wèn)題、本身被多個(gè)父容器包含時(shí)更新的處理等等一系列的問(wèn)題都會(huì)出現(xiàn)锉走。

if (child.getParent() != null) {   
        throw new IllegalStateException("The specified child already has a parent. " +   
                "You must call removeView() on the child's parent first.");   
    }  

2. 然后就是對(duì)子View布局參數(shù)的處理
  3. 調(diào)用addInArray來(lái)添加View
  4. 父View為當(dāng)前的ViewGroup
  5. 焦點(diǎn)的處理
  6. 當(dāng)前View的AttachInfo信息滨彻,這個(gè)信息是用來(lái)在窗口處理中用的。Android的窗口系統(tǒng)就是用過(guò)AttachInfo來(lái)判斷View的所屬窗口的挪蹭,這個(gè)了解下就行亭饵。詳細(xì)信息設(shè)計(jì)到Android框架層的一些東西。

AttachInfo ai = mAttachInfo;   
    if (ai != null) {   
        boolean lastKeepOn = ai.mKeepScreenOn;   
        ai.mKeepScreenOn = false;   
        child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));   
        if (ai.mKeepScreenOn) {   
            needGlobalAttributesUpdate(true);   
        }   
        ai.mKeepScreenOn = lastKeepOn;   
    }  

7. View樹(shù)改變的監(jiān)聽(tīng)

if (mOnHierarchyChangeListener != null) { 
        mOnHierarchyChangeListener.onChildViewAdded(this, child); 
}

8. 子View中的mViewFlags的設(shè)置

if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) { 
       mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE; 
   }

1.2 addInArray方法:

這個(gè)里面的實(shí)現(xiàn)主要是有個(gè)知識(shí)點(diǎn)梁厉,以前也沒(méi)用過(guò)arraycopy辜羊,這里具體實(shí)現(xiàn)就不多加描述了踏兜。

System.arraycopy(children, 0, mChildren, 0, index); 
System.arraycopy(children, index, mChildren, index + 1, count - index);

2. 移除View

移除View的幾種方式:

  • 移除指定的View

  • 移除從指定位置的View

  • 移除從指定位置開(kāi)始的多個(gè)View

  • 移除所有的View

其中具體涉及到的方法就有好多了,不過(guò)最終對(duì)要?jiǎng)h除的子View中所做的無(wú)非就是下列的事情:

  • 如果擁有焦點(diǎn)則清除焦點(diǎn)

  • 將要?jiǎng)h除的View從當(dāng)前的window中解除關(guān)系

  • 設(shè)置View樹(shù)改變的事件監(jiān)聽(tīng)八秃,我們可以通過(guò)監(jiān)聽(tīng)OnHierarchyChangeListener事件來(lái)進(jìn)行一些相應(yīng)的處理

  • 從父容器的子容器數(shù)組中刪除

具體的內(nèi)容這里就不一一貼出來(lái)了碱妆,大家回頭看看源碼就可以了。

3. 查詢

這個(gè)就簡(jiǎn)單了昔驱,就是直接從數(shù)組中取出就可以了:

public View getChildAt(int index) { 
   try { 
        return mChildren[index]; 
    } catch (IndexOutOfBoundsException ex) { 
        return null; 
    } 
}

分析到這兒疹尾,其實(shí)我們已經(jīng)相當(dāng)于分析了ViewGroup四分之一的代碼了,@_@骤肛。

onFinishInflate

我們一般使用View的流程是在onCreate中使用setContentView來(lái)設(shè)置要顯示Layout文件或直接創(chuàng)建一個(gè)View纳本,在當(dāng)設(shè)置了ContentView之后系統(tǒng)會(huì)對(duì)這個(gè)View進(jìn)行解析,然后回調(diào)當(dāng)前視圖View中的onFinishInflate方法腋颠。只有解析了這個(gè)View我們才能在這個(gè)View容器中獲取到擁有Id的組件繁成,同樣因?yàn)橄到y(tǒng)解析完View之后才會(huì)調(diào)用onFinishInflate方法,所以我們自定義組件時(shí)可以onFinishInflate方法中獲取指定子View的引用秕豫。

測(cè)量組件

在ViewGroup中提供了測(cè)量子組件的三個(gè)方法朴艰。
  __1观蓄、measureChild(View, int, int)混移,為子組件添加Padding __

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);   
}  

__2、measureChildren(int, int)根據(jù)指定的高和寬來(lái)測(cè)量所有子View中顯示參數(shù)非GONE的組件侮穿。 __

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);   
        }   
    }   
}  

3歌径、measureChildWithMargins(View, int, int, int, int)測(cè)量指定的子組件,為子組件添加Padding和Margin亲茅。

 protected void measureChildWithMargins(View child,   
        int parentWidthMeasureSpec, int widthUsed,   
        int parentHeightMeasureSpec, int heightUsed) {   
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();   
  
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,   
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin   
                    + widthUsed, lp.width);   
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,   
            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin   
                    + heightUsed, lp.height);   
  
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);   
}  

上面三個(gè)方法都是為子組件設(shè)置了布局參數(shù)回铛。最終調(diào)用的方法是子組件的measure方法。在View中我們知道這個(gè)調(diào)用實(shí)際上就是設(shè)置了子組件的布局參數(shù)并且調(diào)用onMeasure方法克锣,最終設(shè)置了View測(cè)量后的高度和寬度茵肃。

onLayout

這個(gè)函數(shù)是一個(gè)抽象函數(shù),要求實(shí)現(xiàn)ViewGroup的函數(shù)必須實(shí)現(xiàn)這個(gè)函數(shù)袭祟,這也就是ViewGroup是一個(gè)抽象函數(shù)的原因验残。因?yàn)楦鞣N組件實(shí)現(xiàn)的布局方式不一樣,而onLayout是必須被重載的函數(shù)碳蛋。

@Override
protected abstract void onLayout(boolean changed, 
        int l, int t, int r, int b); 

我們回頭來(lái)看View中l(wèi)ayout方法:

public final void layout(int l, int t, int r, int b) { 
    boolean changed = setFrame(l, t, r, b); 
    if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { 
        if (ViewDebug.TRACE_HIERARCHY) { 
            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT); 
        } 

        onLayout(changed, l, t, r, b); 
        mPrivateFlags &= ~LAYOUT_REQUIRED; 
    } 
    mPrivateFlags &= ~FORCE_LAYOUT; 
}

在這個(gè)方法中調(diào)用了setFrame方法士袄,這個(gè)方法是用來(lái)設(shè)置View中的上下左右邊距用的

protected boolean setFrame(int left, int top, int right, int bottom) {  
    boolean changed = false;  
    //.......  
    if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {  
        changed = true;  
  
        // Remember our drawn bit  
        int drawn = mPrivateFlags & DRAWN;  
  
        // Invalidate our old position  
        invalidate();  
  
  
        int oldWidth = mRight - mLeft;  
        int oldHeight = mBottom - mTop;  
  
        mLeft = left;  
        mTop = top;  
        mRight = right;  
        mBottom = bottom;  
  
        mPrivateFlags |= HAS_BOUNDS;  
  
        int newWidth = right - left;  
        int newHeight = bottom - top;  
  
        if (newWidth != oldWidth || newHeight != oldHeight) {  
            onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);  
        }  
  
        if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {  
            // If we are visible, force the DRAWN bit to on so that  
            // this invalidate will go through (at least to our parent).  
            // This is because someone may have invalidated this view  
            // before this call to setFrame came in, therby clearing  
            // the DRAWN bit.  
            mPrivateFlags |= DRAWN;  
            invalidate();  
        }  
  
        // Reset drawn bit to original value (invalidate turns it off)  
        mPrivateFlags |= drawn;  
  
        mBackgroundSizeChanged = true;  
    }  
    return changed;  
}  
//我們可以看到如果新的高度和寬度改變之后會(huì)調(diào)用重新設(shè)置View的四個(gè)參數(shù):  
//protected int mLeft;  
//protected int mRight;  
//protected int mTop;  
//protected int mBottom;  
//這四個(gè)參數(shù)指定了View將要布局的位置她肯。而繪制的時(shí)候是通過(guò)這四個(gè)參數(shù)來(lái)繪制,所以我們?cè)赩iew中調(diào)用layout方法可以實(shí)現(xiàn)指定子View中布局氨鹏。

ViewGroup的繪制

ViewGroup的繪制實(shí)際上是調(diào)用的dispatchDraw,繪制時(shí)需要考慮動(dòng)畫問(wèn)題压状,而動(dòng)畫的實(shí)現(xiàn)實(shí)際上就通過(guò)dispatchDraw來(lái)實(shí)現(xiàn)的仆抵。
   我們不用理會(huì)太多的細(xì)節(jié),直接看其繪制子組件調(diào)用的是drawChild方法,這個(gè)里面具體的東西就多了镣丑,涉及到動(dòng)畫效果的處理还栓,如果有機(jī)會(huì)的話再寫,我們只要知道這個(gè)方法的功能就行传轰。
  這里有個(gè)demo貼出其中的代碼大家可以測(cè)試下剩盒。

public ViewGroup01(Context context)    
{    
super(context);    
Button mButton = new Button(context);    
mButton.setText("測(cè)試");    
addView(mButton);    
}    

@Override  
protected void onLayout(boolean changed, int l, int t, int r, int b)    
{    
View v = getChildAt(0);    
if(v != null)    
    {    
    v.layout(120, 120, 250, 250);    
    }    
}  

@Override  
protected void dispatchDraw(Canvas canvas)    
{    
super.dispatchDraw(canvas);    
View v = getChildAt(0);    
if(v != null)    
    {    
    drawChild(canvas, v, getDrawingTime());    
    }    
}    

效果圖片

9201345467167.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市慨蛙,隨后出現(xiàn)的幾起案子辽聊,更是在濱河造成了極大的恐慌,老刑警劉巖期贫,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件跟匆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡通砍,警方通過(guò)查閱死者的電腦和手機(jī)玛臂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)封孙,“玉大人迹冤,你說(shuō)我怎么就攤上這事』⒓桑” “怎么了泡徙?”我有些...
    開(kāi)封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)膜蠢。 經(jīng)常有香客問(wèn)我堪藐,道長(zhǎng),這世上最難降的妖魔是什么挑围? 我笑而不...
    開(kāi)封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任礁竞,我火速辦了婚禮,結(jié)果婚禮上杉辙,老公的妹妹穿的比我還像新娘模捂。我一直安慰自己,他們只是感情好奏瞬,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布枫绅。 她就那樣靜靜地躺著,像睡著了一般硼端。 火紅的嫁衣襯著肌膚如雪并淋。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天珍昨,我揣著相機(jī)與錄音县耽,去河邊找鬼句喷。 笑死,一個(gè)胖子當(dāng)著我的面吹牛兔毙,可吹牛的內(nèi)容都是我干的唾琼。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼澎剥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼锡溯!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起哑姚,我...
    開(kāi)封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤祭饭,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后叙量,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體倡蝙,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年绞佩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了寺鸥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡品山,死狀恐怖胆建,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情谆奥,我是刑警寧澤眼坏,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布拂玻,位于F島的核電站酸些,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏檐蚜。R本人自食惡果不足惜魄懂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望闯第。 院中可真熱鬧市栗,春花似錦、人聲如沸咳短。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)咙好。三九已至篡腌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間勾效,已是汗流浹背嘹悼。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工叛甫, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人杨伙。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓其监,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親限匣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子抖苦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

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