ImageView详解---进阶之旅

对于Android中ImageView的ScaleType的使用属性


当ImageView的大小与它内容的大小不一致时,就出现了一个问题,里面的内容应该怎么展示?放大、缩小、靠上、靠下、居中、居右…针对这个问题ImageView提供了ScaleType属性来控制。
ImageView一共有8种缩放规则,有些规则特别容易搞混,在选择时经常会懵圈。网上能搜索到解释很多要么模棱两可要么根本就是错误的,我这里直接通过一个Demo展示一下各种ScaleType到底有什么不同,最后再从源码的层面上去分析为什么是这样。

8种缩放规则

ImageView一共有:

  1. MATRIX
  2. FIT_XY
  3. FIT_START
  4. FIT_CENTER
  5. FIT_END
  6. CENTER
  7. CENTER_CROP
  8. CENTER_INSID

8种缩放规则。下面一个一个介绍, 不过MATRIX这种缩放方式放到最后再介绍(理论上它能实现任何你想要的缩放效果)。
为了更清楚的对比各种ScaleType的区别,我这里使用了三种比例大小不同的图片,原图如下:

3张图片1张极长,1张极宽,1张宽高差不太多但极小。每张图片在4个边上都有红色的边框以方便对比图片的缩放是否超出了边界。


下面来一张属性详解图,这样的话会一目了然。

详细解释

FIT_XY

这种方式最好理解.

在X、Y方向上同时拉伸以填满ImageView控件的大小。效果和把图片直接设为ImageView的background一样。

真实效果如下图所示:

图片都完整显示了(红色边框都显示出来了,只不过在x,y方向上拉伸的幅度不一样,所以会显得X、Y方向上的边框粗细不一样)

FIT_START

这种方式用的应该不是很多,但网上关于这种方式的解释基本都是错误的:FIT_START是置于顶部
首先这种解释没有说明:图片内容到底会不会缩放。然后,说内容是置于顶部并不准确。完整且正确的解释如下:

1. 图片内容会缩放,长宽同时按比例缩放,直到其中一个方向上先撑满ImageView控件。
2. 若在X方向上先撑满,则图片内容会居上显示;若在Y方向上先撑满则会在居左显示。

真实效果如下图所示:

FIT_CENTER

这种方式是ImageView的默认缩放方式,网上关于这种方式的解释也是不准确的,正确的解释如下:

1. 图片内容会缩放,长宽同时按比例缩放,直到其中一个方向上先撑满ImageView控件。
2. 缩放完毕后,图片内容会被移动到ImageView的正中心(图片的中心与ImageView的中心对齐)

真实效果如下图所示:

FIT_END

和FIT_START一样,网上关于这种方式的解释也是错误的。正确的解释如下:

1. 图片内容会缩放,长宽同时按比例缩放,直到其中一个方向上先撑满ImageView控件。
2. 若在X方向上先撑满,则图片内容会居下显示;若在Y方向上先撑满则会在居右显示。

真实效果如下图所示:

CENTER

这种方式很简单,网上的解释基本都没什么问题。完整的解释如下:

1. 图片内容不会缩放
2. 图片内容居中显示(图片的中心与ImageView的中心对齐),若图片过大会出现图片周边区域超出ImageView的范围

真实效果如下图所示:

CENTER_CROP

这种方式,从名字上并不能完全清楚它所有的规则,完整的解释如下:

1. 图片内容会缩放,长宽同时按比例缩放,直到最后一个方向上撑满ImageView控件为止。
2. 图片内容居中显示(图片的中心与ImageView的中心对齐),图片的周边可能会被裁剪

真实效果如下图所示:

可以看到,对于第1张图片,其在X方向最后撑满,后两张图片在Y方向上最后撑满。周边的区域被裁剪了。

CENTER_INSID

这种缩放方式很容易与默认的缩放方式(FIT_CENTER)搞混,这是因为除非你的图片足够小,否则你看不出它们的区别。完整的解释如下:

1. 图片内容不一定会缩放,只有在图片长或宽大于ImageView控件的长或宽时才会缩放(而且是缩小)
2. 缩放完成后,图片会居中显示

真实效果如下图所示:

可以看到第2张小图并未缩放。

MATRIX

