12

I want to get a "full page" screenshot of the activity. The view contains a RecyclerView with many items.

I can take a screenshot of the current view with this function:

public Bitmap getScreenBitmap() {
    View v= findViewById(R.id.container).getRootView();
    v.setDrawingCacheEnabled(true);
    v.buildDrawingCache(true);
    Bitmap b = Bitmap.createBitmap(v.getDrawingCache());
    v.setDrawingCacheEnabled(false); // clear drawing cache
    return b;
}

But it contains only the items I can view normally (as expected).

Is there some way to make the RecyclerView magically show in full length (display all items at once) when I take the screenshot?

If not, how should I approach this problem?

davis
  • 1,137
  • 1
  • 10
  • 28

6 Answers6

23

Inspired from Yoav's answer. This code works for recyclerview item types and probably regardless of it's size.

It was tested with a recyclerview having linearlayout manager and three item types. Yet to check it with other layout managers.

public Bitmap getScreenshotFromRecyclerView(RecyclerView view) {
        RecyclerView.Adapter adapter = view.getAdapter();
        Bitmap bigBitmap = null;
        if (adapter != null) {
            int size = adapter.getItemCount();
            int height = 0;
            Paint paint = new Paint();
            int iHeight = 0;
            final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

            // Use 1/8th of the available memory for this memory cache.
            final int cacheSize = maxMemory / 8;
            LruCache<String, Bitmap> bitmaCache = new LruCache<>(cacheSize);
            for (int i = 0; i < size; i++) {
                RecyclerView.ViewHolder holder = adapter.createViewHolder(view, adapter.getItemViewType(i));
                adapter.onBindViewHolder(holder, i);
                holder.itemView.measure(View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY),
                        View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
                holder.itemView.layout(0, 0, holder.itemView.getMeasuredWidth(), holder.itemView.getMeasuredHeight());
                holder.itemView.setDrawingCacheEnabled(true);
                holder.itemView.buildDrawingCache();
                Bitmap drawingCache = holder.itemView.getDrawingCache();
                if (drawingCache != null) {

                    bitmaCache.put(String.valueOf(i), drawingCache);
                }
//                holder.itemView.setDrawingCacheEnabled(false);
//                holder.itemView.destroyDrawingCache();
                height += holder.itemView.getMeasuredHeight();
            }

            bigBitmap = Bitmap.createBitmap(view.getMeasuredWidth(), height, Bitmap.Config.ARGB_8888);
            Canvas bigCanvas = new Canvas(bigBitmap);
            bigCanvas.drawColor(Color.WHITE);

            for (int i = 0; i < size; i++) {
                Bitmap bitmap = bitmaCache.get(String.valueOf(i));
                bigCanvas.drawBitmap(bitmap, 0f, iHeight, paint);
                iHeight += bitmap.getHeight();
                bitmap.recycle();
            }

        }
        return bigBitmap;
    }
Yoav Sternberg
  • 6,421
  • 3
  • 28
  • 30
Prasham
  • 6,646
  • 8
  • 38
  • 55
  • Thank you very very much. You saved my life bro! – Vaibhav Jani Feb 15 '16 at 14:23
  • Thanks for the code, but imageView is not getting exported – Janardhan Y Jan 07 '18 at 11:30
  • they very much thanks, one more point can we add mobile frame with that – Dart Jan 12 '18 at 11:48
  • 1
    java.lang.ArrayIndexOutOfBoundsException: length=10; index=-1 at java.util.ArrayList.get(ArrayList.java:439) at .adapter.InsightsDetailAdapter.onBindViewHolder(InsightsDetailAdapter.java:170) at .ui.insights.insightdetail.InsightsDetailFragment.getScreenshotFromRecyclerView(InsightsDetailFragment.java:456) – Nouman Ch Aug 27 '18 at 10:14
  • Better solution than the first accepted one. The accepted solution on top creates a low quality image with missing data. – Shubham Goel Feb 04 '21 at 14:55
  • java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.graphics.Bitmap.isRecycled()' on a null object reference..... ---on this line--- Bitmap bitmap = bitmaCache.get(String.valueOf(i)); – Ahamadullah Saikat Mar 12 '23 at 11:50
18

Here is my solution for LinearLayoutManager when all the items are on same size and there is only one type of item. This solution is based on This answer.

Note: It can possibly lead to out of memory error.

