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

问题描述 投票:0回答:14

我会尽力解释我到底需要做什么。

我有 3 个独立的屏幕,分别为 A、B、C。还有另一个屏幕称为 HomeScreen,其中所有 3 个屏幕位图都应显示在图库视图中,用户可以选择他想要进入哪个视图。

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

那么,我可以从 HomeScreen 调用另一个 Activity 并且不显示它,只获取该屏幕的位图吗?例如,假设我只是调用 HomeScreen,它调用 Activity A、B、C,并且 A、B、C 中的任何 Activity 都不会显示。它只是通过 getDrawingCache() 给出该屏幕的位图。然后我们可以在主屏幕的图库视图中显示这些位图。

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

请告诉我这是否真的可行。

android bitmap
14个回答
241
投票

有一种方法可以做到这一点。你必须创建一个位图和一个画布并调用 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();

37
投票

这是我的解决方案:

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;
}

享受:)


31
投票

我知道这可能是一个陈旧的问题,但我在让这些解决方案为我工作时遇到问题。 具体来说,我发现如果在视图膨胀后对视图进行任何更改,这些更改将不会合并到渲染的位图中。

这是最终适用于我的案例的方法。不过,有一点需要注意。在调用

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

干杯,

李维


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;
}

23
投票

Android KTX 中有一个很棒的 Kotlin 扩展功能:

View.drawToBitmap(Bitmap.Config)


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*/);

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 及更高版本。我以前不知道该怎么做。


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()

2
投票

希望这有帮助

View view="some view instance";        
view.setDrawingCacheEnabled(true);
Bitmap bitmap=view.getDrawingCache();
view.setDrawingCacheEnabled(false);

更新

getDrawingCache()
方法在 API 级别 28 中已弃用。因此,请寻找 API 级别 > 28 的其他替代方法。


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


0
投票
private fun getBitmapFromView(view: View): Bitmap
{
    val returnedBitmap = Bitmap.createBitmap(view.width,
        view.height
    ,Bitmap.Config.ARGB_8888)

    val canvas = Canvas(returnedBitmap)

    //background image
    val bgDrawable = view.background

    //background image is selected
    if (bgDrawable != null){
        bgDrawable.draw(canvas)
    }
    else{
        canvas.drawColor(Color.WHITE)
    }
    
    view.draw(canvas)
    
    return returnedBitmap

}

0
投票

我在我的项目中遇到了同样的问题并找到了解决方案,我将在这里描述。

public Bitmap catchBitmapFromView(View capture_view){
        if (capture_view == null) {
            return null;
        }

        capture_view.setDrawingCacheEnabled(true);

        Bitmap bitmap = Bitmap.createBitmap(capture_view.getWidth(), capture_view.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        Drawable bgDrawable = capture_view.getBackground();
        if (bgDrawable != null) {
            bgDrawable.draw(canvas);
        } else {
            canvas.drawColor(Color.WHITE);
        }
        capture_view.draw(canvas);

        capture_view.setDrawingCacheEnabled(false);
        return bitmap;
}

0
投票

这里有多个不错的答案,但我认为这是应该使用正确的测量/布局/绘制逻辑来完成的方式。

@Nullable
public static Bitmap captureBitmap(@NonNull View view){
    ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(0, 0);
    if(view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams){
        mlp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
    }
    int parentWms = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    int parentHms = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    int wms = ViewGroup.getChildMeasureSpec(parentWms,
            view.getPaddingLeft() + view.getPaddingRight() + mlp.leftMargin + mlp.rightMargin,
            view.getLayoutParams().width);
    int hms = ViewGroup.getChildMeasureSpec(parentHms,
            view.getPaddingTop() + view.getPaddingBottom() + mlp.topMargin + mlp.bottomMargin,
            view.getLayoutParams().height);
    view.measure(wms, hms);
    view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
    if(view.getMeasuredWidth()<=0 || view.getMeasuredHeight()<=0){
        return null;
    }
    Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    view.draw(canvas);
    return bitmap;
}

如果您使用的视图将任何尺寸测量为 MATCH_PARENT,则需要指定父尺寸,至少在测量方向上。 如果您的视图测量宽度为 MATCH_PARENT 则使用

int parentWms = View.MeasureSpec.makeMeasureSpec(parentWidth, View.MeasureSpec.AT_MOST);

如果您的视图测量为 MATCH_PARENT,则高度也类似。

int parentHms = View.MeasureSpec.makeMeasureSpec(parentHeight, View.MeasureSpec.AT_MOST);

-3
投票
view.setDrawingCacheEnabled(true);
Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());
view.setDrawingCacheEnabled(false);
© www.soinside.com 2019 - 2024. All rights reserved.