1. 程式人生 > >Android 可變裁剪區及縮放裁剪圖片

Android 可變裁剪區及縮放裁剪圖片

大多圖片裁剪大多兩種操作:改變裁剪區圖片不能縮放、裁剪區固定圖片縮放,兩種方法都可以裁剪到不同圖片,本次介紹的是可變裁剪區同時能縮放圖片,同時記錄自己的開發專案過程。

裁剪檢視一共三個view,最底層的縮放CilpImageView ,中間是可變裁剪區CilpBorderView,還有最頂層的CilpTouchView。監聽CilpTouchView的OnTouch事件,通過判斷down手勢是否落在拉伸裁剪區的按鈕內分發給CilpBorderView或者CilpImageView 。
Options類是預設裁剪配置。

CilpBorderView類:

public class CilpBorderView
extends View {
private int borderColor = Color.parseColor("#FFFFFF"); private int outSideColor = Color.parseColor("#20000000"); private float borderWidth = 2; private float lineWidth = 1; private Rect[] rects=new Rect[2]; private Paint cutPaint; private Paint outSidePaint; private
RectF cilpRectF; //圖片右座標 private int iconRight=0; //圖片左座標 private int iconLeft=0; private Bitmap bitmap; private int iconOffset; private int width=0; private int height=0; private int verLine1; private int verLine2; private Options options; private static
final int TOP_ICON_ACTION=1; private static final int BOTTOM_ICON_ACTION=2; private int action=-1; private float actionY; public CilpBorderView(Context context) { super(context); initView(); } public CilpBorderView(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public CilpBorderView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(); } private void initView() { cutPaint = new Paint(); cutPaint.setColor(borderColor); cutPaint.setStrokeWidth(borderWidth); cutPaint.setStyle(Paint.Style.STROKE); outSidePaint=new Paint(); outSidePaint.setAntiAlias(true); outSidePaint.setColor(outSideColor); outSidePaint.setStyle(Paint.Style.FILL); bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_crop_drag_y); iconOffset = bitmap.getWidth()/2; options = new Options(); } @Override protected void onDraw(Canvas canvas) { if (cilpRectF == null) { cilpRectF = new RectF(options.paddingWidth, (getHeight() - options.cilpHeight) / 2, getWidth() - options.paddingWidth, (getHeight() + options.cilpHeight) / 2); verLine1 = (int) (cilpRectF.left + cilpRectF.width() / 3); verLine2 = (int) (cilpRectF.left + cilpRectF.width() * 2 / 3); } if (width == 0) width = getWidth(); if (height == 0) height = getHeight(); canvas.save(); drawLine(canvas); drawRound(canvas); drawIcon(canvas); canvas.restore(); super.onDraw(canvas); } private void drawIcon(Canvas canvas) { if (iconLeft == 0 && iconRight == 0) { iconLeft = width / 2 - iconOffset; iconRight=width / 2 + iconOffset; } canvas.drawBitmap(bitmap, iconLeft, cilpRectF.top-iconOffset, null); canvas.drawBitmap(bitmap, iconLeft, cilpRectF.bottom-iconOffset, null); Rect rect=new Rect(iconLeft-options.iconClick, (int)(cilpRectF.top-iconOffset)-options.iconClick,iconRight+options.iconClick, (int)(cilpRectF.top+iconOffset)+options.iconClick); rects[0]=rect; rect=new Rect(iconLeft-options.iconClick,(int)(cilpRectF.bottom-iconOffset)-options.iconClick,iconRight+options.iconClick, (int)(cilpRectF.bottom+iconOffset)+options.iconClick); rects[1]=rect; } private void drawLine(Canvas canvas){ cutPaint.setStrokeWidth(lineWidth); float p = cilpRectF.top + cilpRectF.height() / 3; //橫線 canvas.drawLine(options.paddingWidth, p, width-options.paddingWidth, p, cutPaint); p = cilpRectF.top + cilpRectF.height() * 2 / 3; canvas.drawLine(options.paddingWidth, p, width-options.paddingWidth, p, cutPaint); //豎線 canvas.drawLine(verLine1, cilpRectF.top, verLine1, cilpRectF.bottom, cutPaint); canvas.drawLine(verLine2, cilpRectF.top, verLine2, cilpRectF.bottom, cutPaint); } private void drawRound(Canvas canvas) { //繪製邊框 cutPaint.setStrokeWidth(borderWidth); canvas.drawRect(cilpRectF, cutPaint); //繪製外區域 //左中框 canvas.drawRect(0, cilpRectF.top, options.paddingWidth, cilpRectF.bottom, outSidePaint); //上框 canvas.drawRect(0, 0, width, cilpRectF.top, outSidePaint); //右中框 canvas.drawRect(cilpRectF.right, cilpRectF.top, width, cilpRectF.bottom, outSidePaint); //下框 canvas.drawRect(0, cilpRectF.bottom, width, height, outSidePaint); } public void setOptions(Options options) { this.options = options; } /** * 根據手勢做拉伸 */ public boolean iconOntouch(MotionEvent event,RectF imgRect){ switch (event.getAction()){ case MotionEvent.ACTION_DOWN: if (rects[0].contains((int)event.getX(),(int)event.getY())) { action=TOP_ICON_ACTION; } if (rects[1].contains((int)event.getX(),(int)event.getY())) { action=BOTTOM_ICON_ACTION; } actionY=event.getY(); break; case MotionEvent.ACTION_MOVE: float y=actionY-event.getY(); switch (action){ case TOP_ICON_ACTION: cilpRectF.top=cilpRectF.top-y; break; case BOTTOM_ICON_ACTION: cilpRectF.bottom=cilpRectF.bottom-y; break; } checkBroad(imgRect); actionY=event.getY(); invalidate(); break; case MotionEvent.ACTION_UP: action=-1; break; } return true; } /** * @description 邊界校驗 * @param imgRect */ private void checkBroad(RectF imgRect) { if ((cilpRectF.bottom-cilpRectF.top) < options.min_height){//高度少於最小高度 switch (action) { case TOP_ICON_ACTION: cilpRectF.top=cilpRectF.bottom - options.min_height; break; case BOTTOM_ICON_ACTION: cilpRectF.bottom=cilpRectF.top + options.min_height; break; } } else if ((cilpRectF.bottom-cilpRectF.top) > options.max_height){//高度大於最大高度 switch (action) { case TOP_ICON_ACTION: cilpRectF.top=cilpRectF.bottom - options.max_height; break; case BOTTOM_ICON_ACTION: cilpRectF.bottom=cilpRectF.top + options.max_height; break; } } if (cilpRectF.top < options.paddingHeight) { cilpRectF.top = options.paddingHeight; } if (cilpRectF.bottom > height-options.paddingHeight){ cilpRectF.bottom = height-options.paddingHeight; } if ( cilpRectF.top < imgRect.top) { cilpRectF.top = imgRect.top; } if ( cilpRectF.bottom > imgRect.bottom) { cilpRectF.bottom = imgRect.bottom; } } //判斷手勢down事件是否落在拉伸按鈕區域內 public boolean isIconClick(MotionEvent event){ if (rects[0].contains((int)event.getX(), (int)event.getY())) { System.out.println("點選頂部圖示"); action=TOP_ICON_ACTION; return true; } if (rects[1].contains((int)event.getX(), (int)event.getY())) { System.out.println("點選底部圖示"); action=BOTTOM_ICON_ACTION; return true; } actionY=event.getY(); return false; } public RectF getCilpRectF() { return cilpRectF; } public void setCilpRectF(RectF cilpRectF) { this.cilpRectF = cilpRectF; invalidate(); }

CilpImageView 類:

public class CilpImageView extends ImageView implements ScaleGestureDetector.OnScaleGestureListener,
ViewTreeObserver.OnGlobalLayoutListener{
    private static float SCALE_MID = 0.1f;
    /**
     * 初始化時的縮放比例,如果圖片寬或高大於螢幕,此值將小0
     */
    private float initScale = 1.0f;
    /**
     * 縮放的手勢檢測
     */
    private ScaleGestureDetector scaleGestureDetector = null;

    private final Matrix scaleMatrix = new Matrix();
    private final float[] matrixValues = new float[9];
    /**
     * 用於雙擊縮放
     */
    private GestureDetector gestureDetector;
    //是否自動縮放任務
    private boolean isAutoScale;

    private float mLastX;
    private float mLastY;
    private float centerX;
    private float centerY;

    //圖片原始寬高
    private int drawableW;
    private int drawableH;

    private boolean isCanDrag;
    private int lastPointerCount;

    private RectF borderRectF;

    private CilpRectFChangeListener cilpRectFChangeListener;

    private Options options;

    public CilpImageView(Context context) {
        super(context);
        initView(context);
    }

    public CilpImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

    public CilpImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context);
    }

    private void initView(Context context) {
        options = new Options();
        setScaleType(ScaleType.MATRIX);
        setBackgroundColor(Color.BLACK);
        gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {

            @Override
            public boolean onDoubleTap(MotionEvent e) {//雙擊
                if (isAutoScale)
                    return true;
                float x=e.getX();
                float y=e.getY();
                if (getScale() != initScale) {
                    CilpImageView.this.postDelayed(new AutoScaleRunnable(initScale, x, y),16);
                } else {
                    CilpImageView.this.postDelayed(new AutoScaleRunnable(initScale * 2, x, y),16);
                }
                isAutoScale=true;
                return true;
            }
        });

        scaleGestureDetector=new ScaleGestureDetector(context,this);

    }


    /**
     * 獲得當前的縮放比例
     *
     * @return
     */
    public final float getScale() {
        scaleMatrix.getValues(matrixValues);
        return matrixValues[Matrix.MSCALE_X];
    }
    /**
     * 邊界校驗
     */
    private void checkBorder(){
        RectF rectF=getMatrixRectF();
        float deltaX = 0;
        float deltaY = 0;

        int width=getWidth();

        // 如果寬或高大於螢幕,則控制範圍,這裡的0.001是因為精度丟失會產生問題,但是誤差一般很小,所以直接加了一個0.01
        if (rectF.width() + 0.01 >= width - 2 * options.paddingWidth) {
            if (rectF.left > options.paddingWidth) {
                deltaX = -rectF.left + options.paddingWidth;
            }

            if (rectF.right < width - options.paddingWidth){
                deltaX = width - options.paddingWidth - rectF.right;
            }
        }

        if (rectF.height() +0.01 >= borderRectF.height()){
            if (rectF.top > borderRectF.top){
                deltaY = -rectF.top + borderRectF.top;
            }

            if (rectF.bottom < borderRectF.bottom){
                deltaY = borderRectF.bottom-rectF.bottom;
            }
        }

        scaleMatrix.postTranslate(deltaX,deltaY);

    }

    /**
     * 根據當前圖片的Matrix獲得圖片的範圍
     */
    public RectF getMatrixRectF() {
        Matrix matrix = scaleMatrix;
        RectF rect = new RectF();
        Drawable d = getDrawable();
        if (null != d) {
            rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
            matrix.mapRect(rect);
        }
        return rect;
    }

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        if (getDrawable() == null) {
            return true;
        }
        float scaleFactor=detector.getScaleFactor();
        scaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
        checkBorder();
        setImageMatrix(scaleMatrix);
        setCilpRectFIfNeed();
        return true;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) { }
    /**
     * 根據手勢做縮放
     */
    public boolean onImageTouch(MotionEvent event, RectF borderRect) {
        this.borderRectF=borderRect;
        if (gestureDetector.onTouchEvent(event)){
            return true;
        }
        scaleGestureDetector.onTouchEvent(event);

        float x = 0, y = 0;
        // 拿到觸控點的個數
        final int pointerCount = event.getPointerCount();
        // 得到多個觸控點的x與y
        for (int i = 0; i < pointerCount; i++) {
            x += event.getX(i);
            y += event.getY(i);
        }
        x = x / pointerCount;
        y = y / pointerCount;

        /**
         * 每當觸控點發生變化時,重置mLasX , mLastY
         */
        if (pointerCount != lastPointerCount) {
            isCanDrag = false;
            mLastX = x;
            mLastY = y;
        }

        lastPointerCount = pointerCount;
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            resetMidScale();
            mLastX = x;
            mLastY = y;
            break;
        case MotionEvent.ACTION_MOVE:
            float dx = x - mLastX;
            float dy = y - mLastY;

            if (!isCanDrag) {
                isCanDrag = isCanDrag(dx, dy);
            }
            if (isCanDrag) {
                if (getDrawable() != null) {
                    RectF rectF = getMatrixRectF();
                    // 如果寬度小於螢幕寬度,則禁止左右移動
                    if (rectF.width() <= borderRectF.width()) {
                        dx = 0;
                    }
                    // 如果高度小於螢幕高度,則禁止上下移動
                    if (rectF.height() <= borderRectF.height()) {
                        dy = 0;
                    }
                    scaleMatrix.postTranslate(dx, dy);
                    checkBorder();
                    setImageMatrix(scaleMatrix);
                }
            }
            mLastX = x;
            mLastY = y;
            break;
        case MotionEvent.ACTION_UP:
            if (SCALE_MID>getScale()) {
                postDelayed(
                        new AutoScaleRunnable(SCALE_MID, getWidth()/2, event.getY()), 1);
            }
            break;
        case MotionEvent.ACTION_CANCEL:
            lastPointerCount = 0;
            break;
        }
        return true;
    }

    /**
     * 是否是拖動行為
     */
    private boolean isCanDrag(float dx, float dy) {
        return Math.sqrt((dx * dx) + (dy * dy)) >= 0;
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            getViewTreeObserver().removeOnGlobalLayoutListener(this);
        } else {
            getViewTreeObserver().removeGlobalOnLayoutListener(this);
        }
    }

    @Override
    public void onGlobalLayout() {
        if (0!=getHeight()) {
            if (borderRectF==null)
                borderRectF=new RectF(options.paddingWidth,(getHeight() - options.cilpHeight)/2,
                        getWidth()-options.paddingWidth,(getHeight() + options.cilpHeight)/2);
            int height=getHeight();
            Drawable d = getDrawable();
            if (d == null)
                return;
            int width = getWidth();
            // 拿到圖片的寬和高
            drawableW = d.getIntrinsicWidth();
            drawableH = d.getIntrinsicHeight();
            float scale ;
            scale= borderRectF.width() /drawableW;
            scaleMatrix.reset();
            initScale = scale;
            SCALE_MID=initScale;

            centerX=(width - (borderRectF.width())) / 2;
            centerY=(height - drawableH*scale) / 2;
            scaleMatrix.postTranslate(centerX,  centerY);

            scaleMatrix.postScale(scale, scale,centerX,centerY);
            // 圖片移動至螢幕中心
            setImageMatrix(scaleMatrix);
            setCilpRectFIfNeed();
        }
    }
    //校驗裁剪邊界
    private void setCilpRectFIfNeed(){
        RectF rectF=getMatrixRectF();
        if (cilpRectFChangeListener!=null && (rectF.height() < borderRectF.height())){

            borderRectF.top = rectF.top;
            borderRectF.bottom = rectF.bottom;

            rectF.right = borderRectF.right;
            rectF.left = borderRectF.left;

            cilpRectFChangeListener.onChange(rectF);
        }
    }
    /**
     * 計算最小縮放值
     */
    private void resetMidScale(){
        int distanceW = (int) (drawableW - borderRectF.width());
        int distanceH = (int) (drawableH - borderRectF.height());

        if (distanceH < distanceW) {//按高度算最小縮放比例
            SCALE_MID= borderRectF.height() /drawableH;
        } else {
            SCALE_MID= borderRectF.width() /drawableW;
        }

    }
    /**
     * 剪下圖片,返回剪下後的bitmap物件
     *
     * @return
     */
    public Bitmap clip(RectF rectF) {
        Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(),
                Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        draw(canvas);
        bitmap=Bitmap.createBitmap(bitmap, (int)rectF.left,
                (int)rectF.top,(int)rectF.width(),(int)rectF.height());
        return bitmap;
    }
    /**
     * 自動縮放任務
     */
    private class AutoScaleRunnable implements Runnable{
        static final float BIGGER=1.07f;
        static final float SMALLER=0.93f;

        private float tarScale;
        private float tmpScale;
        /**
         * 縮放的中中心
         */
        private float x;
        private float y;

        AutoScaleRunnable(float tarScale, float x, float y) {
            this.tarScale=tarScale;
            this.x=x;
            this.y=y;

            if (getScale() < tarScale){
                tmpScale=BIGGER;
            } else {
                tmpScale=SMALLER;
            }
        }

        @Override
        public void run() {
            scaleMatrix.postScale(tmpScale, tmpScale, x, y);
            checkBorder();
            setImageMatrix(scaleMatrix);

            final float currentScale =  getScale();
            // 如果值在合法範圍內,繼續縮放
            if ( ((tmpScale > 1f) && (currentScale < tarScale)) ||
                    ((tmpScale < 1f) && (tarScale <currentScale))){
                final float nextScale = tmpScale * currentScale;//下次的縮放比例

                if (((tmpScale > 1f) && (currentScale <tarScale))){//放大
                    if (nextScale > tarScale){
                        tmpScale = tarScale / currentScale;
                    }
                }
                if (((tmpScale < 1f) && (currentScale > tarScale))){//縮小
                    if (nextScale < tarScale){
                        tmpScale = tarScale / currentScale;
                    }
                }

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    CilpImageView.this.postOnAnimation(this);
                }else{
                    CilpImageView.this.postDelayed(this,1);
                }
            } else {
                setCilpRectFIfNeed();
                isAutoScale=false;
            }
        }
    }

    public void setCilpRectFChangeListener(CilpRectFChangeListener cilpRectFChangeListener) {
        this.cilpRectFChangeListener = cilpRectFChangeListener;
    }

    public void setOptions(Options options) {
        this.options = options;
    }

    public interface CilpRectFChangeListener{
        void onChange(RectF rectF);
    }
}

CilpTouchView類:

public class CilpTouchView extends View implements View.OnTouchListener{

    private CilpImageView imageView;
    private CilpBorderView borderView;

    private boolean iconClick;
    private RectF changeRect;

    public CilpTouchView(Context context, CilpBorderView borderView, final CilpImageView imageView) {
        super(context);
        if (borderView == null || imageView == null) {
            throw new NullPointerException("view is null");
        }
        this.borderView=borderView;
        this.imageView=imageView;

        imageView.setCilpRectFChangeListener(new CilpImageView.CilpRectFChangeListener() {
            @Override
            public void onChange(RectF rectF) {
                CilpTouchView.this.borderView.setCilpRectF(rectF);
            }
        });

        setOnTouchListener(this);
    }
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            if (borderView.isIconClick(event)){
                iconClick = true;
                changeRect= imageView.getMatrixRectF();
            } else {
                iconClick = false;
                changeRect=borderView.getCilpRectF();
            }
        }

        if (iconClick){
            borderView.iconOntouch(event, changeRect);
        } else {
            imageView.onImageTouch(event, changeRect);
        }
        if (event.getAction() == MotionEvent.ACTION_UP) {
            iconClick=false;
        }
        return true;
    }
}

效果圖:
這裡寫圖片描述