Android截屏總結

Linux編程

點擊右側關注,免費入門到精通!

Android截屏的原理:獲取具體需要截屏的區域的Bitmap,然後繪制在畫布上,保存為圖片後進行分享或者其它用途。

在截屏功能中,有時需要截取全屏的內容,有時需要截取超過一屏的內容(比如:Listview,Scrollview,RecyclerView)。下面介紹各種場景獲取Bitmap的方法

普通截屏的做到

獲取當前Window的DrawingCache的方式,即decorView的DrawingCache

/***shotthecurrentscreen,withthestatusbutthestatusistrans**@paramctxcurrentactivity*/publicstaticBitmapshotActivity(Activityctx){Viewview=ctx.getWindow().getDecorView();view.setDrawingCacheEnabled(true);view.buildDrawingCache();Bitmapbp=Bitmap.createBitmap(view.getDrawingCache(),0,0,view.getMeasuredWidth(),view.getMeasuredHeight());view.setDrawingCacheEnabled(false);view.destroyDrawingCache();returnbp;}

獲取當前View的DrawingCache

publicstaticBitmapgetViewBp(Viewv){if(null==v){returnnull;}v.setDrawingCacheEnabled(true);v.buildDrawingCache();if(Build.VERSION.SDK_INT>=11){v.measure(MeasureSpec.makeMeasureSpec(v.getWidth(),MeasureSpec.EXACTLY),MeasureSpec.makeMeasureSpec(v.getHeight(),MeasureSpec.EXACTLY));v.layout((int)v.getX(),(int)v.getY(),(int)v.getX()+v.getMeasuredWidth(),(int)v.getY()+v.getMeasuredHeight());}else{v.measure(MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED),MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED));v.layout(0,0,v.getMeasuredWidth(),v.getMeasuredHeight());}Bitmapb=Bitmap.createBitmap(v.getDrawingCache(),0,0,v.getMeasuredWidth(),v.getMeasuredHeight());v.setDrawingCacheEnabled(false);v.destroyDrawingCache();returnb;}

開源方案

在滾動視圖中,如果當前View並沒有在視圖中全部繪制出來,我們可以利用View的ScrollTo()和ScrollBy()方法來移動畫布,同時獲取當前View的可視部分的DrawingCache,最後進行拼接得到其Bitmap,參考

https://github.com/PGSSoft/scrollscreenshot

Scrollview截屏

三個截屏中,ScrollView最簡單,因為ScrollView只有一個childView,雖然沒有全部顯示在界面上,但是已經全部渲染繪制,因此可以直接 調用scrollView.draw(canvas)來完成截圖

publicstaticBitmapshotScrollView(ScrollViewscrollView){inth=0;Bitmapbitmap=null;for(inti=0;i<scrollView.getChildCount();i++){h+=scrollView.getChildAt(i).getHeight();scrollView.getChildAt(i).setBackgroundColor(Color.parseColor("#ffffff"));}bitmap=Bitmap.createBitmap(scrollView.getWidth(),h,Bitmap.Config.RGB_565);finalCanvascanvas=newCanvas(bitmap);scrollView.draw(canvas);returnbitmap;}

listview截屏

而ListView就是會回收與重用Item,並且只會繪制在螢幕上顯示的ItemView,根據stackoverflow上大神的建議,採用一個List來存儲Item的視圖,這種方案依然不夠好,當Item足夠多的時候,可能會發生oom。

publicstaticBitmapshotListView(ListViewlistview){ListAdapteradapter=listview.getAdapter();intitemscount=adapter.getCount();intallitemsheight=0;List<Bitmap>bmps=newArrayList<Bitmap>();for(inti=0;i<itemscount;i++){ViewchildView=adapter.getView(i,null,listview);childView.measure(View.MeasureSpec.makeMeasureSpec(listview.getWidth(),View.MeasureSpec.EXACTLY),View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED));childView.layout(0,0,childView.getMeasuredWidth(),childView.getMeasuredHeight());childView.setDrawingCacheEnabled(true);childView.buildDrawingCache();bmps.add(childView.getDrawingCache());allitemsheight+=childView.getMeasuredHeight();}Bitmapbigbitmap=Bitmap.createBitmap(listview.getMeasuredWidth(),allitemsheight,Bitmap.Config.ARGB_8888);Canvasbigcanvas=newCanvas(bigbitmap);Paintpaint=newPaint();intiHeight=0;for(inti=0;i<bmps.size();i++){Bitmapbmp=bmps.get(i);bigcanvas.drawBitmap(bmp,0,iHeight,paint);iHeight+=bmp.getHeight();bmp.recycle();bmp=null;}returnbigbitmap;}