上面7种缩放方式是系统内置的,若上面这些缩放方式并不能满足你的需求,MATRIX这种方式就派上用场了。简单说来这种缩放类型,支持你自定义图片的缩放的规则。

假设你有这样一种缩放需求:图片在X轴方向上撑满ImageView控件。在Y轴方向上置于顶部。效果上如下:

而这张图的原图是这样的:

至于Y轴方向上的底部可能被图片填充,也可能不被,这就看图片够不够高了。
那么如何实现这种效果呢?

1. 第1步把ImageView的ScaleType设置为MATRIX
2. 自定义一个ImageView,重写其中的setFrame方法

关键代码如下:

  1. 首先,我们计算出宽度撑满需要的缩放比例:
  2. 1
    
    float widthScaleFactor = w / cw;
    
  1. 然后对matrix进行设置,改变其Scale值。
  1. 1
  1. matrix.setScale(widthScaleFactor, widthScaleFactor, 0, 0);

Android Scaletype源码分析及自定义Matrix缩放规则

上一篇中,我详细的总结了ImageView各种ScaleType对应的缩放效果。对于Matrix这种强大的缩放方式并没有详细介绍,在这一篇中会主要说一下这种缩放方式。

让我们先从源码中看一下上一篇中介绍的几种缩放方式都是怎么实现的,然后再通过Matrix的方式来实现一下上一篇最后没有实现的3种自定义缩放规则(宽度撑满并居下、高度撑满居左、高度撑满居右)

源码分析

打开ImageView的源码,找到如下方法:

1
2
3
4
5
6
7
@Override
protected boolean setFrame(int l, int t, int r, int b) {
    final boolean changed = super.setFrame(l, t, r, b);
    mHaveFrame = true;
    configureBounds();
    return changed;
}

其中有一个configureBounds()方法。我们需要的代码就在这个方法里面。首先看一下FIT_XY这种方式是如何实现的

1. FIT_XY源码实现

1
2
3
4
5
6
if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
    // If the drawable has no intrinsic size, or we're told to
   //  scaletofit, then we just fill our entire view.
    mDrawable.setBounds(0, 0, vwidth, vheight);
    mDrawMatrix = null;
  }

上述代码中的vwidth与vheight是ImageView控件大小减去padding后剩下的值即可容纳内容的最大空间。可以看到这种缩放方式非常简单粗暴,直接通过改变Drawable的绘制区域来达到,并未进行任何的Matrix操作。

2. FIT_START/FIT_CENTER/FIT_END源码实现

之所以把它们3个的放在一起说,是因为它们的实现逻辑是一样,在configureBounds()方法中有如下代码如下:

1
2
3
4
5
// Generate the required transform.
 mTempSrc.set(0, 0, dwidth, dheight);
 mTempDst.set(0, 0, vwidth, vheight);
 mDrawMatrix = mMatrix;
 mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));

可以看到这里用到了setRectToRect方法,这个方法可以根据变换前后的两个矩形得到对应的变换矩阵。上面代码中mTempSrc即是内容矩形大小也是变换的源矩形大小,mTempDst是ImageView控件矩形大小也是目标矩形大小。
这个方法还有一个参数 Matrix.ScaleToFit 它用来指定缩放选项,一共有4个选项:

  1. FILL: 独立的缩放X,Y,所以这个选项可能会改变图片的长宽比(其实效果与FIT_XY一样)
  2. START: 这种缩放方式的效果对应ImageView的FIT_START
  3. CENTER: 这种缩放方式的效果对应ImageView的FIT_CENTER
  4. END: 这种缩放方式的效果对应ImageView的FIT_END

也即是ImageView的这3种缩放规则直接使用的Matrix内置的这几种变换规则实现。你也可以自己用如下代码验证一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Override
	protected boolean setFrame(int l, int t, int r, int b) {
		boolean changed = super.setFrame(l, t, r, b);
		test();
		return changed;
	}

	private void test() {
		final int dw= getDrawable().getIntrinsicWidth();
		final int dh= getDrawable().getIntrinsicHeight();

		final float vw= getWidth() - getPaddingLeft() - getPaddingRight();
		final float vh= getHeight() - getPaddingTop() - getPaddingBottom();

		mTempSrc.set(0, 0, dw, dh);
		mTempDst.set(0, 0, vw, vh);
		Log.e("ghui", "mTempSrc: " + mTempSrc.toShortString());
		Log.e("ghui", "mTempDst: " + mTempDst.toShortString());
		Matrix matrix = getImageMatrix();
		matrix.reset();
		Log.e("ghui", "before: " + matrix.toShortString());
		matrix.setRectToRect(mTempSrc, mTempDst, Matrix.ScaleToFit.START);
		Log.e("ghui", "after: " + matrix.toShortString());
		setImageMatrix(matrix);
	}

