RelativeLayout和ImageView一起使用時遇到的問題

當xml是這樣時:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="300dp"
        android:layout_marginTop="0dp"
        android:adjustViewBounds="true"
        android:background="#ff0000"
        android:src="@drawable/ic_720_1280"/>

</RelativeLayout>

效果是:


Screenshot_1525569407.png

將ImageView的marginTop改成150dp后,效果是:


Screenshot_1525569332.png

圖片的寬明顯發(fā)生了改變。不知道這是不是bug株扛。
通過看源碼摇展,我覺得是RelativeLayout測量方法的問題硫惕。RelativeLayout測量child的寬高時叁巨,先用去掉margins的寬高測量出child的寬财岔,然后用測出的child的寬再去測量child的高避诽。在測量寬時龟虎,用的height是RelativeLayout的高減去margin,ImageView的adjustViewBounds會根據(jù)這個height去計算出width沙庐,所以會導(dǎo)致marginTop越大鲤妥,height越小,width越小拱雏。

下面是自己看源碼的分析:
首先是RelativeLayout的onMeasure方法:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        
        棉安。。铸抑。
        
        View[] views = mSortedHorizontalChildren;
        int count = views.length;

        for (int i = 0; i < count; i++) {
            View child = views[i];
            if (child.getVisibility() != GONE) {
                LayoutParams params = (LayoutParams) child.getLayoutParams();
                int[] rules = params.getRules(layoutDirection);

                applyHorizontalSizeRules(params, myWidth, rules);
                measureChildHorizontal(child, params, myWidth, myHeight);

                if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
                    offsetHorizontalAxis = true;
                }
            }
        }

        贡耽。。鹊汛。

    }

在onMeasure中通過measureChildHorizontal計算出child的寬蒲赂,順便說下myWidth和myHeight是屏幕寬高。measureChildHorizontal的代碼如下:

    private void measureChildHorizontal(
            View child, LayoutParams params, int myWidth, int myHeight) {
        final int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft, params.mRight,
                params.width, params.leftMargin, params.rightMargin, mPaddingLeft, mPaddingRight,
                myWidth);

        final int childHeightMeasureSpec;
        if (myHeight < 0 && !mAllowBrokenMeasureSpecs) {
            if (params.height >= 0) {
                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                        params.height, MeasureSpec.EXACTLY);
            } else {
                // Negative values in a mySize/myWidth/myWidth value in
                // RelativeLayout measurement is code for, "we got an
                // unspecified mode in the RelativeLayout's measure spec."
                // Carry it forward.
                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
            }
        } else {
            final int maxHeight;
            if (mMeasureVerticalWithPaddingMargin) {
                maxHeight = Math.max(0, myHeight - mPaddingTop - mPaddingBottom
                        - params.topMargin - params.bottomMargin);
            } else {
                maxHeight = Math.max(0, myHeight);
            }

            final int heightMode;
            if (params.height == LayoutParams.MATCH_PARENT) {
                heightMode = MeasureSpec.EXACTLY;
            } else {
                heightMode = MeasureSpec.AT_MOST;
            }
            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, heightMode);
        }

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

