Android|图形图像之ImageView.ScaleType

今天终于有时间来说说图形图像的处理了,先来说说ImageView.ScaleType,这一篇只说图像的静态展示。大多数应用或多或少的跟图片有关,比如管理应用的软件,新闻阅读类的软件,图像应用软件,社交直播等等。。。基本上没有应用是不使用图片的,好的应用应该要向用户展示精美的图片,但是大家都知道Android设备千差万别,拥有不同的分辨率,图片的资源也有不同的尺寸,甚至设计师们给我们的图片也都有各自的尺寸,而我们要把他放在不同尺寸的空间内合理展示,还有一些情况是,我们图片是从磁盘或者云端获取的,那么如果正确的显示在我们的ImageVIew中或者自定义组件中呢?看图说话:
ImageView.ScaleType.Center(Center the image in the view, but perform no scaling):使图片内容居中显示,但是不会缩放图片

这里写图片描述这里写图片描述这里写图片描述

观察图片大小,随着ImageView(红色代表布局边界,下面不再提及)的宽高的变化,图片的大小并没有变化,也就是说图片并没有被缩放。

我们看看源码是怎么实现的呢?

if (ScaleType.MATRIX == mScaleType) {
                // Use the specified matrix as-is.
                if (mMatrix.isIdentity()) {
                    mDrawMatrix = null;
                } else {
                    mDrawMatrix = mMatrix;
                }
            }

注意这里mMatrix.isIdentity()表示是否是单位矩阵,如果mMatrix没有被修改过,那么isIdentity()返回的就是true,默认构造的mMatrix就是单位矩阵,因此不会对图片有任何的缩放处理,而图片的宽高又是怎么来的呢?

mDrawable.setBounds(0, 0, dwidth, dheight);

在执行ScaleType的判断之前,都会先给图片设置显示区域的

ImageView.ScaleType.CENTER_CROP:
Scale the image uniformly (maintain the image’s aspect ratio) so that both dimensions (width and height) of the image will be equal to or larger than the corresponding dimension of the view (minus padding)
按照图片的宽高比来缩放图片,最终使图片的宽高等于或者大于视图的宽高,其实最终就是一边相等,另外一边大于或者等于视图的宽度

这里写图片描述这里写图片描述
这里写图片描述这里写图片描述

可以看到无论ImageVIew如何变化,图片始终保持宽高比,并且有一条边缩放到和试图等宽,另外一条边大于容器的宽度。

为什么会显示成这样呢?我们还是来看源码(可在ImageView的源码中查看):

 else if (ScaleType.CENTER_CROP == mScaleType) {
                mDrawMatrix = mMatrix;

                float scale;
                float dx = 0, dy = 0;

                if (dwidth * vheight > vwidth * dheight) {//用宽高比的大小来确定以哪一边的比例缩放为基准
                    scale = (float) vheight / (float) dheight;
                    dx = (vwidth - dwidth * scale) * 0.5f;
                } else {
                    scale = (float) vwidth / (float) dwidth;
                    dy = (vheight - dheight * scale) * 0.5f;
                }

                mDrawMatrix.setScale(scale, scale);
                mDrawMatrix.postTranslate(Math.round(dx), Math.round(dy));
            }
dwidth * vheight > vwidth * dheight

这个条件我们这样来看:
vheight/dheight>vwidth/dwidth
我们把vheight/dheight 叫做高比,把vwidth/dwidth 叫做宽比
当高比大于宽比是,选择用高比作为缩放值,但宽比大于高比时,选择用宽比作为缩放值,为什么这么判断呢?应为对比度小的那一边会在缩放的过程中先到达边界,而对比度大的那一边是比对比度小的那一边后到达边界,而缩放的最终状态就是要对比大的那一边后到达边界,所以才以对比度大的那一边的比值为缩放标准,这个是可以用数据公式证明了,由于格式的原因,我就不写了。
不知道大家还观察到一个现象没有,当缩放到某一状态时,图片的大小是不变的,这个留给大家思考吧,我会在结尾写出来: )

