@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { resolveUri(); int w; int h; // 堆代码 duidaima.com // 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) { // 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 { ... widthSize = resolveSizeAndState(w, widthMeasureSpec, 0); heightSize = resolveSizeAndState(h, heightMeasureSpec, 0); } setMeasuredDimension(widthSize, heightSize); }我们一步步来看,首先看这个函数一开始调用了resolveUri这个函数,这个函数代码如下:
private void resolveUri() { ... if (mResource != 0) { try { d = mContext.getDrawable(mResource); } catch (Exception e) { ... } } else if (mUri != null) { d = getDrawableFromUri(mUri); ... } else { return; } updateDrawable(d); } private void updateDrawable(Drawable d) { ... if (d != null) { ... mDrawableWidth = d.getIntrinsicWidth(); mDrawableHeight = d.getIntrinsicHeight(); ... } else { mDrawableWidth = mDrawableHeight = -1; } }通过上面代码可以看到resolveUri函数会先得到drawable对象(从resource或uri中),然后通过updateDrawable将mDrawableWidth和mDrawableHeight这两个变量设置为drawable的宽高。我们回到onMeasure函数继续往下看,第一个if-else,因为我们讨论的是有图片的情况,所以mDrawable一定不为null,那么走进了else语句,将刚才的mDrawableWidth和mDrawableHeight两个变量的值赋给了w和h这两个局部变量。
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { final int specMode = MeasureSpec.getMode(measureSpec); final int specSize = MeasureSpec.getSize(measureSpec); final int result; switch (specMode) { case MeasureSpec.AT_MOST: if (specSize < size) { result = specSize | MEASURED_STATE_TOO_SMALL; } else { result = size; } break; case MeasureSpec.EXACTLY: result = specSize; break; case MeasureSpec.UNSPECIFIED: default: result = size; } return result | (childMeasuredState & MEASURED_STATE_MASK); }首先从measureSpec中获取mode和size。 这里简单解释一下measureSpec,它是一个int值,前两位(32和31位)存储标志位,即specMode;后面则存储着size即限制大小。而measureSpec是父View传给子View的,也就是说父View会根据自身的情况限制子View的大小。
android:adjustViewBounds="true"ImageView就可以按照图片的比例来显示了。这是怎么实现的?
resizeWidth = widthSpecMode != MeasureSpec.EXACTLY; resizeHeight = heightSpecMode != MeasureSpec.EXACTLY; desiredAspect = (float) w / (float) h;当ImageView的宽高没有都是设置为固定值或match_parent时,resizeWidth和resizeHeight一定有一个为ture。而desiredAspect则是宽高比。继续看onMeasure中接下来的代码,在第二个if-else时,由于resizeWidth和resizeHeight一定有一个为ture,所以走进if语句块。首先调用了resolveAdjustedSize这个函数来计算宽高。我们先来看看resolveAdjustedSize的代码:
private int resolveAdjustedSize(int desiredSize, int maxSize, int measureSpec) { int result = desiredSize; final int specMode = MeasureSpec.getMode(measureSpec); final int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: /* Parent says we can be as big as we want. Just don't be larger than max size imposed on ourselves. */ result = Math.min(desiredSize, maxSize); break; case MeasureSpec.AT_MOST: // Parent says we can be as big as we want, up to specSize. // Don't be larger than specSize, and don't be larger than // the max size imposed on ourselves. result = Math.min(Math.min(desiredSize, specSize), maxSize); break; case MeasureSpec.EXACTLY: // No choice. Do what we are told. result = specSize; break; } return result; }与上面resolveSizeAndState方法很类似,当mode为AT_MOST时,
result = Math.min(Math.min(desiredSize, specSize), maxSize);是取图片size、specSize和maxSize的最小值。
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; } } }当计算后的宽高比与图片宽高比不同时,会根据之前resizeWidth和resizeHeight,用固定的那个值和图片宽高比取计算另外一个值。这样ImageView的宽高比例就完全符合了图片的实际宽高比,不会出现文章前面的留白的情况了。