measureChildHorizontal中最后通過child.measure()測量了child的寬和高刁憋。測量高用的MeasureSpec的size是RelativeLayout去除margin后的高度滥嘴。所以這里使用不同的marginTop會得到不同的高。測量寬用的MeasureSpec通過getChildMeasureSpec獲得至耻。getChildMeasureSpec的代碼如下:

    private int getChildMeasureSpec(int childStart, int childEnd,
            int childSize, int startMargin, int endMargin, int startPadding,
            int endPadding, int mySize) {
        int childSpecMode = 0;
        int childSpecSize = 0;

        // Negative values in a mySize value in RelativeLayout
        // measurement is code for, "we got an unspecified mode in the
        // RelativeLayout's measure spec."
        final boolean isUnspecified = mySize < 0;
        if (isUnspecified && !mAllowBrokenMeasureSpecs) {
            if (childStart != VALUE_NOT_SET && childEnd != VALUE_NOT_SET) {
                // Constraints fixed both edges, so child has an exact size.
                childSpecSize = Math.max(0, childEnd - childStart);
                childSpecMode = MeasureSpec.EXACTLY;
            } else if (childSize >= 0) {
                // The child specified an exact size.
                childSpecSize = childSize;
                childSpecMode = MeasureSpec.EXACTLY;
            } else {
                // Allow the child to be whatever size it wants.
                childSpecSize = 0;
                childSpecMode = MeasureSpec.UNSPECIFIED;
            }

            return MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode);
        }

        // Figure out start and end bounds.
        int tempStart = childStart;
        int tempEnd = childEnd;

        // If the view did not express a layout constraint for an edge, use
        // view's margins and our padding
        if (tempStart == VALUE_NOT_SET) {
            tempStart = startPadding + startMargin;
        }
        if (tempEnd == VALUE_NOT_SET) {
            tempEnd = mySize - endPadding - endMargin;
        }

        // Figure out maximum size available to this view
        final int maxAvailable = tempEnd - tempStart;

        if (childStart != VALUE_NOT_SET && childEnd != VALUE_NOT_SET) {
            // Constraints fixed both edges, so child must be an exact size.
            childSpecMode = isUnspecified ? MeasureSpec.UNSPECIFIED : MeasureSpec.EXACTLY;
            childSpecSize = Math.max(0, maxAvailable);
        } else {
            if (childSize >= 0) {
                // Child wanted an exact size. Give as much as possible.
                childSpecMode = MeasureSpec.EXACTLY;

                if (maxAvailable >= 0) {
                    // We have a maximum size in this dimension.
                    childSpecSize = Math.min(maxAvailable, childSize);
                } else {
                    // We can grow in this dimension.
                    childSpecSize = childSize;
                }
            } else if (childSize == LayoutParams.MATCH_PARENT) {
                // Child wanted to be as big as possible. Give all available
                // space.
                childSpecMode = isUnspecified ? MeasureSpec.UNSPECIFIED : MeasureSpec.EXACTLY;
                childSpecSize = Math.max(0, maxAvailable);
            } else if (childSize == LayoutParams.WRAP_CONTENT) {
                // Child wants to wrap content. Use AT_MOST to communicate
                // available space if we know our max size.
                if (maxAvailable >= 0) {
                    // We have a maximum size in this dimension.
                    childSpecMode = MeasureSpec.AT_MOST;
                    childSpecSize = maxAvailable;
                } else {
                    // We can grow in this dimension. Child can be as big as it
                    // wants.
                    childSpecMode = MeasureSpec.UNSPECIFIED;
                    childSpecSize = 0;
                }
            }
        }

        return MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode);
    }

由于childStart == VALUE_NOT_SET若皱、childEnd == VALUE_NOT_SET成立镊叁,這段代碼最后生成的MeasureSpce的size是去除margin的mySize,而mySize是傳入的屏幕寬走触。
這里的childStart == VALUE_NOT_SET是因為measureChildHorizontal之前執(zhí)行了applyHorizontalSizeRules晦譬,applyHorizontalSizeRules將layoutParams的mLeft和mRight都改成了VALUE_NOT_SET,applyHorizontalSizeRules代碼如下:

    private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth, int[] rules) {
        ...

        childParams.mLeft = VALUE_NOT_SET;
        childParams.mRight = VALUE_NOT_SET;

        ...
    }

