本文介绍一个自定义的图片剪裁控件
该控件由另一篇博文:Android 图片拖拽、放大缩小的自定义控件 扩展而来
如图:
思路:在一个自定义View上绘制一张图片(参照前面提到的另一篇博文),在该自定义View上绘制一个自定义的FloatDrawable,也就是图中的浮层。绘制图片和FloatDrawable的交集的补集部分灰色阴影(这个其实很简单,就一句话)。在自定义View的touch中去处理具体的拖动事件和FloatDrawable的变换。图片的绘制和FloatDrawable的绘制以及变换最终其实就是在操作各自的Rect而已,Rect就是一个有矩形,有四个坐标,图片和FloatDrawable就是按照坐标去绘制的。
CropImageView.java
该类继承View
功能:在onDraw方法中画图片、浮层,处理touch事件,最后根据坐标对图片进行剪裁。
- 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(),
- 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
功能:图片上面的浮动框,通过拖动确定位置
- 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>
- 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();
- }
- }