【问题标题】:Take a screenshot of RecyclerView in FULL length截取 RecyclerView 的全长截图
【发布时间】:2015-05-06 18:43:45
【问题描述】:

我想获得活动的“整页”屏幕截图。该视图包含一个包含许多项目的 RecyclerView。

我可以用这个功能截取当前视图:

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

但它只包含我可以正常查看的项目(如预期的那样)。

当我截取屏幕截图时,有什么方法可以让 RecyclerView 神奇地全长显示(一次显示所有项目)?

如果不是,我应该如何解决这个问题?

【问题讨论】:

  • 如果你有很多项目,不管有没有办法解决,我保证你是内存不足的错误。

标签: java android


【解决方案1】:

灵感来自 Yoav 的回答。此代码适用于 recyclerview 项目类型,并且可能与它的大小无关。

使用具有线性布局管理器和三种项目类型的回收器视图进行了测试。尚未与其他布局管理器一起检查。

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

【讨论】:

  • 非常感谢。兄弟,你救了我的命!
  • 感谢代码,但 imageView 没有被导出
  • 他们非常感谢,我们可以再添加一点移动框架
  • 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 )
  • 比第一个接受的解决方案更好。上面公认的解决方案会创建一个缺少数据的低质量图像。
【解决方案2】:

无论项目和项目类型如何,都截取完整的 Recyclerview 的屏幕截图:

这段代码就像一个魅力。

这是一种简洁而准确的方法:

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

【讨论】:

  • 效果很好。我想做的一件事是删除上面和下面的空白。我试过删除所有填充和边距,但没有运气。
  • 尝试为布局提供背景
  • 我必须做的两件事:在创建位图之前调用 recyclerView.getLayoutManager().scrollToPosition(0) 以移除底部空间并确保输出一致。我在 recyclerView 上方还有另一个视图组,我必须将其创建为单独的位图并将两者组合在一起。
  • 我的屏幕截图为可见区域之外的行设置了黑色背景。有什么建议可以解决吗?
  • 是的,伙计。 @J.Dragon
【解决方案3】:

这是我对LinearLayoutManager 的解决方案,当所有项目的尺寸相同并且只有一种类型的项目。此解决方案基于This answer

注意:可能会导致内存不足的错误。

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

注意 2: 它最初是用 Kotlin 编写的。 Here是我使用的原始代码。

【讨论】:

  • 使其在并非所有项目都具有相同大小的情况下工作需要您保存每个项目的数据,可能在文件中。
  • 我的一个文本视图没有出现在屏幕截图中
  • 链接中的 Kotlin 代码使用了来自github.com/metalabdesign/AsyncAwait的异步库
  • @dmathewwws 实际上它是基于旧版本的Anko (之前 async 是保留字)。 AsyncAwait 可以用于此,但实际上每个异步库都可以在这里工作。
  • 感谢 Yoav 的澄清
【解决方案4】:

我找到的最佳解决方案是

  1. 创建一个新的 NestedScrollView

  2. 将其添加到 recyclerview 的父级

  3. 从其父级中删除recyclerview“将其添加到新父级很重要”

  4. 添加recyclerview到nestedscrollview

  5. 对嵌套滚动视图进行截图

  6. 将 recyclerview 添加到其主要父级。

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

这个方法会返回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;
            
            }

使用后别忘了回收位图

【讨论】:

    【解决方案5】:

    我写了一个方法来获取几个不同视图的截图:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-10-14
      • 1970-01-01
      相关资源
      最近更新 更多