編輯:關於Android編程
在Android應用中,圖片裁剪也是一個經常用到的功能。Android系統中可以用隱式意圖調用系統應用進行裁剪,但是這樣做在不同的手機可能表現出不同的效果,甚至在某些奇葩手機上還會出其他更奇怪的問題,所以調用系統功能進行圖片裁剪在很多時候對我們來說並不是一個好的選擇。這時候就需要我們自己去實現這種裁剪功能了。
功能分析
要完成圖片裁剪的功能,我們需要先知道圖片裁剪的功能有哪些。圖片裁剪之前,我們需要有一個框指示我們需要裁剪的樣式合大小。圖片顯示出來後大小和位置可能並不是我們所期望的,所以我們還需要對圖片進行移動、縮放等操作。確定好位置和大小後,我們需要真正的對圖片進行裁剪,並將裁剪的圖片存起來以供使用。也就是說需要實現圖片裁剪的功能細分後如下:
1、顯示指示框
2、圖片移動和縮放
3、圖片裁剪並保存
最終效果展示如下:
功能實現
顯示指示框
要實現顯示一個如上圖一樣的指示框有很多方法,這裡實現的方式是用自定義的Drawable作為View的背景,然後將這個View覆蓋在原圖片上作為指示框。為了在一定程度上滿足更多的要求,我們讓指示框可設置為矩形也可設置為圓形,陰影區域的顏色也可設置。
要繪制出作為指示的圖層,我們可以將它拆分成兩半,變成兩個封閉的Path進行繪制,也可以先繪制出半透明的覆蓋層,然後在中間裁剪一個洞。顯然,要考慮到這個洞的形狀大小並不是固定的,裁剪的方式比拆分成兩個封閉的Path要簡單多了。
Canvas的canvas.clipPath(Path, Region.Op);方法,可以對Canvas進行裁剪,可以很容易得到這樣的指示框。然而Canvas的clipPath裁剪出來的曲線圖形會有鋸齒,我多番嘗試都沒能去掉鋸齒,所以不得不放棄這個方法。繼而利用paint的paint.setXfermode(new PorterDuffXfermode(mode))方法來實現這個效果。
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); //已經繪制的可以看做為目標圖 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); } }
圖片移動和縮放
圖片的移動和縮放功能,在預覽看大圖的時候也會用到,在進行圖片裁剪時,我們需要對圖片的移動和縮放范圍進行限定,禁止圖片操作完成後出現超出指示框(根據需求也有在操作過程中就不允許超出指示框的情況)。ImageView有一個ImageView.setImageMatrix(Matrix)方法,可以直接設置圖片的變換矩陣。所以我們也可以利用這個方法,結合ImageView的OnTouchListener監聽,來做圖片的移動和縮放處理。
移動縮放核心代碼如下:
@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; //手指移動時,根據當前是移動模式還是縮放模式做相應處理 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; }
當手指抬起時,我們需要對圖片當前狀態進行判斷,避免在限定范圍中存在無圖片區域:
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]); } } }
裁剪和保存
將圖片縮放移動操作到我們預期的大小和位置後,我們就可以將出現在指示框內的區域裁剪出來了。我們有兩種方式,將這個區域裁剪出來,一種是對原圖進行裁剪,另外一種是對ImageView展示出來的圖片進行裁剪。當原圖過大或者圖片是網絡圖片等情況時,對原圖裁剪並不是我們所期望的,而且相對直接對ImageView展示的內容進行裁剪,對原圖進行裁剪還需要我們去計算我們所期望的區域在原圖上的位置。所以我們還是直接對ImageView展示出來的圖片進行裁剪,然後得到裁剪結果比較方便。當然,如果這個裁剪本來就是希望對原圖進行處理的話,那就只能裁剪原圖了。
View有View.getDrawingCache()的方法,可以得到當前View展示的內容,它返回一個Bitmap。需要注意的是,在使用View.getDrawingCache()前,我們需要調用View.setDrawingCacheEnabled(true)來開啟繪制緩存,否則無法得到當前的View所展示的內容。使用完畢後,再調用View.setDrawingCacheEnabled(false)關閉繪制緩存,否則下次調用View.getDrawingCache()時,得到的是之前的內容。。
裁剪的核心代碼:
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; }
裁剪後,將結果保存到指定目錄:
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代碼筆記中,有需要的朋友自行建立工程使用相關類。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持本站。
網頁繪圖表面創建完成之後,調度器就會請求繪制CC Layer Tree,這樣網頁在加載完成之後就能快速顯示出來。通過CC Layer Tree可以依次找到Graphics
DiskLruCache DiskLruCache是一套硬盤緩存的解決方案,算法同LruCache基於LRU算法。DiskLruCache不是由Google官方編寫的,這
一、簡介 Android應用程序中一般都有多個Activity,在Activity中,通過調用StartActivity方法,並在該方法的參數中傳遞Intent對象,就可
本文實例講述了Android編程自定義Notification的用法。分享給大家供大家參考,具體如下:Notification是一種讓你的應用程序在不使用Activity