大家好,又见面了,我是全栈君。
版本号:1.0
日期:2014.6.11 2014.6.12
版权:© 2014 kince 转载注明出处
ImageView是开发中经常使用到的一个控件,也能够说是不可缺少的。
对于它的使用。除了注意ScaleType的理解和设置外,还须要注意其它一些问题,比方设置一张大的背景图片内存占用和释放等。
还有它的拓展性方面,像圆角图片、圆形图片、图片边框等等。因此,假设想熟练使用这个控件,就须要对事实上现的机制有一个基本的了解。
ImageView也是直接继承于View类。基本的结构图例如以下:
鉴于篇幅大小,就不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介绍 这篇文章。
版权声明:本文博客原创文章,博客,未经同意,不得转载。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/117511.html原文链接:https://javaforall.cn