版本号:1.0 
日期:2014.6.11 2014.6.12
版权:© 2014 kince 转载注明出处
  ImageView是开发中经常使用到的一个控件,也能够说是不可缺少的。

对于它的使用。除了注意ScaleType的理解和设置外,还须要注意其它一些问题,比方设置一张大的背景图片内存占用和释放等。

还有它的拓展性方面,像圆角图片、圆形图片、图片边框等等。因此,假设想熟练使用这个控件,就须要对事实上现的机制有一个基本的了解。

  ImageView也是直接继承于View类。基本的结构图例如以下:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2FuZ2ppbnl1NTAx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />

 鉴于篇幅大小,就不copy ImageView的总体代码,选择结构图中的部分作为重点。首先是构造方法,代码例如以下:
 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);
} mBaselineAlignBottom = a.getBoolean(
com.android.internal.R.styleable.ImageView_baselineAlignBottom, false); mBaseline = a.getDimensionPixelSize(
com.android.internal.R.styleable.ImageView_baseline, -1); setAdjustViewBounds(
a.getBoolean(com.android.internal.R.styleable.ImageView_adjustViewBounds,
false)); setMaxWidth(a.getDimensionPixelSize(
com.android.internal.R.styleable.ImageView_maxWidth, Integer.MAX_VALUE)); setMaxHeight(a.getDimensionPixelSize(
com.android.internal.R.styleable.ImageView_maxHeight, Integer.MAX_VALUE)); int index = a.getInt(com.android.internal.R.styleable.ImageView_scaleType, -1);
if (index >= 0) {
setScaleType(sScaleTypeArray[index]);
} int tint = a.getInt(com.android.internal.R.styleable.ImageView_tint, 0);
if (tint != 0) {
setColorFilter(tint);
} int alpha = a.getInt(com.android.internal.R.styleable.ImageView_drawableAlpha, 255);
if (alpha != 255) {
setAlpha(alpha);
} mCropToPadding = a.getBoolean(
com.android.internal.R.styleable.ImageView_cropToPadding, false); a.recycle(); //need inflate syntax/reader for matrix
} private void initImageView() {
mMatrix = new Matrix();
mScaleType = ScaleType.FIT_CENTER;
mAdjustViewBoundsCompat = mContext.getApplicationInfo().targetSdkVersion <=
Build.VERSION_CODES.JELLY_BEAN_MR1;
}
  在构造方法中也是非经常规的从attrs文件里读取属性值,并进行设置。也能够看到ImageView默认使用的ScaleType是FIT_CENTER。说到ScaleType。它是一个枚举类型,用于设置。寻常使用的ScaleType就是在这里定义的。

/**
* Options for scaling the bounds of an image to the bounds of this view.
*/
public enum ScaleType {
/**
* Scale using the image matrix when drawing. The image matrix can be set using
* {@link ImageView#setImageMatrix(Matrix)}. From XML, use this syntax:
* <code>android:scaleType="matrix"</code>.
*/
MATRIX (0),
/**
* Scale the image using {@link Matrix.ScaleToFit#FILL}.
* From XML, use this syntax: <code>android:scaleType="fitXY"</code>.
*/
FIT_XY (1),
/**
* Scale the image using {@link Matrix.ScaleToFit#START}.
* From XML, use this syntax: <code>android:scaleType="fitStart"</code>.
*/
FIT_START (2),
/**
* Scale the image using {@link Matrix.ScaleToFit#CENTER}.
* From XML, use this syntax:
* <code>android:scaleType="fitCenter"</code>.
*/
FIT_CENTER (3),
/**
* Scale the image using {@link Matrix.ScaleToFit#END}.
* From XML, use this syntax: <code>android:scaleType="fitEnd"</code>.
*/
FIT_END (4),
/**
* Center the image in the view, but perform no scaling.
* From XML, use this syntax: <code>android:scaleType="center"</code>.
*/
CENTER (5),
/**
* 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). The image is then centered in the view.
* From XML, use this syntax: <code>android:scaleType="centerCrop"</code>.
*/
CENTER_CROP (6),
/**
* 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). The image is then centered in the view.
* From XML, use this syntax: <code>android:scaleType="centerInside"</code>.
*/
CENTER_INSIDE (7); ScaleType(int ni) {
nativeInt = ni;
}
final int nativeInt;
}
 功能是设置图片的显示位置和大小等方面。

接着就是onMeasure()方法了,它用于设置ImageView的大小。我们在xml文件里设置ImageView的时候,假设指定了固定的宽高,那么onMeasur()方法中測量的大小就是固定的宽高大小;假设是包裹内容,那么就须要进一步的计算。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
resolveUri();//获取图片Drawable
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;在updateDrawable(Drawable d)方法赋值的。
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);
}

在onMeasure方法中,首先调用了resolveUri()这种方法。目的就是为了确定Drawable。