public static Bitmap getRecyclerViewScreenshot(RecyclerView view) {
        int size = view.getAdapter().getItemCount();
        RecyclerView.ViewHolder holder = view.getAdapter().createViewHolder(view, 0);
        view.getAdapter().onBindViewHolder(holder, 0);
        holder.itemView.measure(View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY),
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
        holder.itemView.layout(0, 0, holder.itemView.getMeasuredWidth(), holder.itemView.getMeasuredHeight());
        Bitmap bigBitmap = Bitmap.createBitmap(view.getMeasuredWidth(), holder.itemView.getMeasuredHeight() * size,
                Bitmap.Config.ARGB_8888);
        Canvas bigCanvas = new Canvas(bigBitmap);
        bigCanvas.drawColor(Color.WHITE);
        Paint paint = new Paint();
        int iHeight = 0;
        holder.itemView.setDrawingCacheEnabled(true);
        holder.itemView.buildDrawingCache();
        bigCanvas.drawBitmap(holder.itemView.getDrawingCache(), 0f, iHeight, paint);
        holder.itemView.setDrawingCacheEnabled(false);
        holder.itemView.destroyDrawingCache();
        iHeight += holder.itemView.getMeasuredHeight();
        for (int i = 1; i < size; i++) {
            view.getAdapter().onBindViewHolder(holder, i);
            holder.itemView.setDrawingCacheEnabled(true);
            holder.itemView.buildDrawingCache();
            bigCanvas.drawBitmap(holder.itemView.getDrawingCache(), 0f, iHeight, paint);
            iHeight += holder.itemView.getMeasuredHeight();
            holder.itemView.setDrawingCacheEnabled(false);
            holder.itemView.destroyDrawingCache();
        }
        return bigBitmap;
    }

Note 2: It has originally been written in Kotlin. Here is the original code used by me.

Community
  • 1
  • 1
Yoav Sternberg
  • 6,421
  • 3
  • 28
  • 30
9

Take the Screenshot of complete Recyclerview regardless of its items and item types:

This piece of code works like a charm.

Here is a clean way to do this short and precise:

recyclerView.measure(
        View.MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), View.MeasureSpec.EXACTLY),
        View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));

Bitmap bm = Bitmap.createBitmap(recyclerView.getWidth(), recyclerView.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
recyclerView.draw(new Canvas(bm));

saveImage(bm);
ImageView im
          = new ImageView(getActivity());
im.setImageBitmap(bm);
 new AlertDialog.Builder(getActivity()).setView(im).show();
Nouman Ch
  • 4,023
  • 4
  • 29
  • 42
  • Works well. One thing I would like to do is remove the white space above and below. I've tried removing all padding and margin but no luck. – Jeffrey Sep 22 '18 at 15:37
  • Try giving background to the layout – Nouman Ch Sep 22 '18 at 15:39
  • 4
    Two thing I had to do: Call recyclerView.getLayoutManager().scrollToPosition(0) before creating the bitmap to remove the bottom space and ensure consistent output. I also had another view group above the recyclerView which I had to create as a separate bitmap of and combine the two together. – Jeffrey Sep 23 '18 at 04:18
  • my screenshot gets a black background for the rows that are out of visible area. Any suggestion to fix it? – ChaturaM Apr 24 '19 at 06:58
  • yeah it do budddy. @J.Dragon – Nouman Ch Jul 04 '20 at 16:28
  • What about horizontal recycleview ? – Mohammad Taqi Nov 26 '21 at 09:22
1

The best solution I found is

  1. create a new NestedScrollView

  2. add it to recyclerview's parent

  3. remove recyclerview from its parent "it's important to add it to a new parent"

  4. add recyclerview to nestedscrollview

  5. take screenshot of nestedscrollview

  6. add recyclerview to its main parent.

     nestedScreenShot = new NestedScrollView(getContext());
    
     constraintLayout.removeView(recyclerView);
    
     ConstraintLayout.LayoutParams params =new 
    
     Constraints.LayoutParams(viewGroup.LayoutParams.MATCH_PARENT,
    
     ViewGroup.LayoutParams.MATCH_PARENT);
    
     nestedScreenShot.setLayoutParams(params);
    
    Drawable scrollBackground = scrollView.getBackground();
    if (scrollBackground != null) {
        tempNestedScreenShot.setBackground(scrollBackground);
    }
    tempNestedScreenShot.setPadding(scrollView.getLeft(), 
    scrollView.getTop(), scrollView.getRight(), scrollView.getBottom());
    
     constraintLayout.addView(nestedScreenShot);
    
     nestedScreenShot.addView(recyclerView, params);
    
     new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
    
                nestedScreenShot.removeView(recyclerView);
                constraintLayout.removeView(nestedScreenShot);
                nestedScreenShot = null;
                constraintLayout.addView(recyclerView, params);
    
    
            }
            }, 8000);
      takescreenshotOfNested(nestedScreenShot);
    