measureChildHorizontal的分析就結(jié)束了饺汹』滋恚總結(jié)下,measureChildHorizontal最終會調(diào)用child.measure方法兜辞,傳遞的寬MeasureSpce是size=RelativeLayout寬減去margins迎瞧、mode=AT_MOST,高MeasureSpce是size=RelativeLayout高減去margins逸吵、mode=AT_MOST凶硅。
接下來就是ImageView的onMeasure方法了。代碼如下:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        resolveUri();
        int w;
        int h;

        // Desired aspect ratio of the view's contents (not including padding)
        float desiredAspect = 0.0f;

        // We are allowed to change the view's width
        boolean resizeWidth = false;

        // We are allowed to change the view's height
        boolean resizeHeight = false;

        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);

        if (mDrawable == null) {
            // If no drawable, its intrinsic size is 0.
            mDrawableWidth = -1;
            mDrawableHeight = -1;
            w = h = 0;
        } else {
            w = mDrawableWidth;
            h = mDrawableHeight;
            if (w <= 0) w = 1;
            if (h <= 0) h = 1;

            // We are supposed to adjust view bounds to match the aspect
            // ratio of our drawable. See if that is possible.
            if (mAdjustViewBounds) {
                resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
                resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;

                desiredAspect = (float) w / (float) h;
            }
        }

        final int pleft = mPaddingLeft;
        final int pright = mPaddingRight;
        final int ptop = mPaddingTop;
        final int pbottom = mPaddingBottom;

        int widthSize;
        int heightSize;

        if (resizeWidth || resizeHeight) {
            /* If we get here, it means we want to resize to match the
                drawables aspect ratio, and we have the freedom to change at
                least one dimension.
            */

            // Get the max possible width given our constraints
            widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec);

            // Get the max possible height given our constraints
            heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec);

            if (desiredAspect != 0.0f) {
                // See what our actual aspect ratio is
                final float actualAspect = (float)(widthSize - pleft - pright) /
                                        (heightSize - ptop - pbottom);

                if (Math.abs(actualAspect - desiredAspect) > 0.0000001) {

                    boolean done = false;

                    // Try adjusting width to be proportional to height
                    if (resizeWidth) {
                        int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) +
                                pleft + pright;

                        // Allow the width to outgrow its original estimate if height is fixed.
                        if (!resizeHeight && !sCompatAdjustViewBounds) {
                            widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec);
                        }

                        if (newWidth <= widthSize) {
                            widthSize = newWidth;
                            done = true;
                        }
                    }

                    // Try adjusting height to be proportional to width
                    if (!done && resizeHeight) {
                        int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) +
                                ptop + pbottom;

                        // Allow the height to outgrow its original estimate if width is fixed.
                        if (!resizeWidth && !sCompatAdjustViewBounds) {
                            heightSize = resolveAdjustedSize(newHeight, mMaxHeight,
                                    heightMeasureSpec);
                        }

                        if (newHeight <= heightSize) {
                            heightSize = newHeight;
                        }
                    }
                }
            }
        } else {
            /* We are either don't want to preserve the drawables aspect ratio,
               or we are not allowed to change view dimensions. Just measure in
               the normal way.
            */
            w += pleft + pright;
            h += ptop + pbottom;

            w = Math.max(w, getSuggestedMinimumWidth());
            h = Math.max(h, getSuggestedMinimumHeight());

            widthSize = resolveSizeAndState(w, widthMeasureSpec, 0);
            heightSize = resolveSizeAndState(h, heightMeasureSpec, 0);
        }

        setMeasuredDimension(widthSize, heightSize);
    }

首先mDrawable == null不成立扫皱。
然后mAdjustViewBounds==true成立足绅,mAdjustViewBounds==true是因為我們在xml中設(shè)置了android:adjustViewBounds="true",這個會調(diào)用setAdjustViewBounds方法韩脑,該方法將mAdjustViewBounds標記為true氢妈。
然后我們傳入的MeasureSpce的mode都不是Exactly,得到resizeWidth和resizeHeight都是true段多。
在resizeWidth或resizeHeight為true的情況下首量,會根據(jù)View的比例和圖片比例重新計算width或高。這里我們720*1280比例是會導(dǎo)致width改變进苍。當傳入的heightMeasureSpce的size變小時width也就跟著變小了加缘。根據(jù)前面的分析,當marginTop變大時觉啊,heightMeasureSpce的size會變小拣宏,width也變小。

代碼分析到這里就結(jié)束了杠人。如果覺得有寫的不對的地方勋乾,希望能指出。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末搜吧,一起剝皮案震驚了整個濱河市市俊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌滤奈,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撩满,死亡現(xiàn)場離奇詭異蜒程,居然都是意外死亡绅你,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門昭躺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來忌锯,“玉大人,你說我怎么就攤上這事领炫∨伎澹” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵帝洪,是天一觀的道長似舵。 經(jīng)常有香客問我,道長葱峡,這世上最難降的妖魔是什么砚哗? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮砰奕,結(jié)果婚禮上蛛芥,老公的妹妹穿的比我還像新娘。我一直安慰自己军援,他們只是感情好仅淑,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著胸哥,像睡著了一般涯竟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上烘嘱,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天昆禽,我揣著相機與錄音,去河邊找鬼蝇庭。 笑死醉鳖,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的哮内。 我是一名探鬼主播盗棵,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼北发!你這毒婦竟也來了纹因?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤琳拨,失蹤者是張志新(化名)和其女友劉穎瞭恰,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狱庇,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡惊畏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年恶耽,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片颜启。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡偷俭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出缰盏,到底是詐尸還是另有隱情涌萤,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布口猜,位于F島的核電站负溪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏暮的。R本人自食惡果不足惜笙以,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望冻辩。 院中可真熱鬧猖腕,春花似錦、人聲如沸恨闪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咙咽。三九已至老玛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間钧敞,已是汗流浹背蜡豹。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留溉苛,地道東北人镜廉。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像愚战,于是被迫代替她去往敵國和親娇唯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355

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