假设设置了drawableResource。那么Drawable就是其值;假设没有。那么就从ContentResolver获取一个Drawable。

 private void resolveUri() {
if (mDrawable != null) {
return;
} Resources rsrc = getResources();
if (rsrc == null) {
return;
} Drawable d = null; if (mResource != 0) {
try {
d = rsrc.getDrawable(mResource);
} catch (Exception e) {
Log.w("ImageView", "Unable to find resource: " + mResource, e);
// Don't try again.
mUri = null;
}
} else if (mUri != null) {
String scheme = mUri.getScheme();
if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
try {
// Load drawable through Resources, to get the source density information
ContentResolver.OpenResourceIdResult r =
mContext.getContentResolver().getResourceId(mUri);
d = r.r.getDrawable(r.id);
} catch (Exception e) {
Log.w("ImageView", "Unable to open content: " + mUri, e);
}
} else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
|| ContentResolver.SCHEME_FILE.equals(scheme)) {
InputStream stream = null;
try {
stream = mContext.getContentResolver().openInputStream(mUri);
d = Drawable.createFromStream(stream, null);
} catch (Exception e) {
Log.w("ImageView", "Unable to open content: " + mUri, e);
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
Log.w("ImageView", "Unable to close content: " + mUri, e);
}
}
}
} else {
d = Drawable.createFromPath(mUri.toString());
} if (d == null) {
System.out.println("resolveUri failed on bad bitmap uri: " + mUri);
// Don't try again.
mUri = null;
}
} else {
return;
} updateDrawable(d);
}
  之后在resolveUri()这种方法的最后,调用了 updateDrawable(d)方法。这种方法代码例如以下:
 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);
d.setLayoutDirection(getLayoutDirection());
d.setVisible(getVisibility() == VISIBLE, true);
mDrawableWidth = d.getIntrinsicWidth();
mDrawableHeight = d.getIntrinsicHeight();
applyColorMod();
configureBounds();
} else {
mDrawableWidth = mDrawableHeight = -1;
}
}
  能够看到就是为了Drawable宽高赋值的。回过头来继续看。假设Drawable的宽高不为空的话就分别赋值给w和h。假设为空的话值为-1。然后是一个if推断,mAdjustViewBounds作为推断的变量,它是在setAdjustViewBounds方法中设置的,默觉得false,所以必须设置为true,这个推断才会运行。当然这个变量的值也能够在xml文件里设置(android:adjustViewBounds)。

那这种方法是做什么用的呢?设置View的最大高度,单独使用无效,须要与setAdjustViewBounds一起使用。假设想设置图片固定大小,又想保持图片宽高比,须要例如以下设置:

1) 设置setAdjustViewBounds为true;
2) 设置maxWidth、MaxHeight;
3) 设置设置layout_width和layout_height为wrap_content。

  再看一下这个推断。
if (mAdjustViewBounds) {
resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
resizeHeight = heightSpecMode != MeasureSpec.EXACTLY; desiredAspect = (float) w / (float) h;
}
  widthSpecMode假设不是指定大小的话。由于假设指定了固定大小就不须要又一次设置大小了。

然后接下来的推断也是基于 resizeWidth和resizeHeight 的值,假设不为true的情况下,会运行例如以下代码:

 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);
  考虑了填充,最后设置ImageView的大小。

  最后看一下onDraw()方法,
  @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas); if (mDrawable == null) {
return; // couldn't resolve the URI
} if (mDrawableWidth == 0 || mDrawableHeight == 0) {
return; // nothing to draw (empty bounds)
} if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
mDrawable.draw(canvas);
} else {
int saveCount = canvas.getSaveCount();
canvas.save(); if (mCropToPadding) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
scrollX + mRight - mLeft - mPaddingRight,
scrollY + mBottom - mTop - mPaddingBottom);
} canvas.translate(mPaddingLeft, mPaddingTop); if (mDrawMatrix != null) {
canvas.concat(mDrawMatrix);
}
mDrawable.draw(canvas);
canvas.restoreToCount(saveCount);
}
}
  在onDraw()方法中,实现方式比較简单,假设mDrawMatrix为空,那么就直接绘制出图片;假设不为空,那么还须要绘制矩阵。这就涉及到mDrawMatrix矩阵了。它是在哪赋值的呢,就是ScaleType。

这个是在configureBounds()方法中设置的,

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));
}
}
}
  能够看到在if推断中,对各个ScaleType的类型都进行了推断。依据不同的ScaleType设置不同的矩阵mDrawMatrix。然后通过矩阵对图像进行变换,从而显示出不同的效果。

  除了这一点经常使用到之外,还有就是怎样设置图片资源了,有下面几个方法:setImageResource(int resId)、setImageURI(Uri uri)、setImageDrawable(Drawable drawable)、setImageBitmap(Bitmap bm)等。或者也能够在xml文件里设置。

可是这样直接使用会有一个隐形的弊端,假设显示的图片过多或者单张显示的图片像素过大,就easy出现OOM问题。因此就应该依据需求对图片进行预处理,经常用法有下面几种:

1、缩放、边界压缩
     在内存中载入图片时直接在内存中做处理。

关于图片压缩有非常多方法,这里仅仅是列举一个简单的样例,实际使用价值不大。如有需求能够自行參考其它资料。

  InputStream is = this.getResources().openRawResource(R.drawable.xx);
BitmapFactory.Options options=new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inSampleSize = 10; //width。hight设为原来的十分一
Bitmap btp =BitmapFactory.decodeStream(is,null,options);
2、直接调用JNI
     当使用像 imageView.setBackgroundResource,imageView.setImageResource, 或者 BitmapFactory.decodeResource 这种方法来设置一张大图片的时候,这些函数在完毕decode后,终于都是通过java层的createBitmap来完毕的,须要消耗很多其它内存。

因此,改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap。再将其设为ImageView的 source,decodeStream最大的秘密在于其直接调用JNI>>nativeDecodeAsset()来完毕decode。无需再使用java层的createBitmap,从而节省了java层的空间。

假设在读取时加上图片的Config參数,能够跟有效降低载入的内存。从而跟有效阻止抛out of Memory异常。
  另外,须要特别注意:decodeStream是直接读取图片资料的字节码了, 不会依据机器的各种分辨率来自己主动适应。使用了decodeStream之后。须要在hdpi和mdpi,ldpi中配置对应的图片资源,否则在不同分辨率机器上都是相同大小(像素点数量)。显示出来的大小就不正确了。

  public static Bitmap readBitMap(Context context, int resId){
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
InputStream is = context.getResources().openRawResource(resId);
return BitmapFactory.decodeStream(is,null,opt);
}
3、手动收回占用资源
     尽管虚拟机会自己主动回收垃圾资源,可是有时候不是那么及时,这时候能够手动回收。

   if(!bmp.isRecycle() ){
bmp.recycle() //回收图片所占的内存
system.gc() //提醒系统及时回收
}
4、优化Dalvik虚拟机的堆内存分配
     使用 dalvik.system.VMRuntime类提供的setTargetHeapUtilization方法能够增强程序堆内存的处理效率。
  private final static float TARGET_HEAP_UTILIZATION = 0.75f; 

在程序onCreate时就能够调用

 VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION);
 就可以。

     除了 优化Dalvik虚拟机的堆内存分配 外,还能够强制定义自己软件的对内存大小。使用Dalvik提供的 dalvik.system.VMRuntime类来设置最小堆内存为例:Dalvik.VMRuntime类,提供对虚拟机全局,Dalvik的特定功能的接口。

Android为每一个程序分配的内存能够通过Runtime类的 totalMemory() 、freeMemory() 两个方法获取VM的一些内存信息。

private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;
VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE); //设置最小heap内存为6MB大小。
  
  下面解说一下怎样自己定义一个类继承于ImageView。首先以CircleButton为例,这是github上一个项目,实现一个圆形有点击效果的按钮。例如以下:
   实现思路是这种。先画两个圆形图案,一个是实心的圆。一个是圆环。圆环半径小于实心圆半径。这样默认就看不到圆环,然后再画出设置的图片,覆盖在二者之上。

最后在按下的时候启动一个属性动画,将圆环放大显示,关于具体的分析能够看android-circlebutton介绍 这篇文章。

版权声明:本文博客原创文章,博客,未经同意,不得转载。

最新文章

  1. maven-sprigmvc-mybatis配置
  2. 【Java每日一题】20170105
  3. ACM集训的Training Day 3的A题。。。
  4. android studio ndk配置和ndk开发
  5. 将string转换成char* (转)
  6. java中遇到过的String的一些特性
  7. java事务的处理
  8. Android 退出提示框 代码
  9. HDU 5348 MZL&#39;s endless loop
  10. Spring.NET 中的 ADO.NET 数据访问的示例
  11. vue路由跳转 vue-router的使用
  12. postgreSql 基本操作总结
  13. mysql_study_4
  14. ElasticSearch入门 第一篇:Windows下安装ElasticSearch
  15. JVM打印加载类的详情信息
  16. ThinkPHP内置日志记录
  17. JS localStorage 存储变量
  18. 小程序:位置信息(Location)及微信小程序LBS解决方案实践
  19. Nested Loops,Hash Join 和 Sort Merge Join. 三种不同连接的不同:
  20. JAVA多线程之synchronized和volatile实例讲解

热门文章

  1. C++ Preprosessor import
  2. .Net路(十三)导出数据库到EXCEL
  3. ubuntu 12.04英文版设置成中文版
  4. linux下mysql数据的导出和导入
  5. Android Ant打包笔记
  6. hdu 1398 Square Coins(生成函数,完全背包)
  7. poj1260
  8. 获取编译学习笔记 (六)—— si、di,双环
  9. as3文本框的动态拖拽和编辑
  10. 《Linux Device Drivers》 第十七章 网络驱动程序——note