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 fun
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();
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.
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;
}
The best solution I found is
create a new NestedScrollView
add it to recyclerview's parent
remove recyclerview from its parent "it's important to add it to a new parent"
add recyclerview to nestedscrollview
take screenshot of nestedscrollview
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
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;
}