- 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
- rotected 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文件中设置。但是这样直接使用会有一个隐形的弊端,如果显示的图片过多或者单张显示的图片像素过大,就容易出现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介绍 这篇文章。