This method will return screenshot of nestedscrollview

       private Bitmap takescreenshotOfNested(View u) {
        
        NestedScrollView viewNested = null;
            
        ScrollView viewScroll = null;
              
        if (u instanceof NestedScrollView) {
                 
       
        viewNested = (NestedScrollView) u;
             
     
       } else if (u instanceof ScrollView) {
              
      
         viewScroll = (ScrollView) u;
            
       }
            
                Bitmap bitmap;
                if (viewNested != null) {
                    viewNested.setDrawingCacheEnabled(false);
                    viewNested.invalidate();
                    viewNested.getChildAt(0).setDrawingCacheEnabled(false);
                    viewNested.getChildAt(0).invalidate();
            
                    bitmap = Bitmap.createBitmap(viewNested.getChildAt(0).getWidth(),
                            viewNested.getChildAt(0).getHeight() + 8, Bitmap.Config.ARGB_8888);
                    Canvas canvas = new Canvas(bitmap);
                    canvas.drawColor(activity.getResources().getColor(R.color.layout_background));
                    viewNested.draw(canvas);
                } else {
            
                    try {
            
                        viewScroll.setDrawingCacheEnabled(false);
                        viewScroll.invalidate();
                    } catch (Exception ignored) {
                    }
                    viewScroll.getChildAt(0).setDrawingCacheEnabled(false);
                    viewScroll.getChildAt(0).invalidate();
            
                    bitmap = Bitmap.createBitmap(viewScroll.getChildAt(0).getWidth(),
                            viewScroll.getChildAt(0).getHeight() + 8, Bitmap.Config.ARGB_8888);
                    Canvas canvas = new Canvas(bitmap);
                    canvas.drawColor(activity.getResources().getColor(R.color.layout_background));
                    viewScroll.draw(canvas);
            
                }
                return bitmap;
            
            }

don't forget to recycle the bitmap after using it

Momen Zaqout
  • 1,508
  • 1
  • 16
  • 17
0

I wrote a method to get a screenshot of a few different views:

private static Bitmap getBitmapFromView(View view) {
    if (view.getVisibility() != View.VISIBLE) {
        view = getNextView(view);
        Log.d(TAG, "New view id: " + view.getId());
    }
    //Define a bitmap with the same size as the view
    Bitmap returnedBitmap;
    if (view instanceof ScrollView) {
        returnedBitmap = Bitmap.createBitmap(((ViewGroup) view).getChildAt(0).getWidth(), ((ViewGroup) view).getChildAt(0).getHeight(), Bitmap.Config.ARGB_8888);
    } else if (view instanceof RecyclerView) {
        view.measure(
                View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY),
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));

        returnedBitmap = Bitmap.createBitmap(view.getWidth(), view.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
    } else {
        returnedBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
    }

    //Bind a canvas to it
    Canvas canvas = new Canvas(returnedBitmap);
    //Get the view's background
    Drawable bgDrawable = view.getBackground();
    if (bgDrawable != null) {
        //has background drawable, then draw it on the canvas
        bgDrawable.draw(canvas);
    } else {
        //does not have background drawable, then draw white background on the canvas
        canvas.drawColor(Color.WHITE);
    }
    // draw the view on the canvas
    view.draw(canvas);
    //return the bitmap
    return returnedBitmap;
}

/**
 * If the base view is not visible, then it has no width or height.
 * This causes a problem when we are creating a PDF based on its size.
 * This method gets the next visible View.
 *
 * @param view The invisible view
 * @return The next visible view after the given View, or the original view if there's no more
 * visible views.
 */
private static View getNextView(View view) {
    if (view.getParent() != null && (view.getParent() instanceof ViewGroup)) {
        ViewGroup group = (ViewGroup) view.getParent();
        View child;
        boolean getNext = false;
        //Iterate through all views from parent
        for (int i = 0; i < group.getChildCount(); i++) {
            child = group.getChildAt(i);
            if (getNext) {
                //Make sure the view is visible, else iterate again until we find a visible view
                if (child.getVisibility() == View.VISIBLE) {
                    Log.d(TAG, String.format(Locale.ENGLISH, "CHILD: %s : %s", child.getClass().getSimpleName(), child.getId()));
                    view = child;
                }
            }
            //Iterate until we find out current view,
            // then we want to get the NEXT view
            if (child.getId() == view.getId()) {
                getNext = true;
            }
        }
    }
    return view;
}
eoinzy
  • 2,152
  • 4
  • 36
  • 67
0

Inspired from Nouman Ch answer, that solution works great, but I am facing background color issue in final image and recyclerView scrolling also not working fine after that. Here is quick fix!

After creating the bitmap from recyclerView

presenter?.dataRecyclerView?.let {
            it.measure(
                View.MeasureSpec.makeMeasureSpec(it.width, View.MeasureSpec.EXACTLY),
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
            );

            var bm = Bitmap.createBitmap(
                it.width,
                it.measuredHeight,
                Bitmap.Config.ARGB_8888
            )
            it.draw(Canvas(bm))

//to fix background color of final image
            val newBitmap = Bitmap.createBitmap(bm.width, bm.height, bm.config)
            val canvas = Canvas(newBitmap)
            canvas.drawColor(Color.WHITE)
            canvas.drawBitmap(bm, 0f, 0f, null)
            bm = newBitmap

            saveBitmapeToGallery(bm)
//notify adapter, to fix scrolling
            it.adapter?.notifyDataSetChanged()
        }
inabdev
  • 11
  • 1
  • 7