在Android中将视图转换为位图而不显示它?

157

我将尝试解释我需要做的事情。

我有三个独立的屏幕,称为A、B、C。另外还有一个称为HomeScreen的屏幕,所有3个屏幕的位图应该以画廊视图显示,并且用户可以选择要进入哪个视图。

我已经能够获得所有3个屏幕的位图,并通过在HomeScreen Activity中放置所有代码来显示它们的画廊视图。现在,这使得代码变得非常复杂,我想要简化它。

那么,我可以从HomeScreen调用另一个Activity,而不必显示它,只需获取该屏幕的位图即可。例如,我只是调用HomeScreen并调用Activity A、B、C,而不显示A、B、C中的任何一个Activity。它只会通过getDrawingCache()方法提供该屏幕的位图。然后我们可以在HomeScreen中以画廊视图显示这些位图。

我希望我已经非常清楚地解释了问题。

请让我知道这是否真的可行。


1
我不是很确定,但我觉得你做不到那个。问题在于活动是要展示给用户的。你可以启动活动然后立即隐藏它,但活动仍然会在用户面前显示一瞬间。它展示的时间足够被注意到,因此屏幕闪烁几次会让应用看起来不专业。不过,也可能有一种命令可以启动活动而不显示它;如果存在的话,我就不知道。 - Steve Haley
6
实际上,我能够做到这件事。 - sunil
哦,你怎么能称之为活动而不显示它呢?我可以将当前活动的布局作为模板,在向其中提供不同内容的同时生成位图吗? - zionpi
请在此帖子中检查答案,我找到了某种解决方案:http://stackoverflow.com/questions/36424381/save-multiple-textviews-as-image-of-large-resolution/36455437#36455437 - Wackaloon
以上回答对我都没有用,只有这个有效 https://dev59.com/mm035IYBdhLWcg3wPdgS#26086145 - sum20156
13个回答

239

有一种方法可以做到这一点。你需要创建一个Bitmap和Canvas对象,然后调用view.draw(canvas)。

下面是示例代码:

public static Bitmap loadBitmapFromView(View v) {
    Bitmap b = Bitmap.createBitmap( v.getLayoutParams().width, v.getLayoutParams().height, Bitmap.Config.ARGB_8888);                
    Canvas c = new Canvas(b);
    v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
    v.draw(c);
    return b;
}

如果视图之前没有显示过,则其大小将为零。可以按照以下方式测量其大小:

if (v.getMeasuredHeight() <= 0) {
    v.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    Bitmap b = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
    Canvas c = new Canvas(b);
    v.layout(0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());
    v.draw(c);
    return b;
}

编辑:根据此帖子WRAP_CONTENT作为值传递给makeMeasureSpec()没有任何好处(虽然对于某些视图类它确实有效),建议的方法是:

// Either this
int specWidth = MeasureSpec.makeMeasureSpec(parentWidth, MeasureSpec.AT_MOST);
// Or this
int specWidth = MeasureSpec.makeMeasureSpec(0 /* any */, MeasureSpec.UNSPECIFIED);
view.measure(specWidth, specWidth);
int questionWidth = view.getMeasuredWidth();

1
我尝试了这个,但是我只得到一个半透明的黑盒子。我需要在视图上做些什么来准备位图绘制吗? - Bobbake4
4
为了使其正确运行,我实际上需要将代码更改为 v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());,但感谢您提供的代码 :) - triad
4
现在的代码已经能够正常工作了,我必须使用v.getWidth()而不是v.getLayoutParams().width来获取宽度,高度也是类似的。 - David Manpearl
1
我使用了 v.measure(0, 0); v.getMeasuredWidth(); v.getMeasuredHeight(); - Brais Gabin
7
Bitmap b = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888); 程序运行更顺畅。 - Pierre
显示剩余7条评论

36

这是我的解决方案:

public static Bitmap getBitmapFromView(View view) {
    Bitmap returnedBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(returnedBitmap);
    Drawable bgDrawable =view.getBackground();
    if (bgDrawable!=null) 
        bgDrawable.draw(canvas);
    else 
        canvas.drawColor(Color.WHITE);
    view.draw(canvas);
    return returnedBitmap;
}

享受吧 :)


谢谢。如果高度超过某个值,我在一些设备上遇到了问题。虽然我还没有完全测试,但这似乎解决了这个问题。 - B-M-F
java.lang.IllegalArgumentException: 宽度和高度必须大于0。这里没有什么好享受的 :) - undefined

30

