Android圖片裁剪功能實(shí)現(xiàn)代碼
在Android應(yīng)用中,圖片裁剪也是一個(gè)經(jīng)常用到的功能。Android系統(tǒng)中可以用隱式意圖調(diào)用系統(tǒng)應(yīng)用進(jìn)行裁剪,但是這樣做在不同的手機(jī)可能表現(xiàn)出不同的效果,甚至在某些奇葩手機(jī)上還會(huì)出其他更奇怪的問(wèn)題,所以調(diào)用系統(tǒng)功能進(jìn)行圖片裁剪在很多時(shí)候?qū)ξ覀儊?lái)說(shuō)并不是一個(gè)好的選擇。這時(shí)候就需要我們自己去實(shí)現(xiàn)這種裁剪功能了。
功能分析
要完成圖片裁剪的功能,我們需要先知道圖片裁剪的功能有哪些。圖片裁剪之前,我們需要有一個(gè)框指示我們需要裁剪的樣式合大小。圖片顯示出來(lái)后大小和位置可能并不是我們所期望的,所以我們還需要對(duì)圖片進(jìn)行移動(dòng)、縮放等操作。確定好位置和大小后,我們需要真正的對(duì)圖片進(jìn)行裁剪,并將裁剪的圖片存起來(lái)以供使用。也就是說(shuō)需要實(shí)現(xiàn)圖片裁剪的功能細(xì)分后如下:
1、顯示指示框
2、圖片移動(dòng)和縮放
3、圖片裁剪并保存
最終效果展示如下:

