當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>
效果是:
將ImageView的marginTop改成150dp后,效果是:
圖片的寬明顯發(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é)束了杠人。如果覺得有寫的不對的地方勋乾,希望能指出。