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)繪制的可以看做為目標(biāo)圖
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) {
//一個手指按下時,標(biāo)記為移動模式
case MotionEvent.ACTION_DOWN:
matrix.set(view.getImageMatrix());
savedMatrix.set(matrix);
start.set(event.getX(), event.getY());
mode = DRAG;
break;
//第二個手指按下時,標(biāo)記為縮放模式
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ù)當(dāng)前是移動模式還是縮放模式做相應(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;
}
當(dāng)手指抬起時,我們需要對圖片當(dāng)前狀態(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()){ //當(dāng)前寬度小于最小寬度
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()){ //當(dāng)前高度小于最小高度
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展示出來的圖片進行裁剪。當(dāng)原圖過大或者圖片是網(wǎng)絡(luò)圖片等情況時,對原圖裁剪并不是我們所期望的,而且相對直接對ImageView展示的內(nèi)容進行裁剪,對原圖進行裁剪還需要我們?nèi)ビ嬎阄覀兯谕膮^(qū)域在原圖上的位置。所以我們還是直接對ImageView展示出來的圖片進行裁剪,然后得到裁剪結(jié)果比較方便。當(dāng)然,如果這個裁剪本來就是希望對原圖進行處理的話,那就只能裁剪原圖了。
View有View.getDrawingCache()的方法,可以得到當(dāng)前View展示的內(nèi)容,它返回一個Bitmap。需要注意的是,在使用View.getDrawingCache()前,我們需要調(diào)用View.setDrawingCacheEnabled(true)來開啟繪制緩存,否則無法得到當(dāng)前的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)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 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)文章
android 9.0 launcher3 去掉抽屜式顯示所有 app(代碼詳解)
本文通過實例代碼給大家介紹了android 9.0 Launcher3 去掉抽屜式,顯示所有 app,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-11-11
logcat命令使用方法和查看android系統(tǒng)日志緩沖區(qū)內(nèi)容的方法
這篇文章主要介紹了logcat命令使用方法和查看android系統(tǒng)日志緩沖區(qū)內(nèi)容的方法,需要的朋友可以參考下2014-02-02
詳解Android Material設(shè)計中陰影效果的實現(xiàn)方法
這篇文章主要介紹了Android Material設(shè)計中陰影效果的實現(xiàn)方法,包括自定義陰影的輪廓和裁剪等,需要的朋友可以參考下2016-04-04
AndroidX下使用Activity和Fragment的變化詳解
這篇文章主要介紹了AndroidX下使用Activity和Fragment的變化詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
Jetpack Compose實現(xiàn)對話框和進度條實例解析
對話框和進度條其實并無多大聯(lián)系,放在一起寫是因為兩者的內(nèi)容都不多,所以湊到一起,對話框是我們平時開發(fā)使用得比較多的組件,進度條的使用頻率也很高,比如下載文件,上傳文件,處理任務(wù)時都可以使用進度條2023-04-04
Android IPC機制ACtivity綁定Service通信代碼實例
這篇文章主要介紹了Android IPC機制ACtivity綁定Service通信代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-09-09
ActivityManagerService廣播注冊與發(fā)送示例解析
這篇文章主要為大家介紹了ActivityManagerService廣播注冊與發(fā)送示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03

