前言:
由于公司需要做一个头像的裁剪功能,自己写了一个。此代码实现的功能没有git上提供的那么强大和炫酷,但是仅仅用了300多行的代码,希望能给想要理解和自动动手修改的开发者,带来帮助。
大体样子如下:
下面先放上主要的方法进行介绍,全部代码可自行在git上下载。
-
onDraw:自定义view来画图的方法,通过方法中的canvas来实现将图片画到屏幕上
-
isInClipCircle:判断图片的四个边是否有某条边在阴影方框之内。当与边框重合时就不能在移动图片了
-
onTouchEvent:触屏的操作,如按下,拖动,缩放
-
showImage:主要用于用来接收要处理的bitmap或者图片路径,然后会使用传进来的图片作为bitmap进行处理,然后显示
-
rotate90:实现图片的旋转
-
setViewRectF:获取我们自定义的view的宽高
-
setClipRectDefaultPosition:设置截切框的位置,代码中与setViewF方法中的设置的一样。也就是将截切矿设置为与view一样大。
-
setImageDefaultPosition:设置图片第一次显示的位置。代码中设置的是将图片的中心为基准显示在上面
-
getClipRectImage:获取剪切框中的图片
-
moveImage:用来移动图片
-
scaleImage:用来缩放图片
下面把整个代码贴出来:
package jp.co.sharp.android.parents.kidsguard.activity.profile.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.Xfermode;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ImageView;
import java.util.stream.LongStream;
@SuppressLint("AppCompatCustomView")
public class CorpToView extends ImageView {
private static final int NONE = 0;
private static final int DRAG = 1;
private static final int ZOOM = 2;
private int mood = NONE;
private Matrix matrix = new Matrix();
private Matrix currentMatrix = new Matrix();
private PointF startPoint = new PointF();
private PointF lastPoint = new PointF();
private PointF centerPointForZoom;
private float twoFingerDistanceBeforeZoom;
private RectF viewRectF;
private String mImagePath;
private Bitmap mBmpToCrop;
private RectF clipRect;
private Paint clipCirclePaint;
private Paint clipCircleBorderPaint;
private Xfermode xfermode;
private int touchPosition;
private boolean isClip = false;
private boolean isFirstDraw = true;
public CorpToView(Context context) {
super(context);
init(context);
}
public CorpToView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
Paint mBmpPaint;
mBmpPaint = new Paint();
mBmpPaint.setAntiAlias(true);
mBmpPaint.setFilterBitmap(true);
clipCirclePaint = new Paint();
clipCirclePaint.setAntiAlias(true);
clipCircleBorderPaint = new Paint();
clipCircleBorderPaint.setStyle(Style.STROKE);
clipCircleBorderPaint.setColor(Color.parseColor("#4D575F"));
clipCircleBorderPaint.setStrokeWidth(3);
clipCircleBorderPaint.setAntiAlias(true);
xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
}
@Override
protected void onDraw(Canvas canvas) {
if (mBmpToCrop == null) return;
if(isFirstDraw) {
setViewRectF();
setClipRectDefaultPosition();
setImageDefaultPosition();
isFirstDraw = false;
}
if (mBmpToCrop != null) {
canvas.drawBitmap(mBmpToCrop, matrix, null);
currentMatrix.set(matrix);
if(!isClip) {
//通过Xfermode的DST_OUT来产生中间的透明裁剪区域,一定要另起一个Layer(层)
canvas.saveLayer(0, 0, this.getWidth(), this.getHeight(), null, Canvas.ALL_SAVE_FLAG);
canvas.drawColor(Color.parseColor("#6F010101")); //#a8000000
//中间的透明的圆
clipCirclePaint.setXfermode(xfermode);
canvas.drawCircle(this.clipRect.centerX(), this.clipRect.centerY(), this.getHeight()/2, clipCirclePaint);
//白色的圆边框
canvas.drawCircle(this.clipRect.centerX(), this.clipRect.centerY(), this.getHeight()/2, clipCircleBorderPaint);
}
}
}
private boolean isInClipCircle(Matrix myMatrix) {
float[] myMatrixvalues = new float[9];
myMatrix.getValues(myMatrixvalues);
int imageOutClipLeft = Math.round(myMatrixvalues[2]);
int imageOutClipTop = Math.round(myMatrixvalues[5]);
System.out.println("[isInClipCircle中matirx]的left:" + imageOutClipLeft);
System.out.println("[isInClipCircle中matirx]的top::" + imageOutClipTop);
float scale = myMatrixvalues[0];
int imageOUtClipBootom = Math.round(mBmpToCrop.getHeight() * scale) - Math.abs(imageOutClipTop) - Math.round(clipRect.height());
int imageOUtClipRight = Math.round(mBmpToCrop.getWidth()* scale) - Math.abs(imageOutClipLeft) - Math.round(clipRect.width());
System.out.println("======================================================");
System.out.println("[isInClipCircle中matirx]的Right:" + imageOUtClipRight);
System.out.println("[isInClipCircle中matirx]的Bootem:" + imageOUtClipBootom);
if(imageOutClipTop > 0) {
return true;
}
if(imageOutClipLeft > 0) {
return true;
}
if(imageOUtClipBootom < 0) {
return true;
}
if(imageOUtClipRight < 0) {
return true;
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK){
case MotionEvent.ACTION_DOWN:
mood = DRAG;
startPoint.set(event.getX(), event.getY());
lastPoint.set(event.getX(),event.getY());
currentMatrix.set(matrix);
break;
case MotionEvent.ACTION_MOVE :
if (mood == DRAG){
moveImage(event);
}else if (mood == ZOOM){
scaleImage(event);
}
lastPoint.set(event.getX(),event.getY());
break;
case MotionEvent.ACTION_UP:
mood = NONE;
break;
case MotionEvent.ACTION_POINTER_UP:
mood = NONE;
break;
case MotionEvent.ACTION_POINTER_DOWN:
mood = ZOOM;
twoFingerDistanceBeforeZoom = calculateFingersSlideDistance(event);
if (twoFingerDistanceBeforeZoom > 10f) {
centerPointForZoom = calculateCenterPointForZoom(event);
currentMatrix.set(matrix);
}
break;
}
if(!isInClipCircle(matrix)){
invalidate();
}
return true;
}
/**
* show the origin image by the image path that user give
* @param picPath
*/
public void showImage(String picPath) {
this.mImagePath = picPath;
mBmpToCrop = BitmapFactory.decodeFile(mImagePath);
invalidate();
}
public void showImage(Bitmap bitmap) {
mBmpToCrop =bitmap;
invalidate();
}
public void rotate90(){
//matrix.setRotate(90,mBmpToCrop.getWidth()/2,mBmpToCrop.getHeight()/2);
matrix.setRotate(90,mBmpToCrop.getWidth()/2,mBmpToCrop.getHeight()/2);
Bitmap bitmap = Bitmap.createBitmap(mBmpToCrop,0,0,mBmpToCrop.getWidth(),mBmpToCrop.getHeight(),matrix,true);
mBmpToCrop = bitmap;
Matrix matrix1 = new Matrix();
matrix.set(matrix1);
setImageDefaultPosition();
invalidate();
}
private void setViewRectF() {
viewRectF = new RectF();
viewRectF.left = 0;
viewRectF.top = 0;
viewRectF.right = getWidth();
viewRectF.bottom = getHeight();
}
private void setClipRectDefaultPosition() {
final float CLIP_RECT_WIDTH = 200f;
final float CLIP_RECT_HEIGHT = 200f;
clipRect = new RectF();
/* clipRect.left = (viewRectF.width() - CLIP_RECT_WIDTH) / 2;
clipRect.top = (viewRectF.height() - CLIP_RECT_HEIGHT) / 2;
clipRect.right = clipRect.left + CLIP_RECT_WIDTH;;
clipRect.bottom = clipRect.top + CLIP_RECT_HEIGHT;*/
clipRect.left = viewRectF.left;
clipRect.top = viewRectF.top;
clipRect.right = viewRectF.right;
clipRect.bottom = viewRectF.bottom;
System.out.println("裁剪框的left:" + clipRect.left);
System.out.println("裁剪框的top:" + clipRect.top);
}
private void setImageDefaultPosition () {
float dx = 0;
float dy = 0;
// image less than clip circle
if(mBmpToCrop.getWidth() < viewRectF.width()) {
dx = (viewRectF.width() - mBmpToCrop.getWidth()) / 2;
}
if(mBmpToCrop.getHeight() < viewRectF.height()) {
dy = (viewRectF.height() - mBmpToCrop.getHeight()) / 2;
}
// image bigger than clip circle
if(mBmpToCrop.getWidth()>=viewRectF.width() && mBmpToCrop.getHeight()>=viewRectF.height()) {
dx = (viewRectF.width() - mBmpToCrop.getWidth()) / 2;
dy = (viewRectF.height() - mBmpToCrop.getHeight()) / 2;
}
System.out.println("center dx:" + dx);
System.out.println("center dy:" + dy);
matrix.postTranslate(Math.round(dx),Math.round(dy));
}
/**
* get the image in the clip rect area
* @return
*/
public Bitmap getClipRectImage() {
isClip = true;
destroyDrawingCache();
setDrawingCacheEnabled(true);
buildDrawingCache();
Bitmap mBitmap = getDrawingCache();
isClip = false;
return Bitmap.createBitmap(mBitmap, (int) clipRect.left, (int) clipRect.top, (int) clipRect.width(), (int) clipRect.height());
}
public void showCilpRectImage() {
isClip = true;
destroyDrawingCache();
setDrawingCacheEnabled(true);
buildDrawingCache();
Bitmap mBitmap = getDrawingCache();
mBmpToCrop =Bitmap.createBitmap(mBitmap, (int) clipRect.left, (int) clipRect.top, (int) clipRect.width(), (int) clipRect.height());
matrix = new Matrix();
isClip = false;
invalidate();
}
private void moveImage (MotionEvent event) {
int dx = Math.round(event.getX() - startPoint.x);
int dy = Math.round(event.getY() - startPoint.y);
Matrix myMatrix = new Matrix();
myMatrix.set(matrix);
myMatrix.postTranslate(Math.round(dx), Math.round(dy));
if(isInClipCircle(myMatrix)) {
return;
}
matrix.set(currentMatrix);
matrix.postTranslate(Math.round(dx), Math.round(dy));
startPoint.x = event.getX() ;
startPoint.y = event.getY() ;
}
private PointF calculateCenterPointForZoom(MotionEvent event) {
float midx = event.getX(1) + event.getX(0);
float midy = event.getY(1) + event.getY(0);
return new PointF(midx/2,midy/2);
}
private float calculateFingersSlideDistance(MotionEvent event) {
float dx = event.getX(1) - event.getX(0);
float dy = event.getY(1) - event.getY(0);
return (float)Math.sqrt(dx * dx + dy * dy);
}
private void scaleImage(MotionEvent event) {
float twoFingerDistanceAfterZoom = calculateFingersSlideDistance(event);
if (twoFingerDistanceAfterZoom > 10f) {
float scale = twoFingerDistanceAfterZoom / twoFingerDistanceBeforeZoom;
Matrix myMatrix = new Matrix();
myMatrix.set(matrix);
myMatrix.postScale(scale, scale, centerPointForZoom.x, centerPointForZoom.y);
if(isInClipCircle(myMatrix)) {
return;
}
matrix.set(currentMatrix);
matrix.postScale(scale, scale, centerPointForZoom.x, centerPointForZoom.y);
}
}
}
关于代码的使用:
第一步:设置Activity布局文件,在布局文件中直接引用我们自定义的这个view,如下:
第二步:在Activity中直接调用方法即可,比如将从相册中选择的图片进行裁剪,只需要在onActivityResult方法中进行调用即可,如下:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case 1:
try {
Uri imageUri = data.getData();
Point screenSize = ImageUtils.getScreenSize(this);
Bitmap scaledBitmap = ImageUtils.decodeUriToScaledBitmap(this, imageUri, screenSize.x, screenSize.y);
imageView.showImage(scaledBitmap);
} catch (Exception e) {
}
break;
}
上面的代码是通过直接将bitmap放到自定义view中进行裁剪,但是直接使用bitmap,如果图片太大时,有可能导致内存溢出(oom).所以可以是先用下面方法,通过文件名路径的形式。
/**
*使用这种文件的方式,如果是从相机的返回是,Intent会是null,所以不要用Intent是否为null做判断。
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.i(TAG, "ProfileCameraActivity_onActivityResult");
//20200918 add [clip the picture from the camera] start
switch (requestCode) {
case REQUEST_CODE_CAMERA: //调用系统相机返回
if (resultCode == RESULT_OK) {
Uri uri = Uri.fromFile(cameraPictureTempFile);
//getRealFilePathFromUri的工具类,下面贴上
String path = getRealFilePathFromUri(this, uri);
imageView.showImage(path );
return;
}
break;
}
}
FiltUitl.java
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.MediaStore;
import android.text.TextUtils;
import java.io.File;
/**
* 文件工具类
*/
public class FileUtil {
/**
* 根据Uri返回文件绝对路径
* 兼容了file:///开头的 和 content://开头的情况
*/
public static String getRealFilePathFromUri(final Context context, final Uri uri) {
if (null == uri) return null;
final String scheme = uri.getScheme();
String data = null;
if (scheme == null) {
data = uri.getPath();
}
else if (ContentResolver.SCHEME_FILE.equalsIgnoreCase(scheme)) {
data = uri.getPath();
} else if (ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(scheme)) {
Cursor cursor = context.getContentResolver().query(uri, new String[]{MediaStore.Images.ImageColumns.DATA}, null, null, null);
if (null != cursor) {
if (cursor.moveToFirst()) {
int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
if (index > -1) {
data = cursor.getString(index);
}
}
cursor.close();
}
}
return data;
}
/**
* 检查文件是否存在
*/
public static String checkDirPath(String dirPath) {
if (TextUtils.isEmpty(dirPath)) {
return "";
}
File dir = new File(dirPath);
if (!dir.exists()) {
dir.mkdirs();
}
return dirPath;
}
}
通过上面几步,功能就实现了,可以运行看效果了。
所有代码请移步:
git:https://github.com/GitHubProfessor/easyClip
码云:https://gitee.com/kyle_lhb/easyClip