RecyclerView截屏

我們都知道,在新的Android版本中,已經可以用RecyclerView來代替使用ListView的場景,相比較ListView,RecyclerView對Item View的緩存支持的更好。可以採用和ListView相同的方案,這里也是在stackoverflow上看到的方案。

publicstaticBitmapshotRecyclerView(RecyclerViewview){RecyclerView.Adapteradapter=view.getAdapter();BitmapbigBitmap=null;if(adapter!=null){intsize=adapter.getItemCount();intheight=0;Paintpaint=newPaint();intiHeight=0;finalintmaxMemory=(int)(Runtime.getRuntime().maxMemory()/1024);//Use1/8thoftheavailablememoryforthismemorycache.finalintcacheSize=maxMemory/8;LruCache<String,Bitmap>bitmaCache=newLruCache<>(cacheSize);for(inti=0;i<size;i++){RecyclerView.ViewHolderholder=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();BitmapdrawingCache=holder.itemView.getDrawingCache();if(drawingCache!=null){bitmaCache.put(String.valueOf(i),drawingCache);}height+=holder.itemView.getMeasuredHeight();}bigBitmap=Bitmap.createBitmap(view.getMeasuredWidth(),height,Bitmap.Config.ARGB_8888);CanvasbigCanvas=newCanvas(bigBitmap);DrawablelBackground=view.getBackground();if(lBackgroundinstanceofColorDrawable){ColorDrawablelColorDrawable=(ColorDrawable)lBackground;intlColor=lColorDrawable.getColor();bigCanvas.drawColor(lColor);}for(inti=0;i<size;i++){Bitmapbitmap=bitmaCache.get(String.valueOf(i));bigCanvas.drawBitmap(bitmap,0f,iHeight,paint);iHeight+=bitmap.getHeight();bitmap.recycle();}}returnbigBitmap;}

相信有不少小夥伴用BRVH第三方庫來做recycleview的適配器的。使用這個庫的話再用上面的方法會報角標越界的錯誤,看了BRVH的源碼

publicvoidonBindViewHolder(ViewHolderholder,intpositions){intviewType=holder.getItemViewType();switch(viewType){case0:this.convert((BaseViewHolder)holder,this.mData.get(holder.getLayoutPosition()-this.getHeaderLayoutCount()));case273:case819:case1365:break;case546:this.addLoadMore(holder);break;default:this.convert((BaseViewHolder)holder,this.mData.get(holder.getLayoutPosition()-this.getHeaderLayoutCount()));this.onBindDefViewHolder((BaseViewHolder)holder,this.mData.get(holder.getLayoutPosition()-this.getHeaderLayoutCount()));}}

在調用adapter.onBindViewHolder時,因為里面的position參數未使用,里面用的計算holder.getLayoutPosition() – this.getHeaderLayoutCount()的值一直是-1導致角標越界報錯。

本人理解,RecyclerView的截屏原理是,首先構造每個item的ViewHolder,然後調用具體設置數據到每個item的方法,此時cache中就存有item的內容,此時繪制就能獲取到完整的內容。採用v7包中的onBindViewHolder方法即可,

或者是BRVH的convert方法,可以看到BRVH中沒有暴露出這個方法,而且唯一暴露出的onBindViewHolder還會報角標越界錯誤,此時我們就需要在BRVH的基礎上暴露出convert即可,代碼如下

publicclassMyAdapterextendsBaseQuickAdapter<T>{publicMyAdapter(){super(getItemLayoutResId(),datas);}/***用於對外暴露convert方法,構造緩存視圖(截屏用)*@paramviewHolder*@paramt*/publicvoidstartConvert(BaseViewHolderviewHolder,Tt){convert(viewHolder,t);}@Overrideprotectedvoidconvert(BaseViewHolderviewHolder,Tt){bindView(viewHolder,t);}}

然後將上面所述的獲取Bitmap方法修改一下

/***截取recyclerview*/publicstaticBitmapgetRecyclerViewScreenshot(RecyclerViewview){BaseListFragment.MyAdapteradapter=(BaseListFragment.MyAdapter)view.getAdapter();BitmapbigBitmap=null;if(adapter!=null){intsize=adapter.getData().size();intheight=0;Paintpaint=newPaint();intiHeight=0;finalintmaxMemory=(int)(Runtime.getRuntime().maxMemory()/1024);//Use1/8thoftheavailablememoryforthismemorycache.finalintcacheSize=maxMemory/8;LruCache<String,Bitmap>bitmaCache=newLruCache<>(cacheSize);for(inti=0;i<size;i++){BaseViewHolderholder=(BaseViewHolder)adapter.createViewHolder(view,adapter.getItemViewType(i));//此處需要調用convert方法,否則繪制出來的都是空的itemadapter.startConvert(holder,adapter.getData().get(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();BitmapdrawingCache=holder.itemView.getDrawingCache();if(drawingCache!=null){bitmaCache.put(String.valueOf(i),drawingCache);}height+=holder.itemView.getMeasuredHeight();}bigBitmap=Bitmap.createBitmap(view.getMeasuredWidth(),height,Bitmap.Config.ARGB_8888);CanvasbigCanvas=newCanvas(bigBitmap);DrawablelBackground=view.getBackground();if(lBackgroundinstanceofColorDrawable){ColorDrawablelColorDrawable=(ColorDrawable)lBackground;intlColor=lColorDrawable.getColor();bigCanvas.drawColor(lColor);}for(inti=0;i<size;i++){Bitmapbitmap=bitmaCache.get(String.valueOf(i));bigCanvas.drawBitmap(bitmap,0f,iHeight,paint);iHeight+=bitmap.getHeight();bitmap.recycle();}}returnbigBitmap;}

合成Bitmap

比如四張合成一張

/***將四張圖拼成一張**@parampic1圖一*@parampic2圖二*@parampic3圖三*@parampic4圖四*@returnonly_bitmap*詳情見說明:{@linkcom.bertadata.qxb.util.ScreenShotUtils}*/publicstaticBitmapcombineBitmapsIntoOnlyOne(Bitmappic1,Bitmappic2,Bitmappic3,Bitmappic4,Activitycontext){intw_total=pic2.getWidth();inth_total=pic1.getHeight()+pic2.getHeight()+pic3.getHeight()+pic4.getHeight();inth_pic1=pic1.getHeight();inth_pic4=pic4.getHeight();inth_pic12=pic1.getHeight()+pic2.getHeight();//此處為防止OOM需要對高度做限制if(h_total>HEIGHTLIMIT){returnnull;}Bitmaponly_bitmap=Bitmap.createBitmap(w_total,h_total,Bitmap.Config.ARGB_4444);Canvascanvas=newCanvas(only_bitmap);canvas.drawColor(ContextCompat.getColor(context,R.color.color_content_bg));canvas.drawBitmap(pic1,0,0,null);canvas.drawBitmap(pic2,0,h_pic1,null);canvas.drawBitmap(pic3,0,h_pic12,null);canvas.drawBitmap(pic4,0,h_total-h_pic4,null);returnonly_bitmap;}

圖片後期處理

/***將傳入的Bitmap合理壓縮後輸出到系統截屏目錄下*命名格式為:Screenshot+時間戳+啟信寶報名.jpg*同時通知系統重新掃描系統文件*@parampic1圖一標題欄截圖*@parampic2圖二scrollview截圖*@paramcontext用於通知重新掃描文件系統,為提升性能可去掉*詳情見說明:{@linkcom.bertadata.qxb.util.ScreenShotUtils}*/publicstaticvoidsavingBitmapIntoFile(finalBitmappic1,finalBitmappic2,finalActivitycontext,finalBitmapAndFileCallBackcallBack){if(context==null||context.isFinishing()){return;}Threadthread=newThread(newRunnable(){@Overridepublicvoidrun(){StringfileReturnPath="";intw=pic1.getWidth();Bitmapbottom=BitmapFactory.decodeResource(context.getResources(),R.drawable.ic_picture_combine_bottom);Bitmaptop_banner=BitmapFactory.decodeResource(context.getResources(),R.drawable.ic_picture_combine_top);Bitmapbitmap_bottom=anyRatioCompressing(bottom,(float)w/bottom.getWidth(),(float)w/bottom.getWidth());Bitmapbitmap_top=anyRatioCompressing(top_banner,(float)w/bottom.getWidth(),(float)w/bottom.getWidth());finalBitmaponly_bitmap=combineBitmapsIntoOnlyOne(bitmap_top,pic1,pic2,bitmap_bottom,context);//獲取當前時間SimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-dd-HH-mm-ss-ms",Locale.getDefault());Stringdata=sdf.format(newDate());//獲取記憶體路徑//設置圖片路徑+命名規範//聲明輸出文件StringstoragePath=Environment.getExternalStorageDirectory().getAbsolutePath();StringfileTitle="Screenshot_"+data+"_com.bertadata.qxb.biz_info.jpg";StringfilePath=storagePath+"/DCIM/";finalStringfileAbsolutePath=filePath+fileTitle;Filefile=newFile(fileAbsolutePath);/***質壓與比壓結合*分級壓縮*輸出文件*/if(only_bitmap!=null){try{//首先,對原圖進行一步質量壓縮,形成初步文件FileOutputStreamfos=newFileOutputStream(file);only_bitmap.compress(Bitmap.CompressFormat.JPEG,50,fos);//另建一個文件other_file預備輸出Stringother_fileTitle="Screenshot_"+data+"_com.bertadata.qxb.jpg";Stringother_fileAbsolutePath=filePath+other_fileTitle;Fileother_file=newFile(other_fileAbsolutePath);FileOutputStreamother_fos=newFileOutputStream(other_file);//其次,要判斷質壓之後的文件大小,按文件大小分級進行處理longfile_size=file.length()/1024;//sizeoffile(KB)if(file_size<0||!(file.exists())){//零級:文件判空thrownewNullPointerException();}elseif(file_size>0&&file_size<=256){//一級:直接輸出deleteFile(other_file);//通知刷新文件系統,顯示最新截取的圖文件fileReturnPath=fileAbsolutePath;context.sendBroadcast(newIntent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,Uri.parse("file://"+fileAbsolutePath)));}elseif(file_size>256&&file_size<=768){//二級:簡單壓縮:壓縮為原比例的3/4,質壓為50%anyRatioCompressing(only_bitmap,(float)3/4,(float)3/4).compress(Bitmap.CompressFormat.JPEG,40,other_fos);deleteFile(file);//通知刷新文件系統,顯示最新截取的圖文件fileReturnPath=other_fileAbsolutePath;context.sendBroadcast(newIntent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,Uri.parse("file://"+other_fileAbsolutePath)));}elseif(file_size>768&&file_size<=1280){//三級:中度壓縮:壓縮為原比例的1/2,質壓為40%anyRatioCompressing(only_bitmap,(float)1/2,(float)1/2).compress(Bitmap.CompressFormat.JPEG,40,other_fos);deleteFile(file);//通知刷新文件系統,顯示最新截取的圖文件fileReturnPath=other_fileAbsolutePath;context.sendBroadcast(newIntent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,Uri.parse("file://"+other_fileAbsolutePath)));}elseif(file_size>1280&&file_size<=2048){//四級:大幅壓縮:壓縮為原比例的1/3,質壓為40%anyRatioCompressing(only_bitmap,(float)1/3,(float)1/3).compress(Bitmap.CompressFormat.JPEG,40,other_fos);deleteFile(file);//通知刷新文件系統,顯示最新截取的圖文件fileReturnPath=other_fileAbsolutePath;context.sendBroadcast(newIntent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,Uri.parse("file://"+other_fileAbsolutePath)));}elseif(file_size>2048){//五級:中度壓縮:壓縮為原比例的1/2,質壓為40%anyRatioCompressing(only_bitmap,(float)1/2,(float)1/2).compress(Bitmap.CompressFormat.JPEG,40,other_fos);deleteFile(file);//通知刷新文件系統,顯示最新截取的圖文件fileReturnPath=other_fileAbsolutePath;context.sendBroadcast(newIntent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,Uri.parse("file://"+other_fileAbsolutePath)));}//註銷fos;fos.flush();other_fos.flush();other_fos.close();fos.close();//callback用於回傳保存成功的路徑以及BitmapcallBack.onSuccess(only_bitmap,fileReturnPath);}catch(Exceptione){e.printStackTrace();}}elsecallBack.onSuccess(null,"");}});thread.start();}/***可做到任意寬高比例壓縮(寬高壓比可不同)的壓縮方法(主要用於微壓)**@parambitmap源圖*@paramwidth_ratio寬壓比(float)(0<&&<1)*@paramheight_ratio高壓比(float)(0<&&<1)*@return目標圖片*<p>*/publicstaticBitmapanyRatioCompressing(Bitmapbitmap,floatwidth_ratio,floatheight_ratio){Matrixmatrix=newMatrix();matrix.postScale(width_ratio,height_ratio);returnBitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,false);}

推薦↓↓↓

?16個技術公眾號】都在這里!

涵蓋:工程師大咖、源碼共讀、工程師共讀、數據結構與算法、黑客技術和網路安全、大數據科技、編程前端、Java、Python、Web編程開發、Android、iOS開發、Linux、數據庫研發、幽默工程師等。

分享到Facebook

 


不知道如何找適合的對象?歡迎加官方LINE → Line ID:@shesay
戀愛小秘書免費一對一諮詢!
✔追蹤我的YouTube:https://www.youtube.com/@datenami
✔追蹤我的TikTok:https://www.tiktok.com/@datnami

 

配對成功的關鍵:參加實體交友活動

erose主題派對與戀愛小秘書創辦人娜米表示:「透過各種有趣的實體活動,不僅能親眼真實見到異性,也能在活動進行中讓大家很輕鬆自然的認識彼此、聊天互動,能更快速的找到適合的對象。」

結合大數據用心篩選 + 客製化條件配對

戀愛小秘書團隊已經成功替4000位以上的未婚男女配對成功,這個驚人成果背後的秘密在於「高度客製化服務」,跟每位客戶深度訪談,瞭解客戶真正的特質及需求,從「契合度」提高速配率。

訪談結果結合專屬的人格分析測驗與數據配對分析,精緻化的操作,締造高速配率!

除此之外,戀愛小秘書團隊還會定期追蹤客戶的後續狀況,目的是希望協助客戶發展長期且穩定的伴侶關係。

實名認證防造假!隱私保護最安心!

採用「實名認證」的制度,不僅是把關顧客的身份,避免已婚人士或動機不單純者的加入,更對客戶資料嚴格保密,讓客戶們能在安全且有隱私的狀況下認識另一半。

多元有趣的主題活動,豐富你的社交生活

戀愛小秘書團隊每個月都會規劃豐富多元的實體活動,從戶外踏青、娛樂遊戲、手作、料理課程到桌遊活動,希望客戶們能從歡樂的氣氛中認識彼此。

透過實體活動讓大家先有初步的接觸,然後再為會員們做「客製化」的約會安排。

另外針對想提升自身魅力的客戶,也有投資理財、形象穿搭等講座可供選擇。

追求脫單,先勇敢跨出你的第一步

許多單身者為了心中理想的對象條件,在還沒認識新朋友時,就先限制了自己。建議以認識新朋友的心態,積極參與活動,並適當的設限,才能真正為自己帶來戀愛的機會!勇敢跨出第一步吧!

♡ 現在就和戀愛小秘書娜米聊聊吧Line ID:@shesay

♡ 追蹤娜米的臉書粉絲團

她來報好康

 

SheSay 專注在 兩性、愛情等領域
建立專屬女生觀點的品牌形象
堅持「在第一時間掌握男女的時事議題」
將時下最流行的話題網羅、呈現。

馬上測算你的戀愛密碼

戀愛小秘書-娜米

單身很久?一直被分手?
從生日就看出你的戀愛疑難雜症!
娜米的戀愛數字密碼來幫你了。