功能實(shí)現(xiàn)
顯示指示框
要實(shí)現(xiàn)顯示一個(gè)如上圖一樣的指示框有很多方法,這里實(shí)現(xiàn)的方式是用自定義的Drawable作為View的背景,然后將這個(gè)View覆蓋在原圖片上作為指示框。為了在一定程度上滿(mǎn)足更多的要求,我們讓指示框可設(shè)置為矩形也可設(shè)置為圓形,陰影區(qū)域的顏色也可設(shè)置。
要繪制出作為指示的圖層,我們可以將它拆分成兩半,變成兩個(gè)封閉的Path進(jìn)行繪制,也可以先繪制出半透明的覆蓋層,然后在中間裁剪一個(gè)洞。顯然,要考慮到這個(gè)洞的形狀大小并不是固定的,裁剪的方式比拆分成兩個(gè)封閉的Path要簡(jiǎn)單多了。
Canvas的canvas.clipPath(Path, Region.Op);方法,可以對(duì)Canvas進(jìn)行裁剪,可以很容易得到這樣的指示框。然而Canvas的clipPath裁剪出來(lái)的曲線圖形會(huì)有鋸齒,我多番嘗試都沒(méi)能去掉鋸齒,所以不得不放棄這個(gè)方法。繼而利用paint的paint.setXfermode(new PorterDuffXfermode(mode))方法來(lái)實(shí)現(xiàn)這個(gè)效果。
Android 4.4的Path增加了裁剪功能,我們可以直接用Path的path.op(Path, Path.Op)方法將Path裁剪成我們需要的形狀再進(jìn)行繪制,這種方式效率更高。
指示框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的操作不能少,否則不會(huì)得到想要的效果
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);
//裁剪的方式會(huì)有鋸齒,沒(méi)找到方法去掉鋸齒
//canvas.clipPath(opPath, Region.Op.DIFFERENCE);
//canvas.drawRect(0,0,cWidth,cHeight,paint);
}
}
圖片移動(dòng)和縮放
圖片的移動(dòng)和縮放功能,在預(yù)覽看大圖的時(shí)候也會(huì)用到,在進(jìn)行圖片裁剪時(shí),我們需要對(duì)圖片的移動(dòng)和縮放范圍進(jìn)行限定,禁止圖片操作完成后出現(xiàn)超出指示框(根據(jù)需求也有在操作過(guò)程中就不允許超出指示框的情況)。ImageView有一個(gè)ImageView.setImageMatrix(Matrix)方法,可以直接設(shè)置圖片的變換矩陣。所以我們也可以利用這個(gè)方法,結(jié)合ImageView的OnTouchListener監(jiān)聽(tīng),來(lái)做圖片的移動(dòng)和縮放處理。
移動(dòng)縮放核心代碼如下:
@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) {
//一個(gè)手指按下時(shí),標(biāo)記為移動(dòng)模式
case MotionEvent.ACTION_DOWN:
matrix.set(view.getImageMatrix());
savedMatrix.set(matrix);
start.set(event.getX(), event.getY());
mode = DRAG;
break;
//第二個(gè)手指按下時(shí),標(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;
//手指移動(dòng)時(shí),根據(jù)當(dāng)前是移動(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)手指抬起時(shí),我們需要對(duì)圖片當(dāng)前狀態(tài)進(jìn)行判斷,避免在限定范圍中存在無(wú)圖片區(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]);
}
}
}
裁剪和保存
將圖片縮放移動(dòng)操作到我們預(yù)期的大小和位置后,我們就可以將出現(xiàn)在指示框內(nèi)的區(qū)域裁剪出來(lái)了。我們有兩種方式,將這個(gè)區(qū)域裁剪出來(lái),一種是對(duì)原圖進(jìn)行裁剪,另外一種是對(duì)ImageView展示出來(lái)的圖片進(jìn)行裁剪。當(dāng)原圖過(guò)大或者圖片是網(wǎng)絡(luò)圖片等情況時(shí),對(duì)原圖裁剪并不是我們所期望的,而且相對(duì)直接對(duì)ImageView展示的內(nèi)容進(jìn)行裁剪,對(duì)原圖進(jìn)行裁剪還需要我們?nèi)ビ?jì)算我們所期望的區(qū)域在原圖上的位置。所以我們還是直接對(duì)ImageView展示出來(lái)的圖片進(jìn)行裁剪,然后得到裁剪結(jié)果比較方便。當(dāng)然,如果這個(gè)裁剪本來(lái)就是希望對(duì)原圖進(jìn)行處理的話(huà),那就只能裁剪原圖了。
View有View.getDrawingCache()的方法,可以得到當(dāng)前View展示的內(nèi)容,它返回一個(gè)Bitmap。需要注意的是,在使用View.getDrawingCache()前,我們需要調(diào)用View.setDrawingCacheEnabled(true)來(lái)開(kāi)啟繪制緩存,否則無(wú)法得到當(dāng)前的View所展示的內(nèi)容。使用完畢后,再調(diào)用View.setDrawingCacheEnabled(false)關(guān)閉繪制緩存,否則下次調(diào)用View.getDrawingCache()時(shí),得到的是之前的內(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();
}
源碼
博客中代碼片段的完整類(lèi),以代碼段的形式放在了CSDN代碼筆記中,有需要的朋友自行建立工程使用相關(guān)類(lèi)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android實(shí)現(xiàn)拍照、選擇圖片并裁剪圖片功能
- Android裁剪圖片為圓形圖片的實(shí)現(xiàn)原理與代碼
- 解決Android從相冊(cè)中獲取圖片出錯(cuò)圖片卻無(wú)法裁剪問(wèn)題的方法
- Android實(shí)現(xiàn)從本地圖庫(kù)/相機(jī)拍照后裁剪圖片并設(shè)置頭像
- Android 7.0中拍照和圖片裁剪適配的問(wèn)題詳解
- Android自定義View實(shí)現(xiàn)照片裁剪框與照片裁剪功能
- Android實(shí)現(xiàn)拍照及圖片裁剪(6.0以上權(quán)限處理及7.0以上文件管理)
- Android編程實(shí)現(xiàn)調(diào)用系統(tǒng)圖庫(kù)與裁剪圖片功能
- Android拍照或從圖庫(kù)選擇圖片并裁剪
- android實(shí)現(xiàn)拖拽裁剪功能
相關(guān)文章
android 9.0 launcher3 去掉抽屜式顯示所有 app(代碼詳解)
本文通過(guò)實(shí)例代碼給大家介紹了android 9.0 Launcher3 去掉抽屜式,顯示所有 app,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-11-11
logcat命令使用方法和查看android系統(tǒng)日志緩沖區(qū)內(nèi)容的方法
這篇文章主要介紹了logcat命令使用方法和查看android系統(tǒng)日志緩沖區(qū)內(nèi)容的方法,需要的朋友可以參考下2014-02-02
詳解Android Material設(shè)計(jì)中陰影效果的實(shí)現(xiàn)方法
這篇文章主要介紹了Android Material設(shè)計(jì)中陰影效果的實(shí)現(xiàn)方法,包括自定義陰影的輪廓和裁剪等,需要的朋友可以參考下2016-04-04
Android實(shí)現(xiàn)調(diào)用震動(dòng)的方法
這篇文章主要介紹了Android實(shí)現(xiàn)調(diào)用震動(dòng)的方法,實(shí)例分析了Android中Vibrator類(lèi)的調(diào)用與使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11
AndroidX下使用Activity和Fragment的變化詳解
這篇文章主要介紹了AndroidX下使用Activity和Fragment的變化詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
Jetpack Compose實(shí)現(xiàn)對(duì)話(huà)框和進(jìn)度條實(shí)例解析
對(duì)話(huà)框和進(jìn)度條其實(shí)并無(wú)多大聯(lián)系,放在一起寫(xiě)是因?yàn)閮烧叩膬?nèi)容都不多,所以湊到一起,對(duì)話(huà)框是我們平時(shí)開(kāi)發(fā)使用得比較多的組件,進(jìn)度條的使用頻率也很高,比如下載文件,上傳文件,處理任務(wù)時(shí)都可以使用進(jìn)度條2023-04-04
Android 實(shí)現(xiàn)文字左右對(duì)齊
這篇文章主要介紹了Android 實(shí)現(xiàn)文字左右對(duì)齊效果的方法,幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2021-05-05
Android IPC機(jī)制ACtivity綁定Service通信代碼實(shí)例
這篇文章主要介紹了Android IPC機(jī)制ACtivity綁定Service通信代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09
ActivityManagerService廣播注冊(cè)與發(fā)送示例解析
這篇文章主要為大家介紹了ActivityManagerService廣播注冊(cè)與發(fā)送示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
Android編程實(shí)現(xiàn)壓縮圖片并加載顯示的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)壓縮圖片并加載顯示的方法,涉及Android開(kāi)發(fā)中圖片的運(yùn)算、壓縮處理操作及界面布局顯示壓縮圖片等相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-10-10

