Android圖片裁剪功能實現(xiàn)代碼
在Android應(yīng)用中,圖片裁剪也是一個經(jīng)常用到的功能。Android系統(tǒng)中可以用隱式意圖調(diào)用系統(tǒng)應(yīng)用進行裁剪,但是這樣做在不同的手機可能表現(xiàn)出不同的效果,甚至在某些奇葩手機上還會出其他更奇怪的問題,所以調(diào)用系統(tǒng)功能進行圖片裁剪在很多時候?qū)ξ覀儊碚f并不是一個好的選擇。這時候就需要我們自己去實現(xiàn)這種裁剪功能了。
功能分析
要完成圖片裁剪的功能,我們需要先知道圖片裁剪的功能有哪些。圖片裁剪之前,我們需要有一個框指示我們需要裁剪的樣式合大小。圖片顯示出來后大小和位置可能并不是我們所期望的,所以我們還需要對圖片進行移動、縮放等操作。確定好位置和大小后,我們需要真正的對圖片進行裁剪,并將裁剪的圖片存起來以供使用。也就是說需要實現(xiàn)圖片裁剪的功能細分后如下:
1、顯示指示框
2、圖片移動和縮放
3、圖片裁剪并保存
最終效果展示如下:
功能實現(xiàn)
顯示指示框
要實現(xiàn)顯示一個如上圖一樣的指示框有很多方法,這里實現(xiàn)的方式是用自定義的Drawable作為View的背景,然后將這個View覆蓋在原圖片上作為指示框。為了在一定程度上滿足更多的要求,我們讓指示框可設(shè)置為矩形也可設(shè)置為圓形,陰影區(qū)域的顏色也可設(shè)置。
要繪制出作為指示的圖層,我們可以將它拆分成兩半,變成兩個封閉的Path進行繪制,也可以先繪制出半透明的覆蓋層,然后在中間裁剪一個洞。顯然,要考慮到這個洞的形狀大小并不是固定的,裁剪的方式比拆分成兩個封閉的Path要簡單多了。
Canvas的canvas.clipPath(Path, Region.Op);方法,可以對Canvas進行裁剪,可以很容易得到這樣的指示框。然而Canvas的clipPath裁剪出來的曲線圖形會有鋸齒,我多番嘗試都沒能去掉鋸齒,所以不得不放棄這個方法。繼而利用paint的paint.setXfermode(new PorterDuffXfermode(mode))方法來實現(xiàn)這個效果。
Android 4.4的Path增加了裁剪功能,我們可以直接用Path的path.op(Path, Path.Op)方法將Path裁剪成我們需要的形狀再進行繪制,這種方式效率更高。
指示框Drawable的核心代碼如下:
@Override public void draw(@NonNull Canvas canvas) { int cWidth=canvas.getWidth(); int cHeight=canvas.getHeight(); if(rect==null){ rect=new Rect(cWidth/2-width/2,cHeight/2-height/2,cWidth/2+width/2,cHeight/2+height/2); } canvas.drawColor(Color.TRANSPARENT); Path path=new Path(); path.addRect(0,0,cWidth,cHeight, Path.Direction.CW); cropPath=new Path(); if(shape==SHAPE_RECT){ cropPath.addRect(rect.left,rect.top,rect.right,rect.bottom, Path.Direction.CW); }else if(shape==SHAPE_CIRCLE){ cropPath.addCircle(rect.centerX(),rect.centerY(),rect.width()/2, Path.Direction.CW); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { //可以抗鋸齒 path.op(cropPath, Path.Op.DIFFERENCE); canvas.drawPath(path,paint); }else{ //此方法可以去掉鋸齒 //在這里saveLayer然后restoreToCount的操作不能少,否則不會得到想要的效果 int layerId = canvas.saveLayer(0, 0, cWidth, cHeight, null, Canvas.ALL_SAVE_FLAG); canvas.drawPath(path,paint); //已經(jīng)繪制的可以看做為目標圖 paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); canvas.drawPath(cropPath,paint); paint.setXfermode(null); canvas.restoreToCount(layerId); //裁剪的方式會有鋸齒,沒找到方法去掉鋸齒 //canvas.clipPath(opPath, Region.Op.DIFFERENCE); //canvas.drawRect(0,0,cWidth,cHeight,paint); } }
圖片移動和縮放
圖片的移動和縮放功能,在預(yù)覽看大圖的時候也會用到,在進行圖片裁剪時,我們需要對圖片的移動和縮放范圍進行限定,禁止圖片操作完成后出現(xiàn)超出指示框(根據(jù)需求也有在操作過程中就不允許超出指示框的情況)。ImageView有一個ImageView.setImageMatrix(Matrix)方法,可以直接設(shè)置圖片的變換矩陣。所以我們也可以利用這個方法,結(jié)合ImageView的OnTouchListener監(jiān)聽,來做圖片的移動和縮放處理。
移動縮放核心代碼如下:
@Override public boolean onTouch(View v, MotionEvent event) { if(v!=null&&((ImageView) v).getDrawable()!=null){ ImageView view = (ImageView) v; Rect rect=view.getDrawable().getBounds(); //事件處理 switch (event.getAction() & MotionEvent.ACTION_MASK) { //一個手指按下時,標記為移動模式 case MotionEvent.ACTION_DOWN: matrix.set(view.getImageMatrix()); savedMatrix.set(matrix); start.set(event.getX(), event.getY()); mode = DRAG; break; //第二個手指按下時,標記為縮放模式 case MotionEvent.ACTION_POINTER_DOWN: oldDist = distance(event); if (oldDist > 10f) { savedMatrix.set(matrix); midPoint(mid, event); mode = ZOOM; } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_UP: checkMatrix(rect); mode = NONE; break; //手指移動時,根據(jù)當前是移動模式還是縮放模式做相應(yīng)處理 case MotionEvent.ACTION_MOVE: if (mode == DRAG) { matrix.set(savedMatrix); matrix.postTranslate(event.getX() - start.x, event.getY() - start.y); } else if (mode == ZOOM) { float newDist = distance(event); if (newDist > 10f) { matrix.set(savedMatrix); float scale = newDist / oldDist; matrix.postScale(scale, scale, mid.x, mid.y); } } break; } view.setImageMatrix(matrix); } return true; }
當手指抬起時,我們需要對圖片當前狀態(tài)進行判斷,避免在限定范圍中存在無圖片區(qū)域:
private void checkMatrix(Rect rect){ if(limit==null&&cropPath!=null){ limit=cropPath.limit(); } if(limit!=null){ if(mode==ZOOM){ matrix.getValues(values); if(rect.width()*values[0]<limit.width()){ //當前寬度小于最小寬度 float scale = limit.width()/(float)rect.width()/values[0]; matrix.postScale(scale, scale, mid.x, mid.y); } matrix.getValues(values); if(rect.height()*values[4]<limit.height()){ //當前高度小于最小高度 float scale = limit.height()/(float)rect.height()/values[4]; matrix.postScale(scale, scale, mid.x, mid.y); } } matrix.getValues(values); if(values[2]>=limit.left){ matrix.postTranslate(limit.left-values[2],0); } matrix.getValues(values); if(values[2]+rect.width()*values[0]<limit.right){ matrix.postTranslate(limit.right-rect.width()*values[0]-values[2],0); } matrix.getValues(values); if(values[5]>limit.top){ matrix.postTranslate(0,limit.top-values[5]); } matrix.getValues(values); if(values[5]+rect.height()*values[4]<limit.bottom){ matrix.postTranslate(0,limit.bottom-rect.height()*values[4]-values[5]); } } }
裁剪和保存
將圖片縮放移動操作到我們預(yù)期的大小和位置后,我們就可以將出現(xiàn)在指示框內(nèi)的區(qū)域裁剪出來了。我們有兩種方式,將這個區(qū)域裁剪出來,一種是對原圖進行裁剪,另外一種是對ImageView展示出來的圖片進行裁剪。當原圖過大或者圖片是網(wǎng)絡(luò)圖片等情況時,對原圖裁剪并不是我們所期望的,而且相對直接對ImageView展示的內(nèi)容進行裁剪,對原圖進行裁剪還需要我們?nèi)ビ嬎阄覀兯谕膮^(qū)域在原圖上的位置。所以我們還是直接對ImageView展示出來的圖片進行裁剪,然后得到裁剪結(jié)果比較方便。當然,如果這個裁剪本來就是希望對原圖進行處理的話,那就只能裁剪原圖了。
View有View.getDrawingCache()的方法,可以得到當前View展示的內(nèi)容,它返回一個Bitmap。需要注意的是,在使用View.getDrawingCache()前,我們需要調(diào)用View.setDrawingCacheEnabled(true)來開啟繪制緩存,否則無法得到當前的View所展示的內(nèi)容。使用完畢后,再調(diào)用View.setDrawingCacheEnabled(false)關(guān)閉繪制緩存,否則下次調(diào)用View.getDrawingCache()時,得到的是之前的內(nèi)容。
裁剪的核心代碼:
public Bitmap crop(){ if(imageView!=null&&cropPath!=null){ if(limit==null){ limit=cropPath.limit(); } Paint paint=new Paint(); paint.setAntiAlias(true); imageView.setDrawingCacheEnabled(true); Bitmap bmp=Bitmap.createBitmap(limit.width(),limit.height(), Bitmap.Config.ARGB_8888); Canvas canvas=new Canvas(bmp); canvas.drawColor(Color.TRANSPARENT); int lId=canvas.saveLayer(0,0,limit.width(),limit.height(),null,Canvas.ALL_SAVE_FLAG); Path path=new Path(); path.addPath(cropPath.path(),-limit.left,-limit.top); canvas.drawPath(path,paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); canvas.drawBitmap(imageView.getDrawingCache(),-limit.left,-limit.top,paint); paint.setXfermode(null); canvas.restoreToCount(lId); imageView.setDrawingCacheEnabled(false); return bmp; } return null; }
裁剪后,將結(jié)果保存到指定目錄:
public String cropAndSave(String path) throws IOException { Bitmap bmp=crop(); if(bmp==null)return null; File file=new File(path); if(!file.getParentFile().exists()){ boolean b=file.mkdirs(); if(!b)return null; } if(file.exists()){ boolean c=file.delete(); if(!c)return null; } FileOutputStream fos=new FileOutputStream(file); bmp.compress(Bitmap.CompressFormat.PNG,100,fos); fos.flush(); fos.close(); bmp.recycle(); return file.getAbsolutePath(); }
源碼
博客中代碼片段的完整類,以代碼段的形式放在了CSDN代碼筆記中,有需要的朋友自行建立工程使用相關(guān)類。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
- Android實現(xiàn)拍照、選擇圖片并裁剪圖片功能
- Android裁剪圖片為圓形圖片的實現(xiàn)原理與代碼
- 解決Android從相冊中獲取圖片出錯圖片卻無法裁剪問題的方法
- Android實現(xiàn)從本地圖庫/相機拍照后裁剪圖片并設(shè)置頭像
- Android 7.0中拍照和圖片裁剪適配的問題詳解
- Android自定義View實現(xiàn)照片裁剪框與照片裁剪功能
- Android實現(xiàn)拍照及圖片裁剪(6.0以上權(quán)限處理及7.0以上文件管理)
- Android編程實現(xiàn)調(diào)用系統(tǒng)圖庫與裁剪圖片功能
- Android拍照或從圖庫選擇圖片并裁剪
- android實現(xiàn)拖拽裁剪功能
相關(guān)文章
AndroidHttpClient詳解及調(diào)用示例
本文給大家介紹AndroidHttpClient結(jié)構(gòu)、使用方式及調(diào)用示例詳解,需要的朋友可以參考下2015-10-1060條Android開發(fā)注意事項與經(jīng)驗總結(jié)
我們在Android App開發(fā)過程中總結(jié)了60條技術(shù)經(jīng)驗注意事項,大家在開發(fā)過程中一定要注意,下面我們來詳細說一下這60條經(jīng)驗2018-03-03Android ListView 和ScroolView 出現(xiàn)onmeasure空指針的解決辦法
這篇文章主要介紹了Android ListView 和ScroolView 出現(xiàn)onmeasure空指針的解決辦法的相關(guān)資料,需要的朋友可以參考下2017-02-02Android中Handler、Thread、HandlerThread三者的區(qū)別
本文主要介紹了Android中Handler、Thread、HandlerThread三者的區(qū)別,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-10-10Android studio無法創(chuàng)建類和接口和提示問題的完美解決辦法
這篇文章主要介紹了Android studio無法創(chuàng)建類和接口和提示問題解決辦法,內(nèi)容比較簡單,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2018-04-04淺談Android應(yīng)用安全防護和逆向分析之a(chǎn)pk反編譯
我們有時候在某個app上見到某個功能,某個效果蠻不錯的,我們想看看對方的思路怎么走的,這時候,我們就可以通過反編譯來編譯該apk,拿到代碼,進行分析。2021-06-06