如何获取View的Bitmap

如何获取View的Bitmap

来源 https://www.jianshu.com/p/d22aa98f6e38

我们这里份两种情况进行讨论。

第一种情况,直接从布局文件生成Bitmap

举个例子。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextView android:id="@+id/tvNumber" android:layout_width="60dp" android:layout_height="60dp" android:background="@color/colorPrimary" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout> 

在这个例子中,布局文件中有一个TextView,我们每次在生成Bitmap之前,改变一下TextView的text。然后把生成的Bitmap设置给一个ImageView做背景。

//布局文件对应的view
private View view;
private TextView tvNumber; private int number = 0; //用来显示生成的bitmap private ImageView ivTop; private Button btnGetBitmap; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test_get_drawing_caching); //首先加载布局文件 view = LayoutInflater.from(this).inflate(R.layout.layout_drawing_cache, null); tvNumber = view.findViewById(R.id.tvNumber); ivTop = findViewById(R.id.ivTop); btnGetBitmap = findViewById(R.id.btnGetBitmap); //点击事件 btnGetBitmap.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { number++; //每次生成bitmap之前改变一下tvNumber的text tvNumber.setText(String.valueOf(number)); ivTop.setBackgroundDrawable(new BitmapDrawable(copyByCanvas(view))); } }); } //... 

第一种方法

    /**
     * 通过canvas复制view的bitmap
     *
     * @param view
     * @return
     */
    private Bitmap copyByCanvas(View view) { view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); Bitmap bp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bp); view.draw(canvas); canvas.save(); return bp; } 

第二种方法

/**
     * 通过drawingCache获取bitmap
     *
     * @param view
     * @return
     */
    private Bitmap convertViewToBitmap(View view) { view.setDrawingCacheEnabled(true); view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); //注释1处 Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache()); //如果不调用这个方法,每次生成的bitmap相同 view.setDrawingCacheEnabled(false); return bitmap; } 

在上面方法的注释1处要注意一下,这里我们没有直接返回view.getDrawingCache()方法返回的bitmap,也就是我们 没有这样写

/**
     * 通过drawingCache获取bitmap
     *
     * @param view
     * @return
     */
    private Bitmap convertViewToBitmap(View view) { view.setDrawingCacheEnabled(true); view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); Bitmap mBitmap = view.getDrawingCache(); //如果不调用这个方法,每次生成的bitmap相同 view.setDrawingCacheEnabled(false); return bitmap; } 

因为发现这样写的话,每次获取的bitmap都是不起作用

Bitmap bitmap = convertViewToBitmap2(tvNumber); ivTop.setBackgroundDrawable(new BitmapDrawable(bitmap)); 

使用获取的bitmap构建一个BitmapDrawable对象作为ImageView的背景不起作用 。

有的机型会给出一个警告

扫描二维码关注公众号,回复: 11461959 查看本文章
BitmapDrawable: Canvas: trying to use a recycled bitmap

而有的机型会直接抛出一个运行时异常

java.lang.RuntimeException: 
Canvas: trying to use a recycled bitmap android.graphics.Bitmap@31c9a68
        at android.graphics.BaseCanvas.throwIfCannotDraw(BaseCanvas.java:62) at android.view.DisplayListCanvas.throwIfCannotDraw(DisplayListCanvas.java:226) at android.view.RecordingCanvas.drawBitmap(RecordingCanvas.java:98) at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:545) at android.view.View.getDrawableRenderNode(View.java:20463) at android.view.View.drawBackground(View.java:20399) at android.view.View.draw(View.java:20198) //... 

接下来我们先分析一下原因。

首先看下

Bitmap mBitmap = view.getDrawingCache();

我们的mBitmap引用指向了view.getDrawingCache()方法返回的对象。

View 的getDrawingCache方法

