标签:
做Android开发的童靴们肯定对系统自带的控件使用的都非常熟悉,比如Button、TextView、ImageView等。如果你问我具体使用,我会给说:拿ImageView来说吧,首先创建一个新的项目,在项目布局文件中应用ImageView控件,代码如下:
<?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" > <ImageView android: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" > <TableLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="10dp" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="10dp" android:text="图片尺寸的宽和高都远远大于ImageView的尺寸" /> <TableRow android:layout_width="match_parent" android:layout_height="wrap_content" > <ImageView android:layout_width="300px" android:layout_height="300px" android:background="#aabbcc" android:scaleType="matrix" android:src="@drawable/test" /> <ImageView android:layout_width="300px" android:layout_height="300px" android:layout_marginLeft="10dp" android:background="#aabbcc" android:scaleType="fitXY" android:src="@drawable/test" /> <ImageView android:layout_width="300px" android:layout_height="300px" android:layout_marginLeft="10dp" android:background="#aabbcc" android:scaleType="fitStart" android:src="@drawable/test" /> <ImageView android:layout_width="300px" android:layout_height="300px" android:layout_marginLeft="10dp" android:background="#aabbcc" android:scaleType="fitCenter" android:src="@drawable/test" /> </TableRow> <TableRow android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="5dp" > <TextView android:layout_width="300px" android:layout_height="wrap_content" android:gravity="center" android:text="matrix" /> <TextView android:layout_width="300px" android:layout_height="wrap_content" android:gravity="center" android:text="fitXY" /> <TextView android:layout_width="300px" android:layout_height="wrap_content" android:gravity="center" android:text="fitStart" /> <TextView android:layout_width="300px" android:layout_height="wrap_content" android:gravity="center" android:text="fitCenter" /> </TableRow> <TableRow android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" > <ImageView android:layout_width="300px" android:layout_height="300px" android:background="#aabbcc" android:scaleType="fitEnd" android:src="@drawable/test" /> <ImageView android:layout_width="300px" android:layout_height="300px" android:layout_marginLeft="10dp" android:background="#aabbcc" android:scaleType="center" android:src="@drawable/test" /> <ImageView android:layout_width="300px" android:layout_height="300px" android:layout_marginLeft="10dp" android:background="#aabbcc" android:scaleType="centerCrop" android:src="@drawable/test" /> <ImageView android:layout_width="300px" android:layout_height="300px" android:layout_marginLeft="10dp" android:background="#aabbcc" android:scaleType="centerInside" android:src="@drawable/test" /> </TableRow> <TableRow android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="5dp" > <TextView android:layout_width="300px" android:layout_height="wrap_content" android:gravity="center" android:text="fitEnd" /> <TextView android:layout_width="300px" android:layout_height="wrap_content" android:gravity="center" android:text="center" /> <TextView android:layout_width="300px" android:layout_height="wrap_content" android:gravity="center" android:text="centerCrop" /> <TextView android:layout_width="300px" android:layout_height="wrap_content" android:gravity="center" android:text="centerInside" /> </TableRow> </TableLayout> </ScrollView>
为了快速进入今天文章主题,布局文件并没有按照平时开发中所遵循的Android开发规范来写。布局中使用的是TableLayout标签嵌套TableRow的方式,分成两行,每一行是4列,恰好8种属性可以合理对比查看。当然了这里有更高效的写法来实现同样的效果,比如使用RelativeLayout布局等。
在布局文件中我们定义了每个ImageView的宽高都是固定的300像素,这个尺寸远小于或者是远大于测试的图片尺寸,另外为了便于观察效果我给每个ImageView的背景都设置了颜色,接着运行大图和小图的测试结果,效果如下:
通过上述实验结果对比,就可以得出部分属性的展示效果。比如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 to scaletofit, 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小块,每一个小块都是一个单独的逻辑。每一块的详解如下:
该模块代码首先做了一个条件判断,如果当前mDrawable为空或者是mHaveFrame为false则函数直接返回不再往下执行,由于后边的逻辑主要是根据ScaleType属性的类型来判断图片的展示方式,所以再后来这个函数肯定是能往下走的通的,由于篇幅的原因不再深入讲解该函数的调用时机,我会在之后的文章中专门根据源码讲解一下Android系统下View的绘制流程,在之后的绘制流程中会提到configureBounds()的调用时机。该代码块的逻辑是获取图片的宽高存储在dwidth,dheight中,然后又获取到了ImageView的显示图片区域的宽高存放在vwidth和vheight中。然后定义了一个boolean类型的变量,该变量若为true就表示不需要对图片进行放缩处理。
private void initImageView() { mMatrix = new Matrix(); mScaleType = ScaleType.FIT_CENTER; }可以看到,当我们没有在布局文件中使用scaleType属性或者是没有手动调用setScaleType方法时,那么mScaleType的默认值就是FIT_CENTER。
Android 源码系列之<一>从源码的角度深入理解ImageView的ScaleType属性
标签:
原文地址:http://blog.csdn.net/llew2011/article/details/50855655