我知道这可能是一个陈旧的问题,但我在尝试使用这些解决方案时遇到了问题。具体来说,我发现如果对视图进行了任何更改,且在充气后那些更改将不会合并到渲染位图中。

以下方法最终适用于我的情况,但有一点要注意。在调用getViewBitmap(View)之前,我填充了我的视图,并请求使用已知的尺寸进行布局。这是必要的,因为我的视图布局会使其大小为零,直到放置内容为止。

View view = LayoutInflater.from(context).inflate(layoutID, null);
//Do some stuff to the view, like add an ImageView, etc.
view.layout(0, 0, width, height);

Bitmap getViewBitmap(View view)
{
    //Get the dimensions of the view so we can re-layout the view at its current size
    //and create a bitmap of the same size 
    int width = view.getWidth();
    int height = view.getHeight();

    int measuredWidth = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
    int measuredHeight = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);

    //Cause the view to re-layout
    view.measure(measuredWidth, measuredHeight);
    view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());

    //Create a bitmap backed Canvas to draw the view into
    Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    Canvas c = new Canvas(b);

    //Now that the view is laid out and we have a canvas, ask the view to draw itself into the canvas
    view.draw(c);

    return b;
}

对我来说,“灵魔汁”的秘密就在这里找到了:https://groups.google.com/forum/#!topic/android-developers/BxIBAOeTA1Q

干杯,

Levi


谢谢!似乎在对布局进行任何更改后,必须调用measure和requestLayout才能显示它们。 - TheIT
1
感谢这个解决方案!我有同样的问题。在填充视图之前,我使用了 measure()layout(),导致结果很奇怪。把这些调用移到 createBitmap() 上面,问题就解决了! - Sven Jacobs
如何确定视图的宽度和高度? - undefined

24

试一下这个:

/**
 * Draw the view into a bitmap.
 */
public static Bitmap getViewBitmap(View v) {
    v.clearFocus();
    v.setPressed(false);

    boolean willNotCache = v.willNotCacheDrawing();
    v.setWillNotCacheDrawing(false);

    // Reset the drawing cache background color to fully transparent
    // for the duration of this operation
    int color = v.getDrawingCacheBackgroundColor();
    v.setDrawingCacheBackgroundColor(0);

    if (color != 0) {
        v.destroyDrawingCache();
    }
    v.buildDrawingCache();
    Bitmap cacheBitmap = v.getDrawingCache();
    if (cacheBitmap == null) {
        Log.e(TAG, "failed getViewBitmap(" + v + ")", new RuntimeException());
        return null;
    }

    Bitmap bitmap = Bitmap.createBitmap(cacheBitmap);

    // Restore the view
    v.destroyDrawingCache();
    v.setWillNotCacheDrawing(willNotCache);
    v.setDrawingCacheBackgroundColor(color);

    return bitmap;
}

我该如何在我的主活动类中使用它? - Si8
1
已过时 - Ch Vas

21

21
如果视图未在布局中呈现,这将无法工作。错误:“IllegalStateException: View needs to be laid out before calling drawToBitmap()”。 - Val
易于操作且简单。谢谢兄弟。 - Nabeel
1
如果你调用myView.layout(myView.left, myView.top, myView.measuredWidth, myView.measuredHeight),它能够工作。在此之前,请确保调用myView.measure(MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)) - FlavienSi
@FlavienSi 圆形视图始终为黑色背景。您知道如何更改它吗? - billy gates
奇怪的回答,有什么意义呢?你甚至读了问题吗? - undefined

5

布局或视图转换为位图:

 private Bitmap createBitmapFromLayout(View tv) {      
    int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    tv.measure(spec, spec);
    tv.layout(0, 0, tv.getMeasuredWidth(), tv.getMeasuredHeight());
    Bitmap b = Bitmap.createBitmap(tv.getMeasuredWidth(), tv.getMeasuredWidth(),
            Bitmap.Config.ARGB_8888);
    Canvas c = new Canvas(b);
    c.translate((-tv.getScrollX()), (-tv.getScrollY()));
    tv.draw(c);
    return b;
}

方法调用:

Bitmap src = createBitmapFromLayout(View.inflate(this, R.layout.sample, null)/* or pass your view object*/);

感谢您的翻译!它帮助我剪辑了视图中的一个区域。 - CoolMind

4
我有类似的需求,并根据提供的代码开发了一些东西,特别是由@Azhagthott提供的代码和全新的androidx.core.view.drawToBitmap。希望这对你也有帮助。
import androidx.core.view.drawToBitmap