这里不再赘述。

3.CENTER

CENTER的源码实现对应如下代码:

1
2
3
4
5
6
else if (ScaleType.CENTER == mScaleType) {
// Center bitmap in view, no scaling.
  mDrawMatrix = mMatrix;
  mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f),
  Math.round((vheight - dheight) * 0.5f));
}

可以看到这里是直接通过改变Matrix的Translate将内容从左上角移动到了控件的中心,使内容的中心与控件的中心对齐。并未做任何的缩放操作,
和我们上一篇中总结的效果一致。

4.CENTER_CROP

这种方式的对应的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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));
}

这里会首先比较内容的宽高比与控件的宽高比的大小,若内容的宽高比更大(内容更宽)则以控件高度与内容高度的比作为缩放比例。反之则会将控件宽度与内容宽度的比作为缩放的比例。这样实现的效果即是将内容中更小的一者(宽或高)缩放到与控件对应的一方一样。也与上一篇中总结的效果一样。
接着会通过postTranslate方法将内容移动到控件中心,计算方法与CENTER中的一样。

5. CENTER_INSID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
else if (ScaleType.CENTER_INSIDE == mScaleType) {
  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);
}

可以看到若内容的宽高都小于控件的宽高,这种方式的Scale为1,即不会去缩放。否则的话则会去选择一个更小的缩放比例(控件的宽高与内容的宽高比,取其中较小的一个 )
效果也与上一篇中总结的一致。

Matrix-实现自定义的缩放效果。

其实通过学习上面的源码,自定义实现 宽度撑满并居上、宽度撑满并居下、高度撑满居左、高度撑满居右这几种效果也是很简单的。先把代码贴出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Override
	protected boolean setFrame(int l, int t, int r, int b) {
		boolean changed = super.setFrame(l, t, r, b);
		if (getScaleType() == ScaleType.MATRIX) transformMatrix();
		return changed;
	}

	private void transformMatrix() {
		Matrix matrix = getImageMatrix();
		matrix.reset();
		float h = getHeight();
		float w = getWidth();
		float ch = getDrawable().getIntrinsicHeight();
		float cw = getDrawable().getIntrinsicWidth();
		float widthScaleFactor = w / cw;
		float heightScaleFactor = h / ch;
		if (alignType == AlignType.LEFT) {
			matrix.postScale(heightScaleFactor, heightScaleFactor, 0, 0);
		} else if (alignType == AlignType.RIGHT) {
			matrix.postTranslate(w - cw, 0);
			matrix.postScale(heightScaleFactor, heightScaleFactor, w, 0);
		} else if (alignType == AlignType.BOTTOM) {
			matrix.postTranslate(0, h - ch);
			matrix.postScale(widthScaleFactor, widthScaleFactor, 0, h);
		} else { //default is top
			matrix.postScale(widthScaleFactor, widthScaleFactor, 0, 0);
		}
		setImageMatrix(matrix);
	}

	public enum AlignType {
		LEFT(0),
		TOP(1),
		RIGHT(2),
		BOTTOM(3);
		AlignType(int ni) {
			nativeInt = ni;
		}
		final int nativeInt;
	}

首先看第一种缩放方式:
1.高度撑满居左
因为ImageView的内容默认会居左上,所以这里可以直接对matrix执行缩放操作,将高度缩放到撑满控件。如下图所示:

2.高度撑满居右
因为要居右,所以首先将内容移动到右上角,然后再以右上角为中心执行缩放操作。如下图所示:

3.宽度撑满并居下
因为要居下,所以首先将内容移动到左下角,然后再以左下角为中心执行缩放操作。

4.宽度撑满并居上
这种方式更简单,因为内容默认在左上角,所以这里可以直接执行缩放操作即可。


  1. 
    
    
    

 

发布了20 篇原创文章 · 获赞 24 · 访问量 6万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章