Android 擴(kuò)展-ViewGroup的子View真正實(shí)現(xiàn)Margin屬性

??樓主最近在復(fù)習(xí)自定義View戈二,在復(fù)習(xí)到自定義ViewGroup這個(gè)知識(shí)點(diǎn)時(shí),發(fā)現(xiàn)了一個(gè)問(wèn)題--就是我們之前的定義ViewGroup在考慮Margin屬性可能有問(wèn)題喳资。本文在解決該問(wèn)題給出建議性的意見(jiàn)觉吭,但是不一定是正確的,如果有錯(cuò)誤或者不當(dāng)?shù)牡胤狡偷耍M刚?br> ??本文參考文章:
??1.Android 手把手教您自定義ViewGroup(一)
??2.你的自定義View是否真的支持Margin

1.提出問(wèn)題

??這里我舉一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)鲜滩,假設(shè)我們需要定義一個(gè)ViewGroup放置一個(gè)子View,同時(shí)這個(gè)子View支持Padding和Margin屬性节值。
??這里我先貼出一個(gè)常規(guī)的寫法:

public class CustomViewGroup02 extends ViewGroup {

    public CustomViewGroup02(Context context) {
        super(context);
    }

    public CustomViewGroup02(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomViewGroup02(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        //這里假設(shè)只有一個(gè)子View
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        View view = getChildAt(0);
        MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
        int width = view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + getPaddingLeft() + getPaddingRight();
        int height = view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin + getPaddingTop() + getPaddingBottom();

        setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? widthSize:width, (heightMode == MeasureSpec.EXACTLY) ? heightSize:height);

    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        View view = getChildAt(0);

        MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
        int left = getPaddingLeft() + lp.leftMargin;
        int top = getPaddingTop() + lp.topMargin;
        view.layout(left, top, left + view.getMeasuredWidth(), top + view.getMeasuredHeight());

    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

}

??在代碼中徙硅,我們考慮到了padding屬性和Margin屬性,同時(shí)我們可以在xml代碼測(cè)試一下效果
??xml中這樣寫:

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.apple.android_demo08.CustomViewGroup02
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_marginLeft="20dp"
            android:background="#FFDAB9" />
    </com.example.apple.android_demo08.CustomViewGroup02>
</android.support.constraint.ConstraintLayout>

??模擬器上展示的效果圖:



??看上去似乎是沒(méi)有問(wèn)題的搞疗,我們給TextView設(shè)置了marginLeft為20dp嗓蘑,在手機(jī)上也能正常顯示出來(lái)margin屬性。但是匿乃,如果TextView的layout_width設(shè)置為match_parent會(huì)怎么樣呢桩皿?
??xml代碼:

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.apple.android_demo08.CustomViewGroup02
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_marginLeft="20dp"
            android:background="#FFDAB9" />
    </com.example.apple.android_demo08.CustomViewGroup02>
</android.support.constraint.ConstraintLayout>

??此時(shí)我們?cè)贏ndroid studio右側(cè)的預(yù)覽界面來(lái)看看此時(shí)效果:



??我們發(fā)現(xiàn)雖然TextVeiw向左移動(dòng)了20dp,但是我們發(fā)現(xiàn)了一個(gè)問(wèn)題幢炸,就是TextView右側(cè)超出了屏幕泄隔,也就是說(shuō),TextView的layout_marginLeft 屬性根本沒(méi)有影響到它的width宛徊,只是單純將TextView向右移動(dòng)了20dp梅尤。這個(gè)是有問(wèn)題的柜思,我們?nèi)タ纯聪到y(tǒng)的LinearLayout布局,margin屬性會(huì)影響View的寬和高的巷燥。從而得知赡盘,我們這里支持的Margin屬性是假的!那怎么才能真正的支持Margin屬性呢缰揪?

2.解決問(wèn)題

??要想解決問(wèn)題陨享,必須先知道問(wèn)題出現(xiàn)在哪里。這個(gè)問(wèn)題就出現(xiàn)在onMeasure方法中measureChildren方法钝腺。
??我們先來(lái)看看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);
            }
        }
    }