val view = MyCustomView(context)
view.measure(
    View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.AT_MOST),
    View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.AT_MOST)
)
view.layout(0, 0, width, height)
val bitmap = view.drawToBitmap()

它的宽度和高度是固定的。 - undefined

4
我认为这样会更好一些:
/**
 * draws the view's content to a bitmap. code initially based on :
 * http://nadavfima.com/android-snippet-inflate-a-layout-draw-to-a-bitmap/
 */
@Nullable
public static Bitmap drawToBitmap(final View viewToDrawFrom, int width, int height) {
    boolean wasDrawingCacheEnabled = viewToDrawFrom.isDrawingCacheEnabled();
    if (!wasDrawingCacheEnabled)
        viewToDrawFrom.setDrawingCacheEnabled(true);
    if (width <= 0 || height <= 0) {
        if (viewToDrawFrom.getWidth() <= 0 || viewToDrawFrom.getHeight() <= 0) {
            viewToDrawFrom.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
            width = viewToDrawFrom.getMeasuredWidth();
            height = viewToDrawFrom.getMeasuredHeight();
        }
        if (width <= 0 || height <= 0) {
            final Bitmap bmp = viewToDrawFrom.getDrawingCache();
            final Bitmap result = bmp == null ? null : Bitmap.createBitmap(bmp);
            if (!wasDrawingCacheEnabled)
                viewToDrawFrom.setDrawingCacheEnabled(false);
            return result;
        }
        viewToDrawFrom.layout(0, 0, width, height);
    } else {
        viewToDrawFrom.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
        viewToDrawFrom.layout(0, 0, viewToDrawFrom.getMeasuredWidth(), viewToDrawFrom.getMeasuredHeight());
    }
    final Bitmap drawingCache = viewToDrawFrom.getDrawingCache();
    final Bitmap bmp = ThumbnailUtils.extractThumbnail(drawingCache, width, height);
    final Bitmap result = bmp == null || bmp != drawingCache ? bmp : Bitmap.createBitmap(bmp);
    if (!wasDrawingCacheEnabled)
        viewToDrawFrom.setDrawingCacheEnabled(false);
    return result;
}

使用以上代码,如果想要使用视图自身的位图(宽度和高度设为0),则不必指定位图的大小。
此外,如果您希望将特殊视图(例如SurfaceView、Surface或Window)转换为位图,应考虑使用PixelCopy类。但需要API 24及以上版本支持。在此之前我不知道该如何实现。请参考PixelCopy

有什么想法,位图中没有添加TextView。只添加了ImageViews。 - Khemraj Sharma
@Khemraj 我不明白这个问题。 - android developer
我的TextView不在位图中是我的错,因为我应用了浅色主题,谢谢回复。 - Khemraj Sharma
1
@Khemraj 抱歉,但我仍然不明白。现在一切都好了吗? - android developer
1
是的,兄弟,我不知道为什么你听不懂我 : )。我在布局中有一个TextView,我想把它转换成位图。布局中有一个ImageView和一个TextView。ImageView可以转换为位图。但TextView没有出现在位图中。这就是问题所在。之后我意识到我应用了一个主题,使TextView文本颜色变为白色。我解决了这个问题。现在一切都好了。谢谢。 - Khemraj Sharma
创建的位图的内存大小是否等于视图(源视图)的内存大小? - sandeep dhami

2
希望这可以帮到您。
View view="some view instance";        
view.setDrawingCacheEnabled(true);
Bitmap bitmap=view.getDrawingCache();
view.setDrawingCacheEnabled(false);

更新
getDrawingCache() 方法在API级别28中已被弃用。因此,请寻找其他替代方案,适用于API级别> 28。


3
getDrawingCache 目前已被弃用。 - David Miguel

1
我几天前使用过这个:

fun generateBitmapFromView(view: View): Bitmap {
    val specWidth = View.MeasureSpec.makeMeasureSpec(1324, View.MeasureSpec.AT_MOST)
    val specHeight = View.MeasureSpec.makeMeasureSpec(521, View.MeasureSpec.AT_MOST)
    view.measure(specWidth, specHeight)
    val width = view.measuredWidth
    val height = view.measuredHeight
    val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(bitmap)
    view.layout(view.left, view.top, view.right, view.bottom)
    view.draw(canvas)
    return bitmap
}

这段代码基于此gist

当硬件加速器为真时,Florent的Shapofview对我很有帮助,但它不起作用。 - Bhavin Patel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接