編輯:關於Android編程
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: *功能是設置圖片的顯示位置和大小等方面。接著就是onMeasure()方法了,它用於設置ImageView的大小,我們在xml文件中設置ImageView的時候,如果指定了固定的寬高,那麼onMeasur()方法中測量的大小就是固定的寬高大小;如果是包裹內容,那麼就需要進一步的計算。android:scaleType="matrix"
. */ MATRIX (0), /** * Scale the image using {@link Matrix.ScaleToFit#FILL}. * From XML, use this syntax:android:scaleType="fitXY"
. */ FIT_XY (1), /** * Scale the image using {@link Matrix.ScaleToFit#START}. * From XML, use this syntax:android:scaleType="fitStart"
. */ FIT_START (2), /** * Scale the image using {@link Matrix.ScaleToFit#CENTER}. * From XML, use this syntax: *android:scaleType="fitCenter"
. */ FIT_CENTER (3), /** * Scale the image using {@link Matrix.ScaleToFit#END}. * From XML, use this syntax:android:scaleType="fitEnd"
. */ FIT_END (4), /** * Center the image in the view, but perform no scaling. * From XML, use this syntax:android:scaleType="center"
. */ 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:android:scaleType="centerCrop"
. */ 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:android:scaleType="centerInside"
. */ CENTER_INSIDE (7); ScaleType(int ni) { nativeInt = ni; } final int nativeInt; }
@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;
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文件中設置。但是這樣直接使用會有一個隱形的弊端,如果顯示的圖片過多或者單張顯示的圖片像素過大,就容易出現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異常。
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介紹 這篇文章。
0x00 序隨著移動安全越來越火,各種調試工具也都層出不窮,但因為環境和需求的不同,並沒有工具是萬能的。另外工具是死的,人是活的,如果能搞懂工具的原理再結合上自身的經驗,
適配器模式的定義是:將一個類的接口,轉換成客戶期望的另一個接口,適配器讓原本接口不兼容的類可以作無間。看下以下兩張圖就更清楚了 適配器可以實現接口的解耦,如果過一段時
工作幾年發現自己沒留下啥東西,天天開發開發,沒總結過。 這次想總結下。故而寫這個系列的博客。希望對廣大的開發者有所幫助。OK 首先先分析下 框架的作用,以及框架所應擁有的
前言本文的中文注釋代碼demo更新在我的github上。SDWebImage是一個十分有名的Objective-C第三方開源框架,作用是: Asynchronous im