<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#bbaacc" > <ImageViewandroid:src="@drawable/ic_launcher"android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#aabbcc" /></LinearLayout>上边布局文件为了便于查看各种属性效果,故意加了两个背景颜色,这对我们今天的源码分析影响不大。接着运行一下代码,效果图如下:

恩,不错,运行结果正如所愿,屏幕上显示的正是我们设置的图片,这时候心中不由欣喜ImageView的使用就是这样简单,so easy嘛!呵呵,如果真是这么想那就大错特错了,上边的示例仅仅是在布局文件中使用了ImageView的src,layout_width和layout_height这三个属性罢了,它其他的重要属性我们还没有用到,今天这篇文章就是主要结合源码讲解ImageView的另一个重要的属性------ScaleType,其他的一些属性等将来需要的话再做详细解说。好了,现在正式进入主题。
ScaleType属性主要是用来定义图片(Bitmap)如何在ImageView中展示的,姑且就认为是展示吧,系统给我们提供了8种可选属性:matrix、fitXY、fitStart、fitCenter、fitEnd、center、centerCrop和centerInside。每一种属性对应的展示效果是不一样的,下面我们先来做一个实验来说明每一种属性的显示效果,我从之前的项目中挑选了两张图片,一张图片的实际尺寸是720*1152,另一张是我把第一张图翻转放缩得到的,它的实际尺寸是96*60,之所以采用两张图片是为了便于对比和观察结果,原图片如下:


OK,测试图片准备好了,接下来我们在布局文件中分别使用ScaleType的每一个属性值,我们开始写布局文件,内容如下:
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#bbccaa" > <TableLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:padding="10dp" > <TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginBottom="10dp"android:text="图片尺寸的宽和高都远远大于ImageView的尺寸" /> <TableRowandroid:layout_width="match_parent"android:layout_height="wrap_content" > <ImageViewandroid:layout_width="300px"android:layout_height="300px"android:background="#aabbcc"android:scaleType="matrix"android:src="@drawable/test" /> <ImageViewandroid:layout_width="300px"android:layout_height="300px"android:layout_marginLeft="10dp"android:background="#aabbcc"android:scaleType="fitXY"android:src="@drawable/test" /> <ImageViewandroid:layout_width="300px"android:layout_height="300px"android:layout_marginLeft="10dp"android:background="#aabbcc"android:scaleType="fitStart"android:src="@drawable/test" /> <ImageViewandroid:layout_width="300px"android:layout_height="300px"android:layout_marginLeft="10dp"android:background="#aabbcc"android:scaleType="fitCenter"android:src="@drawable/test" /></TableRow> <TableRowandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="5dp" > <TextViewandroid:layout_width="300px"android:layout_height="wrap_content"android:gravity="center"android:text="matrix" /> <TextViewandroid:layout_width="300px"android:layout_height="wrap_content"android:gravity="center"android:text="fitXY" /> <TextViewandroid:layout_width="300px"android:layout_height="wrap_content"android:gravity="center"android:text="fitStart" /> <TextViewandroid:layout_width="300px"android:layout_height="wrap_content"android:gravity="center"android:text="fitCenter" /></TableRow> <TableRowandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="10dp" > <ImageViewandroid:layout_width="300px"android:layout_height="300px"android:background="#aabbcc"android:scaleType="fitEnd"android:src="@drawable/test" /> <ImageViewandroid:layout_width="300px"android:layout_height="300px"android:layout_marginLeft="10dp"android:background="#aabbcc"android:scaleType="center"android:src="@drawable/test" /> <ImageViewandroid:layout_width="300px"android:layout_height="300px"android:layout_marginLeft="10dp"android:background="#aabbcc"android:scaleType="centerCrop"android:src="@drawable/test" /> <ImageViewandroid:layout_width="300px"android:layout_height="300px"android:layout_marginLeft="10dp"android:background="#aabbcc"android:scaleType="centerInside"android:src="@drawable/test" /></TableRow> <TableRowandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="5dp" > <TextViewandroid:layout_width="300px"android:layout_height="wrap_content"android:gravity="center"android:text="fitEnd" /> <TextViewandroid:layout_width="300px"android:layout_height="wrap_content"android:gravity="center"android:text="center" /> <TextViewandroid:layout_width="300px"android:layout_height="wrap_content"android:gravity="center"android:text="centerCrop" /> <TextViewandroid:layout_width="300px"android:layout_height="wrap_content"android:gravity="center"android:text="centerInside" /></TableRow></TableLayout></ScrollView>为了快速进入今天文章主题,布局文件并没有按照平时开发中所遵循的Android开发规范来写。布局中使用的是TableLayout标签嵌套TableRow的方式,分成两行,每一行是4列,恰好8种属性可以合理对比查看。当然了这里有更高效的写法来实现同样的效果,比如使用RelativeLayout布局等。

