首页 > 代码库 > 解析View的getDrawingCache方法

解析View的getDrawingCache方法

1. View 的getDrawingCache方法

  有时候需要将某个view的内容以图片的方式保存下来,感觉就和截图差不多,可以使用View 的getDrawingCache方法,返回一个Bitmap对象。

2. View的getDrawingCache的具体实现

  查看View的getDrawingCache()方法

/** 
 * <p>Calling this method is equivalent to calling <code>getDrawingCache(false)</code>.</p> 
 * 
 * @return A non-scaled bitmap representing this view or null if cache is disabled. 
 * 
 * @see #getDrawingCache(boolean) 
 */ 
public Bitmap getDrawingCache() { 
    return getDrawingCache(false); 
}

  看代码继续调用了getDrawingCache(false)方法,继续查看getDrawingCache(false)方法。

/** 
 * <p>Returns the bitmap in which this view drawing is cached. The returned bitmap 
 * is null when caching is disabled. If caching is enabled and the cache is not ready, 
 * this method will create it. Calling {@link #draw(android.graphics.Canvas)} will not 
 * draw from the cache when the cache is enabled. To benefit from the cache, you must 
 * request the drawing cache by calling this method and draw it on screen if the 
 * returned bitmap is not null.</p> 
 * 
 * <p>Note about auto scaling in compatibility mode: When auto scaling is not enabled, 
 * this method will create a bitmap of the same size as this view. Because this bitmap 
 * will be drawn scaled by the parent ViewGroup, the result on screen might show 
 * scaling artifacts. To avoid such artifacts, you should call this method by setting 
 * the auto scaling to true. Doing so, however, will generate a bitmap of a different 
 * size than the view. This implies that your application must be able to handle this 
 * size.</p> 
 * 
 * @param autoScale Indicates whether the generated bitmap should be scaled based on 
 *        the current density of the screen when the application is in compatibility 
 *        mode. 
 * 
 * @return A bitmap representing this view or null if cache is disabled. 
 * 
 * @see #setDrawingCacheEnabled(boolean) 
 * @see #isDrawingCacheEnabled() 
 * @see #buildDrawingCache(boolean) 
 * @see #destroyDrawingCache() 
 */ 
public Bitmap getDrawingCache(boolean autoScale) { 
    if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) { 
        return null; 
    } 
    if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED) { 
        buildDrawingCache(autoScale); 
    } 
    return autoScale ? mDrawingCache : mUnscaledDrawingCache; 
}

  查看getDrawingCache(false)方法,如果该视图的标志是WILL_NOT_CACHE_DEAWING(表示该view没有任何绘图缓存)则直接返回null,如果视图的标志是DRWING_CACHE_ENABLED(表示该view将自己的绘图缓存成一个bitmap),则调用buildDrawingCache(autoScale)方法。

  因为传递过来的autoScale为false,则返回的Bitmap是mUnscaledDrawingCache。

  查看buildDrawingCache(autoScale)方法:

/** 
 * <p>Forces the drawing cache to be built if the drawing cache is invalid.</p> 
 * 
 * <p>If you call {@link #buildDrawingCache()} manually without calling 
 * {@link #setDrawingCacheEnabled(boolean) setDrawingCacheEnabled(true)}, you 
 * should cleanup the cache by calling {@link #destroyDrawingCache()} afterwards.</p> 
 * 
 * <p>Note about auto scaling in compatibility mode: When auto scaling is not enabled, 
 * this method will create a bitmap of the same size as this view. Because this bitmap 
 * will be drawn scaled by the parent ViewGroup, the result on screen might show 
 * scaling artifacts. To avoid such artifacts, you should call this method by setting 
 * the auto scaling to true. Doing so, however, will generate a bitmap of a different 
 * size than the view. This implies that your application must be able to handle this 
 * size.</p> 
 * 
 * <p>You should avoid calling this method when hardware acceleration is enabled. If 
 * you do not need the drawing cache bitmap, calling this method will increase memory 
 * usage and cause the view to be rendered in software once, thus negatively impacting 
 * performance.</p> 
 * 
 * @see #getDrawingCache() 
 * @see #destroyDrawingCache() 
 */ 
