• Android View转换为Bitmap,实现截屏效果


    前言

            安卓设备一般都自带截图功能,但是用户体验有不好之处。就是会连带着状态栏📶、🔋、时间日期、其他不必要页面中信息,等等与用户想截屏的内容不符的信息也会被保存下来。通常,截图后用户会再次裁剪一次才能想把真正需求分享出去。

            因此,咱们技术研发会遇到针对性的会做一些应用内的截屏功能。


    一、getDrawingCache

       getDrawingCache()是其中一种截图手段,使用方便,主要针对应用内截图。

    1、创建View

    1. fun getShareView() : View {
    2. val shareView: View =
    3. LayoutInflater.from(context).inflate(R.layout.share_layout, null)
    4. //内容...
    5. return shareView
    6. }

    注意:一般大家实现思路都是点击事件里进行创建View绘制,很可能会遇到网络图片还未加载完的情况。因此,建议做延迟处理,或在点击前前置创建好。

    2、测试和绘制

    1. public static void layoutView(View v, int width, int height) {
    2. v.layout(0, 0, width, height);
    3. int measuredWidth = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
    4. int measuredHeight = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
    5. v.measure(measuredWidth, measuredHeight);
    6. v.layout(0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());
    7. }

    如果不走这个方法,bitmap转换时会没有视图(黑屏情况)。 

    调用方法:

    1. // 设置视图的dp宽高
    2. layoutView(share_view, dp2px(210), dp2px(180));
    1. public static int dp2px(float dp) {
    2. float scale = Resources.getSystem().getDisplayMetrics().density;
    3. return (int) (dp * scale + 0.5f);
    4. }

    3、转换Bitmap

    1. public static Bitmap getCacheBitmapFromView(View view) {
    2. final boolean drawingCacheEnabled = true;
    3. view.setDrawingCacheEnabled(drawingCacheEnabled);
    4. //设置背景色 //view.setBackgroundColor(CommonUtils.getContext().getResources().getColor(R.color.half_white));
    5. view.buildDrawingCache(drawingCacheEnabled);
    6. final Bitmap drawingCache = view.getDrawingCache();
    7. Bitmap bitmap;
    8. if (drawingCache != null) {
    9. bitmap = Bitmap.createBitmap(drawingCache);
    10. view.setDrawingCacheEnabled(false);
    11. } else {
    12. bitmap = null;
    13. }
    14. return bitmap;
    15. }

    二、黑屏问题

            一般情况下,上面的代码能够正常实现效果。但有时候,生成Bitmap会出现问题(Bitmap全黑色)。主要原因是drawingCache的值大于系统给定的值。我们可以看一下buildDrawingCache()方法中的一段代码:

    1. //所要cache的view绘制的宽度和高度
    2. if (width <= 0 || height <= 0 ||
    3. //计算的是当前所需要的drawingCache 大小
    4. (width * height * (opaque && !translucentWindow ? 2 : 4) >
    5. //得到的是系统所提供的最大的DrawingCache的值
    6. ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize())) {
    7. destroyDrawingCache();
    8. return;
    9. }

    当所需要的drawingCache  > 系统所提供的最大DrawingCache值时,生成Bitmap就会出现问题,此时获取的Bitmap就为null。

    所以在只需要修改所需的cache值就可以解决问题了。于是我们引入第二种方法:

    解决方案:

    1. public static Bitmap convertViewToBitmap(View view){
    2. view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
    3. MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
    4. view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
    5. view.buildDrawingCache();
    6. Bitmap bitmap = view.getDrawingCache();
    7. return bitmap;
    8. }

    view 使用 "getMeasuredWidth()"、 "getMeasuredHeight()"方法计算长宽。此时,Bitmap就能正确获取了。

    三、源码分析

    1. public void buildDrawingCache() {
    2. buildDrawingCache(false);
    3. }
    4. public Bitmap getDrawingCache() {
    5. return getDrawingCache(false);
    6. }
    7. public Bitmap getDrawingCache(boolean autoScale) {
    8. if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) {
    9. return null;
    10. }
    11. if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED) {
    12. buildDrawingCache(autoScale);
    13. }
    14. return autoScale ? mDrawingCache : mUnscaledDrawingCache;
    15. }
    16. public void buildDrawingCache(boolean autoScale) {
    17. if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?
    18. mDrawingCache == null : mUnscaledDrawingCache == null)) {
    19. if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
    20. Trace.traceBegin(Trace.TRACE_TAG_VIEW,
    21. "buildDrawingCache/SW Layer for " + getClass().getSimpleName());
    22. }
    23. try {
    24. buildDrawingCacheImpl(autoScale);
    25. } finally {
    26. Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    27. }
    28. }
    29. }
    30. private void buildDrawingCacheImpl(boolean autoScale) {
    31. mCachingFailed = false;
    32. int width = mRight - mLeft;
    33. int height = mBottom - mTop;
    34. final AttachInfo attachInfo = mAttachInfo;
    35. final boolean scalingRequired = attachInfo != null && attachInfo.mScalingRequired;
    36. if (autoScale && scalingRequired) {
    37. width = (int) ((width * attachInfo.mApplicationScale) + 0.5f);
    38. height = (int) ((height * attachInfo.mApplicationScale) + 0.5f);
    39. }
    40. final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor;
    41. final boolean opaque = drawingCacheBackgroundColor != 0 || isOpaque();
    42. final boolean use32BitCache = attachInfo != null && attachInfo.mUse32BitDrawingCache;
    43. final long projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4);
    44. final long drawingCacheSize =
    45. ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize();
    46. if (width <= 0 || height <= 0 || projectedBitmapSize > drawingCacheSize) {
    47. if (width > 0 && height > 0) {
    48. Log.w(VIEW_LOG_TAG, getClass().getSimpleName() + " not displayed because it is"
    49. + " too large to fit into a software layer (or drawing cache), needs "
    50. + projectedBitmapSize + " bytes, only "
    51. + drawingCacheSize + " available");
    52. }
    53. destroyDrawingCache();
    54. mCachingFailed = true;
    55. return;
    56. }
    57. ..检测drawingCache原有数据操作..
    58. try {
    59. bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),
    60. width, height, quality);
    61. bitmap.setDensity(getResources().getDisplayMetrics().densityDpi);
    62. if (autoScale) {
    63. mDrawingCache = bitmap;
    64. } else {
    65. mUnscaledDrawingCache = bitmap;
    66. }
    67. if (opaque && use32BitCache) bitmap.setHasAlpha(false);
    68. } catch (OutOfMemoryError e) {
    69. // If there is not enough memory to create the bitmap cache, just
    70. // ignore the issue as bitmap caches are not required to draw the
    71. // view hierarchy
    72. if (autoScale) {
    73. mDrawingCache = null;
    74. } else {
    75. mUnscaledDrawingCache = null;
    76. }
    77. mCachingFailed = true;
    78. return;
    79. }
    80. ..执行Bitmap写入autoScale ? mDrawingCache : mUnscaledDrawingCache操作..
    81. }

       从以上源码中,可以看到getDrawingcache = null 的条件共有四个: 
          1、(mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING为true 
          2、没有设置setDrawingCacheEnabled(true) 
          3、width <= 0 || height <= 0 || projectedBitmapSize > drawingCacheSize为true 
          4、OutOfMemory 
     

            除了第一个条件,其他的都是buildDrawingCache执行时才会触发。下面来分析下条件三。既然子布局可以正常显示,那么一定是满足width>0和height>0的, drawingCacheSize肯定是一个固定值,就是当前设备系统所允许的最大绘制缓存值。projectedBitmapSize的计算方式为width * height * (opaque && !use32BitCache ? 2 : 4),顾名思义,就是当前计划缓存的图片大小,(opaque && !use32BitCache ? 2 : 4)不可能为0,也不可能是导致计划缓存值变大的主因,width就是屏幕的宽,这个没有变动的条件,那么可以肯定就是height出现了异常,对于视图高度的计算,android源码表示如下:

    1. @ViewDebug.ExportedProperty(category = "layout")
    2. public final int getHeight() {
    3. return mBottom - mTop;
    4. }

    一个View的高度getHeight()就是底-高,其中mBottom指的是视图自身的底边到父视图顶边的距离,mTop指的是视图自身的顶边到父视图顶边的距离。

    四、View转Canvas转Bitmap

    1.     Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
    2.     Canvas canvas = new Canvas(bitmap);
    3.     view.measure(View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(view.getHeight(), View.MeasureSpec.EXACTLY));
    4.     view.layout((int) view.getX(), (int) view.getY(), (int) view.getX() + view.getMeasuredWidth(), (int) view.getY() + view.getMeasuredHeight());
    5.     view.draw(canvas);
    6.     return bitmap;

  • 相关阅读:
    ApplicationContext接口解读
    改bug问题
    ExoPlayer架构详解与源码分析(8)——Loader
    SQL Server不允许保存更改的解决方法
    JAVA-读取excel转成html 将excel表格转换为HTML文件格式 转成前端表格样式
    Tengine日志切割,删除7天之前的日志文件
    代码随想录算法训练营第五十一天| 309.最佳买卖股票时机含冷冻期、714.买卖股票的最佳时机含手续费
    Android Proguard混淆
    【毕业设计源码】基于JAVA的微信小程序直播在线教育平台
    SpringCloudAlibaba 微服务整合分布式事务Seata
  • 原文地址:https://blog.csdn.net/csdn_aiyang/article/details/126766336