Android View转换为Bitmap实现应用内截屏功能
作者:艾阳Blog
前言
安卓设备一般都自带截图功能,但是用户体验有不好之处。就是会连带着状态栏📶、🔋、时间日期、其他不必要页面中信息,等等与用户想截屏的内容不符的信息也会被保存下来。通常,截图后用户会再次裁剪一次才能想把真正需求分享出去。
因此,咱们技术研发会遇到针对性的会做一些应用内的截屏功能。
一、getDrawingCache
getDrawingCache()是其中一种截图手段,使用方便,主要针对应用内截图。
1、创建View
fun getShareView() : View { val shareView: View = LayoutInflater.from(context).inflate(R.layout.share_layout, null) //内容... return shareView }
注意:一般大家实现思路都是点击事件里进行创建View绘制,很可能会遇到网络图片还未加载完的情况。因此,建议做延迟处理,或在点击前前置创建好。
2、测试和绘制
public static void layoutView(View v, int width, int height) { v.layout(0, 0, width, height); int measuredWidth = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); int measuredHeight = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); v.measure(measuredWidth, measuredHeight); v.layout(0, 0, v.getMeasuredWidth(), v.getMeasuredHeight()); }
如果不走这个方法,bitmap转换时会没有视图(黑屏情况)。
调用方法:
// 设置视图的dp宽高 layoutView(share_view, dp2px(210), dp2px(180));
public static int dp2px(float dp) { float scale = Resources.getSystem().getDisplayMetrics().density; return (int) (dp * scale + 0.5f); }
3、转换Bitmap
public static Bitmap getCacheBitmapFromView(View view) { final boolean drawingCacheEnabled = true; view.setDrawingCacheEnabled(drawingCacheEnabled); //设置背景色 //view.setBackgroundColor(CommonUtils.getContext().getResources().getColor(R.color.half_white)); view.buildDrawingCache(drawingCacheEnabled); final Bitmap drawingCache = view.getDrawingCache(); Bitmap bitmap; if (drawingCache != null) { bitmap = Bitmap.createBitmap(drawingCache); view.setDrawingCacheEnabled(false); } else { bitmap = null; } return bitmap; }
二、黑屏问题
一般情况下,上面的代码能够正常实现效果。但有时候,生成Bitmap会出现问题(Bitmap全黑色)。主要原因是drawingCache的值大于系统给定的值。我们可以看一下buildDrawingCache()方法中的一段代码:
//所要cache的view绘制的宽度和高度 if (width <= 0 || height <= 0 || //计算的是当前所需要的drawingCache 大小 (width * height * (opaque && !translucentWindow ? 2 : 4) > //得到的是系统所提供的最大的DrawingCache的值 ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize())) { destroyDrawingCache(); return; }
当所需要的drawingCache > 系统所提供的最大DrawingCache值时,生成Bitmap就会出现问题,此时获取的Bitmap就为null。
所以在只需要修改所需的cache值就可以解决问题了。于是我们引入第二种方法:
解决方案:
public static Bitmap convertViewToBitmap(View view){ view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); view.buildDrawingCache(); Bitmap bitmap = view.getDrawingCache(); return bitmap; }
view 使用 "getMeasuredWidth()"、 "getMeasuredHeight()"方法计算长宽。此时,Bitmap就能正确获取了。
三、源码分析
public void buildDrawingCache() { buildDrawingCache(false); } public Bitmap getDrawingCache() { return getDrawingCache(false); } public Bitmap getDrawingCache(boolean autoScale) { if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) { return null; } if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED) { buildDrawingCache(autoScale); } return autoScale ? mDrawingCache : mUnscaledDrawingCache; } public void buildDrawingCache(boolean autoScale) { if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ? mDrawingCache == null : mUnscaledDrawingCache == null)) { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "buildDrawingCache/SW Layer for " + getClass().getSimpleName()); } try { buildDrawingCacheImpl(autoScale); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } } } private void buildDrawingCacheImpl(boolean autoScale) { mCachingFailed = false; int width = mRight - mLeft; int height = mBottom - mTop; final AttachInfo attachInfo = mAttachInfo; final boolean scalingRequired = attachInfo != null && attachInfo.mScalingRequired; if (autoScale && scalingRequired) { width = (int) ((width * attachInfo.mApplicationScale) + 0.5f); height = (int) ((height * attachInfo.mApplicationScale) + 0.5f); } final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor; final boolean opaque = drawingCacheBackgroundColor != 0 || isOpaque(); final boolean use32BitCache = attachInfo != null && attachInfo.mUse32BitDrawingCache; final long projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4); final long drawingCacheSize = ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize(); if (width <= 0 || height <= 0 || projectedBitmapSize > drawingCacheSize) { if (width > 0 && height > 0) { Log.w(VIEW_LOG_TAG, getClass().getSimpleName() + " not displayed because it is" + " too large to fit into a software layer (or drawing cache), needs " + projectedBitmapSize + " bytes, only " + drawingCacheSize + " available"); } destroyDrawingCache(); mCachingFailed = true; return; } ..检测drawingCache原有数据操作.. try { bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(), width, height, quality); bitmap.setDensity(getResources().getDisplayMetrics().densityDpi); if (autoScale) { mDrawingCache = bitmap; } else { mUnscaledDrawingCache = bitmap; } if (opaque && use32BitCache) bitmap.setHasAlpha(false); } catch (OutOfMemoryError e) { // If there is not enough memory to create the bitmap cache, just // ignore the issue as bitmap caches are not required to draw the // view hierarchy if (autoScale) { mDrawingCache = null; } else { mUnscaledDrawingCache = null; } mCachingFailed = true; return; } ..执行Bitmap写入autoScale ? mDrawingCache : mUnscaledDrawingCache操作.. }
从以上源码中,可以看到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源码表示如下:
@ViewDebug.ExportedProperty(category = "layout") public final int getHeight() { return mBottom - mTop; }
一个View的高度getHeight()就是底-高,其中mBottom指的是视图自身的底边到父视图顶边的距离,mTop指的是视图自身的顶边到父视图顶边的距离。
四、View转Canvas转Bitmap
Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); view.measure(View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(view.getHeight(), View.MeasureSpec.EXACTLY)); view.layout((int) view.getX(), (int) view.getY(), (int) view.getX() + view.getMeasuredWidth(), (int) view.getY() + view.getMeasuredHeight()); view.draw(canvas); return bitmap;
到此这篇关于Android View转换为Bitmap实现应用内截屏功能的文章就介绍到这了,更多相关Android View转换为Bitmap内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!