public void buildDrawingCache(boolean autoScale) { 
    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ? 
            mDrawingCache == null : mUnscaledDrawingCache == null)) { 
        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, 
                    "buildDrawingCache/SW Layer for " + getClass().getSimpleName()); 
        } 
        try { 
            buildDrawingCacheImpl(autoScale); 
        } finally { 
            Trace.traceEnd(Trace.TRACE_TAG_VIEW); 
        } 
    } 
}

  如果mPrivateFlags与PFLAG_DRAWING_CACHE_VALID与运算为0,或者mUnscaledDrawingCache为null,则调用buildDrawingCacheImpl(autoScale)方法。

查看buildDrawingCacheImpl(autoScale)方法,

/** 
 * private, internal implementation of buildDrawingCache, used to enable tracing 
 */

 

private void buildDrawingCacheImpl(boolean autoScale) {
    mCachingFailed = false;

    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;

    final long projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4);
    final long drawingCacheSize =
            ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize();
    if (width <= 0 || height <= 0 || projectedBitmapSize > drawingCacheSize) {
        if (width > 0 && height > 0) {
            Log.w(VIEW_LOG_TAG, getClass().getSimpleName() + " not displayed because it is"
                    + " too large to fit into a software layer (or drawing cache), needs "
                    + projectedBitmapSize + " bytes, only "
                    + drawingCacheSize + " available");
        }
        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:
                case DRAWING_CACHE_QUALITY_LOW:
                case DRAWING_CACHE_QUALITY_HIGH:
                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(mResources.getDisplayMetrics(),
                    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 |= PFLAG_DRAWN;
    if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated ||
            mLayerType != LAYER_TYPE_NONE) {
        mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID;
    }

    // Fast path for layouts with no backgrounds
    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
        dispatchDraw(canvas);
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().draw(canvas);
        }
    } else {
        draw(canvas);
    }

    canvas.restoreToCount(restoreCount);
    canvas.setBitmap(null);

    if (attachInfo != null) {
        // Restore the cached Canvas for our siblings
        attachInfo.mCanvas = canvas;
    }
}


  代码中AttachInfo类是连接到他的父window时给view的一组信息。

  参数autoScale的值为false,可以忽略一些if判断语句。

  方法分为三步:

    第一步:得到view的宽width与高height。

int width = mRight - mLeft;
int height = mBottom - mTop;

    第二步,生成bitmap。

bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),width, height, quality);
bitmap.setDensity(getResources().getDisplayMetrics().densityDpi);

  use32BitCache数据代表的是view是否需要使用32-bit绘图缓存,当window为半透明的时候,使用32位绘图缓存。

    第三步,绘制canvas。

canvas.setBitmap(bitmap);

canvas = new Canvas(bitmap);

draw(canvas);

3. 自定义view的getDrawingCache方法

  有时候自定义的view虽然继承View,但是调用View的getDrawingCache()方法的时候会出现一些问题,返回的bitmap为null,这个时候就需要自己写一个getDrawingCache方法,可以参考buildDrawingCacheImpl方法去实现,实现如下:

public Bitmap getBitmap() { 
    Bitmap bitmap = null; 
    int width = getRight() - getLeft(); 
    int height = getBottom() - getTop(); 
    final boolean opaque = getDrawingCacheBackgroundColor() != 0 || isOpaque(); 
    Bitmap.Config quality; 
    if (!opaque) { 
        switch (getDrawingCacheQuality()) { 
            case DRAWING_CACHE_QUALITY_AUTO: 
            case DRAWING_CACHE_QUALITY_LOW: 
            case DRAWING_CACHE_QUALITY_HIGH: 
            default: 
                quality = Bitmap.Config.ARGB_8888; 
                break; 
        } 
    } else { 
        quality = Bitmap.Config.RGB_565; 
    } 
    if (opaque) bitmap.setHasAlpha(false); 
    bitmap = Bitmap.createBitmap(getResources().getDisplayMetrics(), 
            width, height, quality); 
    bitmap.setDensity(getResources().getDisplayMetrics().densityDpi); 
    boolean clear = getDrawingCacheBackgroundColor() != 0; 
    Canvas canvas = new Canvas(bitmap); 
    if (clear) { 
        bitmap.eraseColor(getDrawingCacheBackgroundColor()); 
    } 
    computeScroll(); 
    final int restoreCount = canvas.save(); 
    canvas.translate(-getScrollX(), -getScrollY()); 
    draw(canvas); 
    canvas.restoreToCount(restoreCount); 
    canvas.setBitmap(null); 
    return bitmap; 
}

 

解析View的getDrawingCache方法