??這個(gè)方法表達(dá)的意思非常簡(jiǎn)單抛姑,就是循環(huán)測(cè)量每個(gè)子View。然后我們?cè)賮?lái)看看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方法里面艳狐,先利用父布局的XXXXMeasureSpec定硝、padding值和子View向父布局申請(qǐng)的大小來(lái)生成子View的寬和高。這里我們就看出問(wèn)題了毫目,我們發(fā)現(xiàn)系統(tǒng)在測(cè)量子View的width和height時(shí)蔬啡,只是考慮了padding的影響,沒(méi)有考慮Margin對(duì)View的width和height的影響镀虐。
??看到這里箱蟆,我們明白了,為什么之前我們給TextView設(shè)置了marginLeft刮便,同時(shí)設(shè)置TextView的layout_width為match_parent時(shí)空猜,TextView只是單純的向右移動(dòng)了,而沒(méi)有調(diào)整TextView的大小恨旱。因?yàn)槲覀兺ㄟ^(guò)measureChild方法來(lái)測(cè)量每個(gè)子View是不會(huì)考慮Margin屬性對(duì)View的大小的影響辈毯。
??知道的問(wèn)題所在,解決問(wèn)題就非常的容易搜贤。解決的問(wèn)題的辦法就是重寫measureChildren方法谆沃,在測(cè)量每個(gè)View時(shí),考慮到margin的影響入客。其實(shí)在ViewGroup還有一個(gè)方法那就是measureChildWidthMargins方法管毙,這個(gè)方法測(cè)量每個(gè)View時(shí),考慮到了每個(gè)View的margin屬性的影響桌硫。我們來(lái)看看measureChildWidthMargins方法的源代碼:

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

??我們發(fā)現(xiàn)在這個(gè)方法里面夭咬,將Margin屬性的影響也考慮到的。那么我們就來(lái)重寫measureChildren方法:

    @Override
    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View view = getChildAt(i);
            if (view != null && view.getVisibility() != GONE){
                measureChildWithMargins(view, widthMeasureSpec, 0, heightMeasureSpec, 0);
            }
        }
    }

??在這個(gè)重寫的代碼中铆隘,我們需要主要兩點(diǎn):

??1.在原來(lái)的measureChildren方法的if判斷條件是:(child.mViewFlags & VISIBILITY_MASK) != GONE卓舵,而我們這里是:view != null && view.getVisibility() != GONE。我們這里的依據(jù)是LinearLayout膀钠,系統(tǒng)的LinearLayout也重寫了measureChildren方法的掏湾,它的判斷條件就是:view != null && view.getVisibility() != GONE裹虫。
??2.measureChildrenWithMargins方法多出兩個(gè)參數(shù),分別是:widthUsed融击,heightUsed筑公,這里傳入的是兩個(gè)0,這里的依據(jù)還是LinearLayout尊浪,LinearLayout調(diào)用measureChildrenWithMargins傳入就是兩個(gè)0匣屡。

??重寫之后,我們來(lái)看看之前的match_parent的情況(記得Rebuild一下工程):



??這下就變得正常得多了拇涤!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末捣作,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子鹅士,更是在濱河造成了極大的恐慌券躁,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掉盅,死亡現(xiàn)場(chǎng)離奇詭異也拜,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)怔接,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門搪泳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)稀轨,“玉大人扼脐,你說(shuō)我怎么就攤上這事》芄簦” “怎么了瓦侮?”我有些...
    開(kāi)封第一講書人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)佣谐。 經(jīng)常有香客問(wèn)我肚吏,道長(zhǎng),這世上最難降的妖魔是什么狭魂? 我笑而不...
    開(kāi)封第一講書人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任罚攀,我火速辦了婚禮,結(jié)果婚禮上雌澄,老公的妹妹穿的比我還像新娘斋泄。我一直安慰自己,他們只是感情好镐牺,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布炫掐。 她就那樣靜靜地躺著,像睡著了一般睬涧。 火紅的嫁衣襯著肌膚如雪募胃。 梳的紋絲不亂的頭發(fā)上旗唁,一...
    開(kāi)封第一講書人閱讀 52,682評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音痹束,去河邊找鬼检疫。 笑死,一個(gè)胖子當(dāng)著我的面吹牛祷嘶,可吹牛的內(nèi)容都是我干的电谣。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼抹蚀,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼剿牺!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起环壤,我...
    開(kāi)封第一講書人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤晒来,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后郑现,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體湃崩,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年接箫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了攒读。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡辛友,死狀恐怖薄扁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情废累,我是刑警寧澤邓梅,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站邑滨,受9級(jí)特大地震影響日缨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜掖看,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一匣距、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧哎壳,春花似錦毅待、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春驶乾,著一層夾襖步出監(jiān)牢的瞬間邑飒,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工级乐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留疙咸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓风科,卻偏偏與公主長(zhǎng)得像撒轮,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子贼穆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

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