這兩天幫同事解決一個問題;
View.getDrawingCache獲得資料始終為null,但是在某些裝置上並不為null,糾結夠 久啊,網上說了一些原因:
1) (mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING 這個值為true
2) (mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED 為false,buildDrawingCache沒執行
3) buildDrawingCache執行失敗
這些在源碼中都可以看到,在獲得快取資料的時候,跟背景色(drawingCacheBackgroundColor),透明度isOpaque,use32BitCache這些有關係,看是細看這些東西都是表面的,是系統在buildDrawingCache的時候,根據View或都系統設定而來的;有些屬性是不能更改的;這樣一來當一個固定大小的View在不同的裝置上產生的圖片就可能有所不同,我同事這邊存在的問題就是,設定View的固定大小為1360*768,而我的裝置解析度為1024*600,而源碼裡可以看到這樣代碼:
if (width <= 0 || height <= 0 || // Projected bitmap size in bytes (width * height * (opaque && !use32BitCache ? 2 : 4) > ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize())) { destroyDrawingCache(); mCachingFailed = true; return; }
當我們在buildDrawingCache的時候,系統給了我們預設最大的DrawingCacheSize為螢幕寬*高*4;而我的View的CacheSize大小超過了某些裝置預設值,就會導致獲得為空白;開始想著用反射的方法去改變這些屬性,或者設定背景顏色來改變圖片品質,這樣一來CacheSize大小 就可能會變小,但是這樣始終不能達到效果;
最終解決方案:
查看系統buildDrawingCache方法可以看到:
public void buildDrawingCache(boolean autoScale) { if ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || (autoScale ? mDrawingCache == null : mUnscaledDrawingCache == null)) { mCachingFailed = false; if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.BUILD_CACHE); } int width = mRight - mLeft; int height = mBottom - mTop; final AttachInfo attachInfo = mAttachInfo; final boolean scalingRequired = attachInfo != null && attachInfo.mScalingRequired; if (autoScale && scalingRequired) { width = (int) ((width * attachInfo.mApplicationScale) + 0.5f); height = (int) ((height * attachInfo.mApplicationScale) + 0.5f); } final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor; final boolean opaque = drawingCacheBackgroundColor != 0 || isOpaque(); final boolean use32BitCache = attachInfo != null && attachInfo.mUse32BitDrawingCache; if (width <= 0 || height <= 0 || // Projected bitmap size in bytes (width * height * (opaque && !use32BitCache ? 2 : 4) > ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize())) { destroyDrawingCache(); mCachingFailed = true; return; } boolean clear = true; Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache; if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) { Bitmap.Config quality; if (!opaque) { // Never pick ARGB_4444 because it looks awful // Keep the DRAWING_CACHE_QUALITY_LOW flag just in case switch (mViewFlags & DRAWING_CACHE_QUALITY_MASK) { case DRAWING_CACHE_QUALITY_AUTO: quality = Bitmap.Config.ARGB_8888; break; case DRAWING_CACHE_QUALITY_LOW: quality = Bitmap.Config.ARGB_8888; break; case DRAWING_CACHE_QUALITY_HIGH: quality = Bitmap.Config.ARGB_8888; break; default: quality = Bitmap.Config.ARGB_8888; break; } } else { // Optimization for translucent windows // If the window is translucent, use a 32 bits bitmap to benefit from memcpy() quality = use32BitCache ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; } // Try to cleanup memory if (bitmap != null) bitmap.recycle(); try { bitmap = Bitmap.createBitmap(width, height, quality); bitmap.setDensity(getResources().getDisplayMetrics().densityDpi); if (autoScale) { mDrawingCache = bitmap; } else { mUnscaledDrawingCache = bitmap; } if (opaque && use32BitCache) bitmap.setHasAlpha(false); } catch (OutOfMemoryError e) { // If there is not enough memory to create the bitmap cache, just // ignore the issue as bitmap caches are not required to draw the // view hierarchy if (autoScale) { mDrawingCache = null; } else { mUnscaledDrawingCache = null; } mCachingFailed = true; return; } clear = drawingCacheBackgroundColor != 0; } Canvas canvas; if (attachInfo != null) { canvas = attachInfo.mCanvas; if (canvas == null) { canvas = new Canvas(); } canvas.setBitmap(bitmap); // Temporarily clobber the cached Canvas in case one of our children // is also using a drawing cache. Without this, the children would // steal the canvas by attaching their own bitmap to it and bad, bad // thing would happen (invisible views, corrupted drawings, etc.) attachInfo.mCanvas = null; } else { // This case should hopefully never or seldom happen canvas = new Canvas(bitmap); } if (clear) { bitmap.eraseColor(drawingCacheBackgroundColor); } computeScroll(); final int restoreCount = canvas.save(); if (autoScale && scalingRequired) { final float scale = attachInfo.mApplicationScale; canvas.scale(scale, scale); } canvas.translate(-mScrollX, -mScrollY); mPrivateFlags |= DRAWN; if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated || mLayerType != LAYER_TYPE_NONE) { mPrivateFlags |= DRAWING_CACHE_VALID; } // Fast path for layouts with no backgrounds if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW); } mPrivateFlags &= ~DIRTY_MASK; dispatchDraw(canvas); } else { draw(canvas); } canvas.restoreToCount(restoreCount); canvas.setBitmap(null); if (attachInfo != null) { // Restore the cached Canvas for our siblings attachInfo.mCanvas = canvas; } } } /** * Create a snapshot of the view into a bitmap. We should probably make * some form of this public, but should think about the API. */ Bitmap createSnapshot(Bitmap.Config quality, int backgroundColor, boolean skipChildren) { int width = mRight - mLeft; int height = mBottom - mTop; final AttachInfo attachInfo = mAttachInfo; final float scale = attachInfo != null ? attachInfo.mApplicationScale : 1.0f; width = (int) ((width * scale) + 0.5f); height = (int) ((height * scale) + 0.5f); Bitmap bitmap = Bitmap.createBitmap(width > 0 ? width : 1, height > 0 ? height : 1, quality); if (bitmap == null) { throw new OutOfMemoryError(); } Resources resources = getResources(); if (resources != null) { bitmap.setDensity(resources.getDisplayMetrics().densityDpi); } Canvas canvas; if (attachInfo != null) { canvas = attachInfo.mCanvas; if (canvas == null) { canvas = new Canvas(); } canvas.setBitmap(bitmap); // Temporarily clobber the cached Canvas in case one of our children // is also using a drawing cache. Without this, the children would // steal the canvas by attaching their own bitmap to it and bad, bad // things would happen (invisible views, corrupted drawings, etc.) attachInfo.mCanvas = null; } else { // This case should hopefully never or seldom happen canvas = new Canvas(bitmap); } if ((backgroundColor & 0xff000000) != 0) { bitmap.eraseColor(backgroundColor); } computeScroll(); final int restoreCount = canvas.save(); canvas.scale(scale, scale); canvas.translate(-mScrollX, -mScrollY); // Temporarily remove the dirty mask int flags = mPrivateFlags; mPrivateFlags &= ~DIRTY_MASK; // Fast path for layouts with no backgrounds if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { dispatchDraw(canvas); } else { draw(canvas); } mPrivateFlags = flags; canvas.restoreToCount(restoreCount); canvas.setBitmap(null); if (attachInfo != null) { // Restore the cached Canvas for our siblings attachInfo.mCanvas = canvas; } return bitmap; }
產生DrawingCache的過程貌似就是利用獲得View的Canvas然後畫到bitmap上,直接返回對應 的bitmap,這樣一來,就是我們用getDrawingCache獲得的bitmap;跟我們直接將View畫到bitmap貌似區別 不是很大,受啟發;如下:
自己產生Bitmap;
public static Bitmap loadBitmapFromView(View v, boolean isParemt) {if (v == null) {return null;}Bitmap screenshot;screenshot = Bitmap.createBitmap(v.getWidth(), v.getHeight(), HDConstantSet.BITMAP_QUALITY);Canvas c = new Canvas(screenshot);v.draw(c);return screenshot;}
這樣也就將View產生了我們需要的bitmap了,但是有些情況下:比如ViewPager在用getDrawingCache和我自己產生的Bitmap時候,會有區別,ViewPager第一屏是正常的,滑動到第二個螢幕的時候,我手動產生的Bitmap不見了,而系統getDrawingCache方法產生 的Bitmap是可見的,鬱悶,,,詳細看了一下系統buildDrawingCache訪求,發現在Canvas繪製Bitmap之後,多了一個步驟:
computeScroll(); final int restoreCount = canvas.save(); canvas.scale(scale, scale); canvas.translate(-mScrollX, -mScrollY);
很明顯,系統Canvas,對預設位置進行了移動,即啟發:我們在用哥滑動View獲得它的Bitmap時候,獲得的是整個View的地區(包括隱藏的),如果想得到目前範圍,需要重新置放到當前可顯示的地區;自己的代碼修改:
public static Bitmap loadBitmapFromView(View v, boolean isParemt) {if (v == null) {return null;}Bitmap screenshot;screenshot = Bitmap.createBitmap(v.getWidth(), v.getHeight(), HDConstantSet.BITMAP_QUALITY);Canvas c = new Canvas(screenshot);c.translate(-v.getScrollX(), -v.getScrollY());v.draw(c);return screenshot;}
完美解決用自己產生 的Bitmap替換系統的getDrawingCache()方法;
當然系統getDrawingCache()考慮的因素很多,這一些我們也可以自己直接定義,比如透明磁,大小 ,圖片品質等;最重要就是
c.translate(-v.getScrollX(), -v.getScrollY());
這行一代碼需要理解 ;
上面個人見解,如有理解不對的地方,大神留言指導啊........