【Android】图片的旋转拖放移动裁剪

 

前言:

由于公司需要做一个头像的裁剪功能,自己写了一个。此代码实现的功能没有git上提供的那么强大和炫酷,但是仅仅用了300多行的代码,希望能给想要理解和自动动手修改的开发者,带来帮助。
大体样子如下:

 

下面先放上主要的方法进行介绍,全部代码可自行在git上下载。

  1. onDraw:自定义view来画图的方法,通过方法中的canvas来实现将图片画到屏幕上
  2. isInClipCircle:判断图片的四个边是否有某条边在阴影方框之内。当与边框重合时就不能在移动图片了
  3. onTouchEvent:触屏的操作,如按下,拖动,缩放
  4. showImage:主要用于用来接收要处理的bitmap或者图片路径,然后会使用传进来的图片作为bitmap进行处理,然后显示
  5. rotate90:实现图片的旋转
  6. setViewRectF:获取我们自定义的view的宽高
  7. setClipRectDefaultPosition:设置截切框的位置,代码中与setViewF方法中的设置的一样。也就是将截切矿设置为与view一样大。
  8. setImageDefaultPosition:设置图片第一次显示的位置。代码中设置的是将图片的中心为基准显示在上面
  9. getClipRectImage:获取剪切框中的图片
  10. moveImage:用来移动图片
  11. 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值