通过上述实验结果对比,就可以得出部分属性的展示效果。比如fitXY属性,当ImageView的属性设置成了fitXY时,图片的宽和高就会相应的拉伸或者是压缩来填充满整个ImageView,注意这种拉放缩不成比例。当ImageView的属性设置成了matrix时,如果图片宽高大于ImageView的宽高时,图片的显示就是从ImageView的左上角开始平铺,超出部分不再显示;如果图片宽高小于ImageView的宽高时,图片的显示也是从ImageView的左上角开始平铺,缺少部分空白显示出来或者是显示ImageView的背景。以上仅仅是根据运行结果来得出的结果,权威结论还要通过查看源码来得出,本文分析的源码是Android2.2版本。
分析ImageView的源码首先从它的构造方法开始,看一下构造方法里边都做了什么工作。构造方法如下:
public ImageView(Context context) {super(context);initImageView(); }public ImageView(Context context, AttributeSet attrs) {this(context, attrs, 0); }public ImageView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);initImageView(); TypedArray a = context.obtainStyledAttributes(attrs,com.android.internal.R.styleable.ImageView, defStyle, 0); Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src);if (d != null) {setImageDrawable(d);} ////////////////////////////////////////////////////// 以下源码部分属性初始化不涉及主核心,不再贴出////////////////////////////////////////////////////a.recycle(); //need inflate syntax/reader for matrix } 通过查看构造方法发现,在三个构造方法中都调用了initImageView()这个方法,这个方法是干嘛使的,我们稍后在看。其次是根据在布局文件中定义的属性来初始化相关属性,在测试布局中我们仅仅是用了ImageView的src,layout_width,layout_height和scaleType属性(background属性暂时忽略)。那也就是说在构造函数的初始化中就是对相关属性进行了赋值操作,通过解析src属性我们获取到了一个Drawable的实例对象d,如果d是非空的话就把d作为参数又调用了setImageDrawable(d)函数,我们看看一下这个函数主要做了什么工作,源码如下:/*** Sets a drawable as the content of this ImageView.** @param drawable The drawable to set*/ public void setImageDrawable(Drawable drawable) {if (mDrawable != drawable) {mResource = 0;mUri = null; int oldWidth = mDrawableWidth;int oldHeight = mDrawableHeight; updateDrawable(drawable); if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {requestLayout();}invalidate();} } 此方法类型是public的,目的是干嘛使的不再解释了,注释上说的是把给定的drawable作为当前ImageView的展示内容,接着是个条件判断,在刚刚完成初始化的时候mDrawable属性还没有被赋值此时为空,因此判断成立程序进入条件语句继续执行,在这里把属性mResource和mUri归零操作并计入mDrawableWidth和mDrawableHeight的初始值,紧接着把传递进来的drawable参数传递给了updateDrawable()方法,那我们继续跟进updateDrawable()看看这里边又做了什么操作,源码如下:private void updateDrawable(Drawable d) {if (mDrawable != null) {mDrawable.setCallback(null);unscheduleDrawable(mDrawable);}mDrawable = d;if (d != null) {d.setCallback(this);if (d.isStateful()) {d.setState(getDrawableState());}d.setLevel(mLevel);mDrawableWidth = d.getIntrinsicWidth();mDrawableHeight = d.getIntrinsicHeight();applyColorMod();configureBounds();} else {mDrawableWidth = mDrawableHeight = -1;} } 该方法首先进行了非空判断,此时mDrawable的值依然是空,所以条件判断不成立跳过此部分,紧接着把传递进来的非空参数d的字赋值给了属性mDrawable,到这里mDrawable才算是完成了赋值操作。然后又进行了条件判断,并设置d的callback为当前ImageView(因为ImageView的父类View实现了Drawable的Callback接口)接下来又把图片的宽和高分别赋值给了mDrawableWidth和mDrawableHeight,紧接着又调用了applyColorMod()方法,当我们没有给ImageView设置透明度或者是颜色过滤器时该方法不会执行。然后调用configureBounds()方法,此方法是我们今天要讲的和ScaleType属性息息相关的重点,不耽误时间了赶紧瞅一下源码吧,(*^__^*) 嘻嘻…… private void configureBounds() {if (mDrawable == null || !mHaveFrame) {return;}int dwidth = mDrawableWidth;int dheight = mDrawableHeight;int vwidth = getWidth() - mPaddingLeft - mPaddingRight;int vheight = getHeight() - mPaddingTop - mPaddingBottom;boolean fits = (dwidth < 0 || vwidth == dwidth) &&(dheight < 0 || vheight == dheight); ////////////////////////////////////////代码块一//////////////////////////////////////// if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {/* If the drawable has no intrinsic size, or we"re told toscaletofit, then we just fill our entire view.*/mDrawable.setBounds(0, 0, vwidth, vheight);mDrawMatrix = null; ////////////////////////////////////////代码块二//////////////////////////////////////// } else {// We need to do the scaling ourself, so have the drawable// use its native size.mDrawable.setBounds(0, 0, dwidth, dheight);if (ScaleType.MATRIX == mScaleType) {// Use the specified matrix as-is.if (mMatrix.isIdentity()) {mDrawMatrix = null;} else {mDrawMatrix = mMatrix;} ////////////////////////////////////////代码块三//////////////////////////////////////// } else if (fits) {// The bitmap fits exactly, no transform needed.mDrawMatrix = null; ////////////////////////////////////////代码块四//////////////////////////////////////// } else if (ScaleType.CENTER == mScaleType) {// Center bitmap in view, no scaling.mDrawMatrix = mMatrix;mDrawMatrix.setTranslate((int) ((vwidth - dwidth) * 0.5f + 0.5f), (int) ((vheight - dheight) * 0.5f + 0.5f)); ////////////////////////////////////////代码块五//////////////////////////////////////// } 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((int) (dx + 0.5f), (int) (dy + 0.5f)); ////////////////////////////////////////代码块六//////////////////////////////////////// } 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 = (int) ((vwidth - dwidth * scale) * 0.5f + 0.5f);dy = (int) ((vheight - dheight * scale) * 0.5f + 0.5f);mDrawMatrix.setScale(scale, scale);mDrawMatrix.postTranslate(dx, dy); ////////////////////////////////////////代码块七//////////////////////////////////////// } else {// Generate the required transform.mTempSrc.set(0, 0, dwidth, dheight);mTempDst.set(0, 0, vwidth, vheight); mDrawMatrix = mMatrix;mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType)); ////////////////////////////////////////代码块八//////////////////////////////////////// }}} configureBoundd()函数比较长,为了方便分析源码,我把该函数代码分成了8小块,每一个小块都是一个单独的逻辑。每一块的详解如下:private void initImageView() {mMatrix = new Matrix();mScaleType = ScaleType.FIT_CENTER; } 可以看到,当我们没有在布局文件中使用scaleType属性或者是没有手动调用setScaleType方法时,那么mScaleType的默认值就是FIT_CENTER。