@Deprecated
    public Bitmap getDrawingCache() { //调用重载方法 return getDrawingCache(false); } 
@Deprecated
    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) { //注释1处 buildDrawingCache(autoScale); } return autoScale ? mDrawingCache : mUnscaledDrawingCache; } 

首先在注释1处,会根据传入的autoScale变量生成bitmap对象。如果autoScale为false,则将生成的bitmap对象赋值给mUnscaledDrawingCache。

View的buildDrawingCache方法精简版

 @Deprecated
    public void buildDrawingCache(boolean autoScale) { //... buildDrawingCacheImpl(autoScale); } 

View的buildDrawingCacheImpl方法精简版

private void buildDrawingCacheImpl(boolean autoScale) { //... try { bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(), width, height, quality); bitmap.setDensity(getResources().getDisplayMetrics().densityDpi); //根据传入的autoScale决定将生成的bitmap对象赋值给mDrawingCache还是mUnscaledDrawingCache if (autoScale) { mDrawingCache = bitmap; } else { mUnscaledDrawingCache = bitmap; } if (opaque && use32BitCache) bitmap.setHasAlpha(false); } //... } 

到现在我们应该知道了,在这个例子中,最终mBitmap和mUnscaledDrawingCache指向了同一个对象。

然后在生成了bitmap对象以后,我们调用了

view.setDrawingCacheEnabled(false);

View的setDrawingCacheEnabled方法

@Deprecated
    public void setDrawingCacheEnabled(boolean enabled) { mCachingFailed = false; //注释1处 setFlags(enabled ? DRAWING_CACHE_ENABLED : 0, DRAWING_CACHE_ENABLED); } 

在上面的注释1处,我们传入的参数enabled是false,所以我们调用setFlags方法最终传入的参数是 setFlags(0, DRAWING_CACHE_ENABLED);
在这种情况下,setFlags方法内部会调用一个分支判断

void setFlags(int flags, int mask) { //... if ((changed & DRAWING_CACHE_ENABLED) != 0) { //注释1处 destroyDrawingCache(); mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; invalidateParentCaches(); } //... } 

在注释1处会调用destroyDrawingCache方法

@Deprecated
    public void destroyDrawingCache() { if (mDrawingCache != null) { mDrawingCache.recycle(); mDrawingCache = null; } //mUnscaledDrawingCache不为null if (mUnscaledDrawingCache != null) { //注释1处 mUnscaledDrawingCache.recycle(); mUnscaledDrawingCache = null; } } 

在注释1处,将我们刚生成的mUnscaledDrawingCache所指向的bitmap对象给回收了。在这里我们可以认为将bitmap对象回收就获取不到bitmap对象上的像素信息了,并且不会绘制任何信息。

然后我们使用返回的bitmap对象构建了一个BitmapDrawable对象,并将BitmapDrawable对象设置为ImageView的背景。

 ivTop.setBackgroundDrawable(new BitmapDrawable(bitmap)); 
@Deprecated
public void setBackgroundDrawable(Drawable background) { //... requestLayout(); mBackgroundSizeChanged = true; //注释1处 invalidate(true); } 

在注释1处调用了invalidate方法,这个方法最终会导致view 重新绘制。
View的draw方法

public void draw(Canvas canvas) { //... drawBackground(canvas); } 

View的drawBackground方法

private void drawBackground(Canvas canvas) { //... final Drawable background = mBackground; //注释1处,我们传入的是BitmapDrawable background.draw(canvas); } 

在上面的注释1处,会调用BitmapDrawable的draw方法

@Override
public void draw(Canvas canvas) { //... //这里就是报异常的代码 canvas.drawBitmap(bitmap, null, mDstRect, paint); } 

到这里,我们知道了不能使用一个被回收的bitmap的原因所在。接下来轻松一点,看看获取View的bitmap的第二种情况。

第二种情况,在获取Bitmap之前,View显示在屏幕上了已经

这种情况下就比较简单了。不需要view的measure和layout过程了。

方法一

/**
     * 通过drawingCache获取bitmap
     *
     * @param view
     * @return
     */
    private Bitmap convertViewToBitmap2(View view) { view.setDrawingCacheEnabled(true); Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache()); //如果不调用这个方法,每次生成的bitmap相同 view.setDrawingCacheEnabled(false); return bitmap; } 

方法二

/**
     * 通过canvas复制view的bitmap
     *
     * @param view
     * @return
     */
    private Bitmap copyByCanvas2(View view) { int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); Log.d(TAG, "copyByCanvas: width=" + width + ",height=" + height); Bitmap bp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bp); view.draw(canvas); canvas.save(); return bp; } 

参考链接:
[1]:两种获取view的bitmap的方法
[2]:drawingcache解析 通过view的绘制缓存得到bitmap,从而实现view内容截图


================ End

猜你喜欢

转载自www.cnblogs.com/lsgxeva/p/13394403.html