ImageVIew.ScaleType.CENTER_INSIDE:
(Scale the image uniformly (maintain the image’s aspect ratio) so that both dimensions (width and height) of the image will be equal to or less than the corresponding dimension of the view (minus padding).)
按照宽高比缩放图片使图片的宽高小于或者等于视图的宽高:
这里写图片描述这里写图片描述这里写图片描述这里写图片描述这里写图片描述
可以看出上面是CENTER_INSIDE的所有显示效果,包括两种情况:
如果图片的宽和高都大于视图的宽和高,则图片不进行缩放,如果图片的宽或者高其中有一条边的长度大于视图的宽或者高时,图片会被缩放,所以只要是进行缩放,图片的其中一条边会与视图的边长相等,另外一条边的长度会小于视图的对应边的长度,说了结果,我们就来看看计算原理(可在ImageView的源码中查看):

                if (dwidth <= vwidth && dheight <= vheight) {
                    scale = 1.0f;
                } else {
                    scale = Math.min((float) vwidth / (float) dwidth,
                            (float) vheight / (float) dheight);
                }

当dwidth<=vwidth&&dheight<=vheight时,scale=1.0f,这时候,图片是原大小
当dwidth>vwidth|| dheight>vheight时,图片会取 vwidth/dwidth和vheight/dheight这两者中较小的一边来缩放,为什么呢?你们可以想象一下,在缩放的过程中,谁会先到达边界呢(接近视图的宽或者高),一定是vwidth/dwidth和vheight/dheight中较大的那个,图片的宽(高)与视图的宽(高)越接近,那么在缩放的过程中,图片的宽(高)先接近视图的宽(高),但是CENTER_INSIDE的最终显示效果是,图片的一条边与视图的一条边相等,另外一条边小于视图的边,所以为了达到显示效果,还要继续缩放,那么我们可以肯定最终要想达到CENTER_INSIDE的显示效果,scale必须是vwidth/dwidth和vheight/dheight中较小的那个:

scale = Math.min((float) vwidth / (float) dwidth,(float) vheight / (float) dheight);

ImageView.ScaleType.FIT_CENTER(Compute a scale that will maintain the original src aspect ratio, but will also ensure that src fits entirely inside dst. At least one axis (X or Y) will fit exactly. The result is centered inside dst):
保持图片的宽高比,计算出一个缩放比例,使整个图片位于目标区域内,并且至少在一个方向(X 或者 Y)会完全填充,最终的结果是,图片会居中显示在目标区域内!
这里写图片描述这里写图片描述
这里写图片描述这里写图片描述这里写图片描述

FIT_CENTER的显示效果与CENTER_INSIDE的效果的区别是,不管视图的宽高是否大于或小于图片的宽高,图片始终是缩放的,并且居中显示,之前介绍CENTER_INSIDE时的算法为:

                if (dwidth <= vwidth && dheight <= vheight) {
                    scale = 1.0f;
                } else {
                    scale = Math.min((float) vwidth / (float) dwidth,
                            (float) vheight / (float) dheight);
                }

可以往上翻看CENTER_INSIDE的显现效果,当视图的宽高大于图片的宽高时,图片是原图显示的,不进行任何缩放,但是FIT_CENTER是图片始终会被缩放(图片与视图大小完全相等时,可以认为缩放因子为 1)

ImageView.ScaleType.FIT_START(Compute a scale that will maintain the original src aspect ratio, but will also ensure that src fits entirely inside dst. At least one axis (X or Y) will fit exactly. START aligns the result to the left and top edges of dst):
保持图片的宽高比,计算出一个缩放比例,使整个图片位于目标区域内,并且至少在一个方向(X 或者 Y)会完全填充,最终的结果是,图片会以目标显示区域的左边和顶部对齐:

这里写图片描述这里写图片描述

计算Scale的方式与CENTER_INSIDE类似,只是去掉了图片的宽高大小与视图的宽高大小的比较:

scale = Math.min((float) vwidth / (float) dwidth,(float) vheight / (float) dheight);

至于如何在目标区域的左边和顶部对齐,涉及到矩阵的平移了,相信大家都懂吧:)

ImageView.ScaleType.FIT_END(Compute a scale that will maintain the original src aspect ratio, but will also ensure that src fits entirely inside dst. At least one axis (X or Y) will fit exactly. END aligns the result to the right and bottom edges of dst):
保持图片的宽高比,计算出一个缩放比例,使整个图片位于目标区域内,并且至少在一个方向(X 或者 Y)会完全填充,最终的结果是,图片会以目标显示区域的右边和底边对齐
这里写图片描述这里写图片描述这里写图片描述这里写图片描述

这个计算Scale同上!

ImageView.ScaleType.FIT_XY(Scale in X and Y independently, so that src matches dst exactly):
单独缩放图片宽或者高,直到图片的宽高完全匹配视图:
这里写图片描述这里写图片描述
这里写图片描述
如果视图的宽高比与图片的宽高比不一样,图片就完全变形了,在实际项目中基本上不会碰到这样的显示需求。

ImageView.ScaleType.MATRIX(Scale using the image matrix when drawing):
使用ImageView自身的matrix来缩放图片,如果你没有通过ImageView.setImageMatrix来重置默认的matrix的话,系统会使用默认的创建的Matrix,而这个默认的Matrix就是一个单位矩阵,不会对图片有任何影响:
这里写图片描述这里写图片描述这里写图片描述这里写图片描述

图片没有进行任何缩放,只显示在目标区域内的可见内容,超出的部分不会显示,并且图片始终是从视图的左上角开始显示的!

至此已经介绍玩所有图片的平面缩放:
ImageView.ScaleType.MATRIX
ImageView.ScaleType.CENTER
ImageView.ScaleType.CENTER_CROP
ImageView.ScaleType.CENTER_INSIDE
ImageView.ScaleType.FIT_START
ImageView.ScaleType.FIT_CENTER
ImageView.ScaleType.FIT_END
ImageView.ScaleType.FIT_XY

其中我们平常比较常用的为:
ImageView.ScaleType.CENTER
ImageView.ScaleType.CENTER_CROP
ImageView.ScaleType.CENTER_INSIDE
ImageView.ScaleType.FIT_CENTER

不管是使用原生控件,还是自定义控件,还是开源控件,凡是对图片进行平面缩放显示的,尤其是人物图像或者是类似瀑布流类似的图片显示与处理,都逃脱不了以上8中显示方式!可以查看你们公司自己的项目中已有的关于图片处理的,或者是对Bitmap的大小处理的,以及开源库中对图片的处理方式,不外乎我上面提到的两种最重要的算法:

                float scale;
                float dx = 0, dy = 0;

                if (dwidth * vheight > vwidth * dheight) {
                    scale = (float) vheight / (float) dheight;
                    dx = (vwidth - dwidth * scale) * 0.5f;
                } else {
                    scale = (float) vwidth / (float) dwidth;
                    dy = (vheight - dheight * scale) * 0.5f;
                }

                mDrawMatrix.setScale(scale, scale);
                mDrawMatrix.postTranslate(Math.round(dx), Math.round(dy));
                mDrawMatrix = mMatrix;
                float scale;
                float dx;
                float dy;

                if (dwidth <= vwidth && dheight <= vheight) {
                    scale = 1.0f;
                } else {
                    scale = Math.min((float) vwidth / (float) dwidth,
                            (float) vheight / (float) dheight);
                }

                dx = Math.round((vwidth - dwidth * scale) * 0.5f);
                dy = Math.round((vheight - dheight * scale) * 0.5f);

                mDrawMatrix.setScale(scale, scale);
                mDrawMatrix.postTranslate(dx, dy);

这是系统源码,大家可以在ImageView.configureBounds中查阅

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值