1. 程式人生 > >Android 自定義控件——圖片剪裁

Android 自定義控件——圖片剪裁

ets nis anti none span out pro int() mat

如圖:

技術分享技術分享

思路:在一個自定義View上繪制一張圖片(參照前面提到的另一篇文章),在該自定義View上繪制一個自定義的FloatDrawable,也就是圖中的浮層。繪制圖片和FloatDrawable的交集的補集部分灰色陰影(這個其實很簡單,就一句話)。在自定義View的touch中去處理具體的拖動事件和FloatDrawable的變換。圖片的繪制和FloatDrawable的繪制以及變換最終其實就是在操作各自的Rect而已,Rect就是一個有矩形,有四個坐標,圖片和FloatDrawable就是按照坐標去繪制的。

CropImageView.java

該類繼承View

功能:在onDraw方法中畫圖片、浮層,處理touch事件,最後根據坐標對圖片進行剪裁。

package com.play.playgame.cropimg;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

/** * Created by Administrator on 2017/9/22. */ public class CropImageView extends View { // 在touch重要用到的點, private float mX_1 = 0; private float mY_1 = 0; // 觸摸事件判斷 private final int STATUS_SINGLE = 1; private final int STATUS_MULTI_START = 2; private final int STATUS_MULTI_TOUCHING = 3
; // 當前狀態 private int mStatus = STATUS_SINGLE; // 默認裁剪的寬高 private int cropWidth; private int cropHeight; // 浮層Drawable的四個點 private final int EDGE_LT = 1; private final int EDGE_RT = 2; private final int EDGE_LB = 3; private final int EDGE_RB = 4; private final int EDGE_MOVE_IN = 5; private final int EDGE_MOVE_OUT = 6; private final int EDGE_NONE = 7; public int currentEdge = EDGE_NONE; protected float oriRationWH = 0; protected final float maxZoomOut = 5.0f; protected final float minZoomIn = 0.333333f; protected Drawable mDrawable; protected FloatDrawable mFloatDrawable; protected Rect mDrawableSrc = new Rect();// 圖片Rect變換時的Rect protected Rect mDrawableDst = new Rect();// 圖片Rect protected Rect mDrawableFloat = new Rect();// 浮層的Rect protected boolean isFrist = true; private boolean isTouchInSquare = true; protected Context mContext; public CropImageView(Context context) { super(context); init(context); } public CropImageView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public CropImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } @SuppressLint("NewApi") private void init(Context context) { this.mContext = context; try { if (android.os.Build.VERSION.SDK_INT >= 11) { this.setLayerType(LAYER_TYPE_SOFTWARE, null); } } catch (Exception e) { e.printStackTrace(); } mFloatDrawable = new FloatDrawable(context); } public void setDrawable(Drawable mDrawable, int cropWidth, int cropHeight) { this.mDrawable = mDrawable; this.cropWidth = cropWidth; this.cropHeight = cropHeight; this.isFrist = true; invalidate(); } @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent event) { if (event.getPointerCount() > 1) { if (mStatus == STATUS_SINGLE) { mStatus = STATUS_MULTI_START; } else if (mStatus == STATUS_MULTI_START) { mStatus = STATUS_MULTI_TOUCHING; } } else { if (mStatus == STATUS_MULTI_START || mStatus == STATUS_MULTI_TOUCHING) { mX_1 = event.getX(); mY_1 = event.getY(); } mStatus = STATUS_SINGLE; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mX_1 = event.getX(); mY_1 = event.getY(); currentEdge = getTouch((int) mX_1, (int) mY_1); isTouchInSquare = mDrawableFloat.contains((int) event.getX(), (int) event.getY()); break; case MotionEvent.ACTION_UP: checkBounds(); break; case MotionEvent.ACTION_POINTER_UP: currentEdge = EDGE_NONE; break; case MotionEvent.ACTION_MOVE: if (mStatus == STATUS_MULTI_TOUCHING) { } else if (mStatus == STATUS_SINGLE) { int dx = (int) (event.getX() - mX_1); int dy = (int) (event.getY() - mY_1); mX_1 = event.getX(); mY_1 = event.getY(); // 根據得到的那一個角,並且變換Rect if (!(dx == 0 && dy == 0)) { switch (currentEdge) { case EDGE_LT: mDrawableFloat.set(mDrawableFloat.left + dx, mDrawableFloat.top + dy, mDrawableFloat.right, mDrawableFloat.bottom); break; case EDGE_RT: mDrawableFloat.set(mDrawableFloat.left, mDrawableFloat.top + dy, mDrawableFloat.right + dx, mDrawableFloat.bottom); break; case EDGE_LB: mDrawableFloat.set(mDrawableFloat.left + dx, mDrawableFloat.top, mDrawableFloat.right, mDrawableFloat.bottom + dy); break; case EDGE_RB: mDrawableFloat.set(mDrawableFloat.left, mDrawableFloat.top, mDrawableFloat.right + dx, mDrawableFloat.bottom + dy); break; case EDGE_MOVE_IN: if (isTouchInSquare) { mDrawableFloat.offset((int) dx, (int) dy); } break; case EDGE_MOVE_OUT: break; } mDrawableFloat.sort(); invalidate(); } } break; } return true; } // 根據初觸摸點判斷是觸摸的Rect哪一個角 public int getTouch(int eventX, int eventY) { if (mFloatDrawable.getBounds().left <= eventX && eventX < (mFloatDrawable.getBounds().left + mFloatDrawable .getBorderWidth()) && mFloatDrawable.getBounds().top <= eventY && eventY < (mFloatDrawable.getBounds().top + mFloatDrawable .getBorderHeight())) { return EDGE_LT; } else if ((mFloatDrawable.getBounds().right - mFloatDrawable .getBorderWidth()) <= eventX && eventX < mFloatDrawable.getBounds().right && mFloatDrawable.getBounds().top <= eventY && eventY < (mFloatDrawable.getBounds().top + mFloatDrawable .getBorderHeight())) { return EDGE_RT; } else if (mFloatDrawable.getBounds().left <= eventX && eventX < (mFloatDrawable.getBounds().left + mFloatDrawable .getBorderWidth()) && (mFloatDrawable.getBounds().bottom - mFloatDrawable .getBorderHeight()) <= eventY && eventY < mFloatDrawable.getBounds().bottom) { return EDGE_LB; } else if ((mFloatDrawable.getBounds().right - mFloatDrawable .getBorderWidth()) <= eventX && eventX < mFloatDrawable.getBounds().right && (mFloatDrawable.getBounds().bottom - mFloatDrawable .getBorderHeight()) <= eventY && eventY < mFloatDrawable.getBounds().bottom) { return EDGE_RB; } else if (mFloatDrawable.getBounds().contains(eventX, eventY)) { return EDGE_MOVE_IN; } return EDGE_MOVE_OUT; } @Override protected void onDraw(Canvas canvas) { if (mDrawable == null) { return; } if (mDrawable.getIntrinsicWidth() == 0 || mDrawable.getIntrinsicHeight() == 0) { return; } configureBounds(); // 在畫布上花圖片 mDrawable.draw(canvas); canvas.save(); // 在畫布上畫浮層FloatDrawable,Region.Op.DIFFERENCE是表示Rect交集的補集 canvas.clipRect(mDrawableFloat, Region.Op.DIFFERENCE); // 在交集的補集上畫上灰色用來區分 canvas.drawColor(Color.parseColor("#a0000000")); canvas.restore(); // 畫浮層 mFloatDrawable.draw(canvas); } protected void configureBounds() { // configureBounds在onDraw方法中調用 // isFirst的目的是下面對mDrawableSrc和mDrawableFloat只初始化一次, // 之後的變化是根據touch事件來變化的,而不是每次執行重新對mDrawableSrc和mDrawableFloat進行設置 if (isFrist) { oriRationWH = ((float) mDrawable.getIntrinsicWidth()) / ((float) mDrawable.getIntrinsicHeight()); final float scale = mContext.getResources().getDisplayMetrics().density; int w = Math.min(getWidth(), (int) (mDrawable.getIntrinsicWidth() * scale + 0.5f)); int h = (int) (w / oriRationWH); int left = (getWidth() - w) / 2; int top = (getHeight() - h) / 2; int right = left + w; int bottom = top + h; mDrawableSrc.set(left, top, right, bottom); mDrawableDst.set(mDrawableSrc); int floatWidth = dipTopx(mContext, cropWidth); int floatHeight = dipTopx(mContext, cropHeight); if (floatWidth > getWidth()) { floatWidth = getWidth(); floatHeight = cropHeight * floatWidth / cropWidth; } if (floatHeight > getHeight()) { floatHeight = getHeight(); floatWidth = cropWidth * floatHeight / cropHeight; } int floatLeft = (getWidth() - floatWidth) / 2; int floatTop = (getHeight() - floatHeight) / 2; mDrawableFloat.set(floatLeft, floatTop, floatLeft + floatWidth, floatTop + floatHeight); isFrist = false; } mDrawable.setBounds(mDrawableDst); mFloatDrawable.setBounds(mDrawableFloat); } // 在up事件中調用了該方法,目的是檢查是否把浮層拖出了屏幕 protected void checkBounds() { int newLeft = mDrawableFloat.left; int newTop = mDrawableFloat.top; boolean isChange = false; if (mDrawableFloat.left < getLeft()) { newLeft = getLeft(); isChange = true; } if (mDrawableFloat.top < getTop()) { newTop = getTop(); isChange = true; } if (mDrawableFloat.right > getRight()) { newLeft = getRight() - mDrawableFloat.width(); isChange = true; } if (mDrawableFloat.bottom > getBottom()) { newTop = getBottom() - mDrawableFloat.height(); isChange = true; } mDrawableFloat.offsetTo(newLeft, newTop); if (isChange) { invalidate(); } } // 進行圖片的裁剪,所謂的裁剪就是根據Drawable的新的坐標在畫布上創建一張新的圖片 public Bitmap getCropImage() { Bitmap tmpBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.RGB_565); Canvas canvas = new Canvas(tmpBitmap); mDrawable.draw(canvas); Matrix matrix = new Matrix(); float scale = (float) (mDrawableSrc.width()) / (float) (mDrawableDst.width()); matrix.postScale(scale, scale); Bitmap ret = Bitmap.createBitmap(tmpBitmap, mDrawableFloat.left, mDrawableFloat.top, mDrawableFloat.width(), mDrawableFloat.height(), matrix, true); tmpBitmap.recycle(); tmpBitmap = null; return ret; } public int dipTopx(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } }

FloatDrawable.java

繼承自Drawable

功能:圖片上面的浮動框,通過拖動確定位置

package com.play.playgame.cropimg;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;

public class FloatDrawable extends Drawable {

    private Context mContext;
    private int offset = 50;
    private Paint mLinePaint = new Paint();
    private Paint mLinePaint2 = new Paint();
    {
        mLinePaint.setARGB(200, 50, 50, 50);
        mLinePaint.setStrokeWidth(1F);
        mLinePaint.setStyle(Paint.Style.STROKE);
        mLinePaint.setAntiAlias(true);
        mLinePaint.setColor(Color.WHITE);
        //
        mLinePaint2.setARGB(200, 50, 50, 50);
        mLinePaint2.setStrokeWidth(7F);
        mLinePaint2.setStyle(Paint.Style.STROKE);
        mLinePaint2.setAntiAlias(true);
        mLinePaint2.setColor(Color.WHITE);
    }

    public FloatDrawable(Context context) {
        super();
        this.mContext = context;

    }

    public int getBorderWidth() {
        return dipTopx(mContext, offset);//根據dip計算的像素值,做適配用的
    }

    public int getBorderHeight() {
        return dipTopx(mContext, offset);
    }

    @Override
    public void draw(Canvas canvas) {

        int left = getBounds().left;
        int top = getBounds().top;
        int right = getBounds().right;
        int bottom = getBounds().bottom;

        Rect mRect = new Rect(left + dipTopx(mContext, offset) / 2, top
                + dipTopx(mContext, offset) / 2, right
                - dipTopx(mContext, offset) / 2, bottom
                - dipTopx(mContext, offset) / 2);
        //畫默認的選擇框
        canvas.drawRect(mRect, mLinePaint);
        //畫四個角的四個粗拐角、也就是八條粗線
        canvas.drawLine((left + dipTopx(mContext, offset) / 2 - 3.5f), top
                        + dipTopx(mContext, offset) / 2,
                left + dipTopx(mContext, offset) - 8f,
                top + dipTopx(mContext, offset) / 2, mLinePaint2);
        canvas.drawLine(left + dipTopx(mContext, offset) / 2,
                top + dipTopx(mContext, offset) / 2,
                left + dipTopx(mContext, offset) / 2,
                top + dipTopx(mContext, offset) / 2 + 30, mLinePaint2);
        canvas.drawLine(right - dipTopx(mContext, offset) + 8f,
                top + dipTopx(mContext, offset) / 2,
                right - dipTopx(mContext, offset) / 2,
                top + dipTopx(mContext, offset) / 2, mLinePaint2);
        canvas.drawLine(right - dipTopx(mContext, offset) / 2,
                top + dipTopx(mContext, offset) / 2 - 3.5f,
                right - dipTopx(mContext, offset) / 2,
                top + dipTopx(mContext, offset) / 2 + 30, mLinePaint2);
        canvas.drawLine((left + dipTopx(mContext, offset) / 2 - 3.5f), bottom
                        - dipTopx(mContext, offset) / 2,
                left + dipTopx(mContext, offset) - 8f,
                bottom - dipTopx(mContext, offset) / 2, mLinePaint2);
        canvas.drawLine((left + dipTopx(mContext, offset) / 2), bottom
                        - dipTopx(mContext, offset) / 2,
                (left + dipTopx(mContext, offset) / 2),
                bottom - dipTopx(mContext, offset) / 2 - 30f, mLinePaint2);
        canvas.drawLine((right - dipTopx(mContext, offset) + 8f), bottom
                        - dipTopx(mContext, offset) / 2,
                right - dipTopx(mContext, offset) / 2,
                bottom - dipTopx(mContext, offset) / 2, mLinePaint2);
        canvas.drawLine((right - dipTopx(mContext, offset) / 2), bottom
                        - dipTopx(mContext, offset) / 2 - 30f,
                right - dipTopx(mContext, offset) / 2,
                bottom - dipTopx(mContext, offset) / 2 + 3.5f, mLinePaint2);

    }

    @Override
    public void setBounds(Rect bounds) {
        super.setBounds(new Rect(bounds.left - dipTopx(mContext, offset) / 2,
                bounds.top - dipTopx(mContext, offset) / 2, bounds.right
                + dipTopx(mContext, offset) / 2, bounds.bottom
                + dipTopx(mContext, offset) / 2));
    }

    @Override
    public void setAlpha(int alpha) {

    }

    @Override
    public void setColorFilter(ColorFilter cf) {

    }

    @Override
    public int getOpacity() {
        return 0;
    }

    public int dipTopx(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

}

使用

布局中:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <com.onehead.cropimage.CropImageView
        android:id="@+id/cropimage"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

Activity中:

public class MainActivity extends ActionBarActivity {
      private CropImageView mView;

     @Override
      protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mView = (CropImageView) findViewById(R.id.cropimage);
            //設置資源和默認長寬
            mView.setDrawable(getResources().getDrawable(R.drawable.test2), 300,
            300);
            //調用該方法得到剪裁好的圖片
            Bitmap mBitmap= mView.getCropImage();
   }
}

Android 自定義控件——圖片剪裁