Android 自定义控件——图片剪裁

本文介绍一个自定义的图片剪裁控件

该控件由另一篇博文:Android 图片拖拽、放大缩小的自定义控件 扩展而来

如图:

Android 自定义控件——图片剪裁_第1张图片         Android 自定义控件——图片剪裁_第2张图片

思路:在一个自定义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);
	}

}

使用

布局中:



    

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();
	}

}


转载于:https://www.cnblogs.com/gmm283029/p/4498951.html

你可能感兴趣的:(Android 自定义控件——图片剪裁)