结合源码分析ImageView的adjustViewBounds属性

关于adjustViewBounds属性,网上的资料并不太多,而且都有些问题,通过查看源码并实验之后,我稍微弄清楚了这个属性,不过可能还有一些问题,如果大家发现了可以告诉我。
首先说三点结论:
(1) adjustViewBounds是会改变ImageView的宽高的,而ScaleType只是调整图片绘制的位置和缩放比例的。
(2) adjustViewBounds并不一定要和maxWidth和maxHeight一起用,只要父ViewGroup对ImageView的宽高造成了限制,adjustViewBounds也是有效的。
(3) adjustViewBounds为true,且ImageView最终的宽高比与图片的宽高比一致且宽高小于或等于图片的宽高时,ScaleType中只有CENTER这个属性会对最后绘制的图片产生影响,其他的Type没有作用。
那么接下来详细说明一下。
首先看一下ImageView的onMeasure方法的源码:

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

    int pleft = mPaddingLeft;
    int pright = mPaddingRight;
    int ptop = mPaddingTop;
    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
            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 && !mAdjustViewBoundsCompat) {
                        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 && !mAdjustViewBoundsCompat) {
                        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);
}

通过源码可以看到,如果想要resize的话,需要resizeWidth == true||resizeHeight == true,而它们为true的条件为:
resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;
结合onMeasure的知识我们知道,有两种情况可以出现上面这行代码的情况:
(1)ImageView为wrap_content时。
(2)ImageView为match_parent且父ViewGroup的MeasuredMode为AT_MOST时。
总而言之,就是ImageView的宽高并不能确定时,就可以进入resize的逻辑了。进入resize之后逻辑也很简单:
w和h是图片的宽高,pLeft和pRight等等都是ImageView的padding。而resolveAdjustedSize的源码是这样的:

private int resolveAdjustedSize(int desiredSize, int maxSize, int measureSpec) {
    int result = desiredSize;
    int specMode = MeasureSpec.getMode(measureSpec);
    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;
}

显然进入这段代码时ImageView肯定是AT_MOST了,因此最终的返回值也就是图片的宽,maxWidth以及measuredSpec中指定的width这三者的最小值了。maxWidth和maxHeight如果不在XML中指定的话,默认为int最大值,measuredSpec中的width和height一般是父ViewGroup中剩余的宽和高。这也解释了上述第2点,只要父ViewGroup对ImageView对宽高造成了限制,效果同maxWidth和maxHeight。
接下来就是调整图片尺寸的代码了。不准备细说了,大概就是先计算好图片本来的比例,然后用这个比例调整宽高。但是显然如果有一条边的长度是有限制的话,可能最终会造成ImageView的比例并不等于图片的比例的问题。
接下来我们请doge来演示一下。
doge们
6张图片从上倒下对应的代码分别为:

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_red_light"
        android:scaleType="center"
        android:src="@drawable/doge" />

    <ImageView
        android:layout_width="150dp"
        android:layout_height="75dp"
        android:background="@android:color/holo_red_light"
        android:src="@drawable/doge"></ImageView>

    <FrameLayout
        android:layout_width="150dp"
        android:layout_height="75dp"
        android:background="@android:color/holo_green_light">

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@android:color/holo_red_light"
            android:src="@drawable/doge" />
    </FrameLayout>

    <FrameLayout
        android:layout_width="150dp"
        android:layout_height="75dp"
        android:background="@android:color/black">

        <FrameLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@android:color/holo_green_light">

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:adjustViewBounds="true"
                android:background="@android:color/holo_red_light"
                android:scaleType="fitCenter"
                android:src="@drawable/doge" />
        </FrameLayout>
    </FrameLayout>

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:adjustViewBounds="true"
        android:background="@android:color/holo_red_light"
        android:maxHeight="75dp"
        android:maxWidth="150dp"
        android:src="@drawable/doge" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:adjustViewBounds="true"
        android:background="@android:color/holo_red_light"
        android:scaleType="center"
        android:src="@drawable/doge" />

我给doge设了背景色,可以看到adjustViewBounds的和wrap_content的ImageView都没有显示背景色,而没有adjustViewBounds的ImageView则显示了背景色,说明adjustViewBounds确实调整了ImageView的size。
其中最后一张图片是由于parent空间不足,所以虽然表面上没有maxHeight和maxWidth的限制,但其实是被parent限制了。并且这张图片的scaleType我也特意设成了center,可以看见是截取原尺寸的图片显示的,参看源码可以知道,众多scaleType中只有center这个属性是不会对图片进行缩放的,其他的选项都会进行缩放,但是对于wrap_content和经过adjustViewBounds调整的ImageView来说缩放其实并没有效果,所以最终只有center会产生一些影响。

最后,关于最开始的第三条再解释一下,为什么要加上”且ImageView最终的宽高比与图片的宽高比一致且宽高小于或等于图片的宽高时”这么啰嗦的条件。我来给你们看一下宽高比一致但是宽高大于图片时会怎么样:
这里写图片描述
代码是这样的

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="150dp"
        android:adjustViewBounds="true"
        android:background="@android:color/holo_red_light"
        android:scaleType="center"
        android:src="@drawable/doge" />

其实当两条边都可以resize时是没有这种问题的,但如果其中一条边长度写死了并且还写的比图片的size大的话最终就有可能会这样,在这种情况下,所有的scaleType显然都会有影响。

好了,本篇文章到此结束,如果有什么问题可以告诉我,3Q~~

可能有人会问宽高比与图片不一致的情况还没讲呢,很简单,将上面的ImageView加一行设成下面这样就行。不过你知道doge会变成什么样吗?改变scaleType还会有效果吗?就不截图了,大家猜猜看啊。

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="150dp"
        android:adjustViewBounds="true"
        android:background="@android:color/holo_red_light"
        android:maxWidth="100dp"
        android:scaleType="center"
        android:src="@drawable/doge" />
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值