实现 Android 的照片选择区域功能
主要有参考 pqpo/SmartCropper
1, 显示
显示四条边和八个点,
八个点: 4 个角和 4 条边的中点
/* 裁剪区域, 0, 左上 -> LeftTop, 1, 右上 -> RightTop, 2, 右下 -> RightBottom, 3, 左下 -> LeftBottom */ Point[] mCropPoints; // 4 条边的中点 Point[] mEdgeMidPoints;
绘制
protected void onDrawCropPoint(Canvas canvas) { //绘制蒙版 onDrawMask(canvas); //绘制辅助线 onDrawGuideLine(canvas); //绘制选区线 onDrawLines(canvas); //绘制锚点 onDrawPoints(canvas); //绘制放大镜 // ... }
具体绘制部分:
绘制八个点
protected void onDrawPoints(Canvas canvas) { if (!checkPoints(mCropPoints)) { return; } // 绘制 4 个角 for (Point point : mCropPoints) { canvas.drawCircle(getViewPointX(point), getViewPointY(point), dp2px(POINT_RADIUS), mPointFillPaint); canvas.drawCircle(getViewPointX(point), getViewPointY(point), dp2px(POINT_RADIUS), mPointPaint); } if (mShowEdgeMidPoint) { setEdgeMidPoints(); // 中间锚点 // 绘制 4 条边上的中点 for (Point point : mEdgeMidPoints){ canvas.drawCircle(getViewPointX(point), getViewPointY(point), dp2px(POINT_RADIUS), mPointFillPaint); canvas.drawCircle(getViewPointX(point), getViewPointY(point), dp2px(POINT_RADIUS), mPointPaint); } } }
绘制 4 条边上的中点前,
先算出当前 4 条边上中点的位置
public void setEdgeMidPoints(){ // 中点不存在,就新建 if (mEdgeMidPoints == null){ mEdgeMidPoints = new Point[4]; for (int i = 0; i < mEdgeMidPoints.length; i++){ mEdgeMidPoints[i] = new Point(); } } // 维护 4 个顶点的位置, // 通过顶点的位置,算出边上中点的位置 int len = mCropPoints.length; for (int i = 0; i < len; i++){ // 为了避免极端情况, // 采用 ( 坐标 + 距离的一半 ) 的方式 mEdgeMidPoints[i].set(mCropPoints[i].x + (mCropPoints[(i+1)%len].x - mCropPoints[i].x)/2, mCropPoints[i].y + (mCropPoints[(i+1)%len].y - mCropPoints[i].y)/2); } }
2, 拖动
拖动分 2 种情况,角点拖拽,中点平移
8 个类型, 4 个角点拖拽,4 个中点平移
enum DragPointType{ LEFT_TOP, RIGHT_TOP, RIGHT_BOTTOM, LEFT_BOTTOM, TOP, RIGHT, BOTTOM, LEFT; // 判断是角点拖拽,不是中点平移 public static boolean isEdgePoint(DragPointType type){ return type == TOP || type == RIGHT || type == BOTTOM || type == LEFT; } }
移动的处理
@Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); boolean handle = true; switch (action) { case MotionEvent.ACTION_DOWN: // 识别到,当前点 mDraggingPoint = getNearbyPoint(event); if (mDraggingPoint == null) { handle = false; } break; case MotionEvent.ACTION_MOVE: // 移动 toImagePointSize(mDraggingPoint, event); break; case MotionEvent.ACTION_UP: // 手指抬起, // 操作取消 mDraggingPoint = null; break; } // 绘制 // 更新完位置后,刷新绘制 invalidate(); return handle || super.onTouchEvent(event); }
识别到,当前点
private Point getNearbyPoint(MotionEvent event) { // 判断 4 个角点,可用 if (checkPoints(mCropPoints)) { for (Point p : mCropPoints) { // 找出当前的点 if (isTouchPoint(p, event)) return p; } } // 判断 4 个中点可用 if (checkPoints(mEdgeMidPoints)) { for (Point p : mEdgeMidPoints){ // 找出当前的点 if (isTouchPoint(p, event)) return p; } } return null; }
找出当前的点,的方法
private static final float TOUCH_POINT_CATCH_DISTANCE = 15; private boolean isTouchPoint(Point p, MotionEvent event){ float x = event.getX(); float y = event.getY(); float px = getViewPointX(p); float py = getViewPointY(p); double distance = Math.sqrt(Math.pow(x - px, 2) + Math.pow(y - py, 2)); // 也就是,判断距离 if (distance < dp2px(TOUCH_POINT_CATCH_DISTANCE)) { return true; } return false; }
2.1 ,角点拖拽
先介绍 4 个角点拖拽
private void toImagePointSize(Point dragPoint, MotionEvent event) { if (dragPoint == null) { return; } // 找出当前移动类型, // 是角点拖拽,还是中点平移 DragPointType pointType = getPointType(dragPoint); int x = (int) ((Math.min(Math.max(event.getX(), mActLeft), mActLeft + mActWidth) - mActLeft) / mScaleX); int y = (int) ((Math.min(Math.max(event.getY(), mActTop), mActTop + mActHeight) - mActTop) / mScaleY); // 判断可以移动 // ... if (DragPointType.isEdgePoint(pointType)){ // ... // 中点平移 } else { // 角点拖拽 // 实现很简单, // 更新就好了 dragPoint.y = y; dragPoint.x = x; } }
找出当前移动类型,
是角点拖拽,还是中点平移
// 拿采集的点,找出对应的类型 private DragPointType getPointType(Point dragPoint){ if (dragPoint == null) return null; DragPointType type; // 看,是不是顶点 / 角点 if (checkPoints(mCropPoints)) { for (int i = 0; i < mCropPoints.length; i++) { if (dragPoint == mCropPoints[i]) { // 找到了,直接返回 type = DragPointType.values()[i]; return type; } } } // 看,是不是中点 if (checkPoints(mEdgeMidPoints)) { for (int i = 0; i < mEdgeMidPoints.length; i++){ if (dragPoint == mEdgeMidPoints[i]){ // 找到了,直接返回 type = DragPointType.values()[4+i]; return type; } } } return null; }
2.2,中点平移
private void toImagePointSize(Point dragPoint, MotionEvent event) { if (dragPoint == null) { return; } DragPointType pointType = getPointType(dragPoint); int x = // ... int y = // ... // 判断可以移动 // ... if (DragPointType.isEdgePoint(pointType)){ // 中点平移, // 拿到的是,一个偏移向量 int xoff = x - dragPoint.x; int yoff = y - dragPoint.y; moveEdge(pointType, xoff, yoff); } else { // 角点拖拽 // ... } }
拿到偏移向量,修改对应的两个顶点的坐标
private void moveEdge(DragPointType type, int xoff, int yoff){ switch (type){ case TOP: // 这边的平移,比较简单 // 找到中点,旁边的两个焦点 // 再移动位置 movePoint(mCropPoints[P_LT], 0, yoff); movePoint(mCropPoints[P_RT], 0, yoff); break; case RIGHT: // 右移处理 movePoint(mCropPoints[P_RT], xoff, 0); movePoint(mCropPoints[P_RB], xoff, 0); break; case BOTTOM: // 下移处理 // ... case LEFT: // 左移处理 // ... default: break; } }
简单的平移代码
拿到偏移向量, 修改坐标,完事
private void movePoint(Point point, int xoff, int yoff){ if (point == null) return; int x = point.x + xoff; int y = point.y + yoff; // 检查边界 if (x < 0 || x > getDrawable().getIntrinsicWidth()) return; if (y < 0 || y > getDrawable().getIntrinsicHeight()) return; point.x = x; point.y = y; }
中点平移增强
这里的中点平移,拿到平移向量后,
直接添加到中点旁边的两个角点上
效果增强为,中点平移,中点旁边的两个角点顺着两侧边,做平移
计算稍微复杂,
知道中点之前的位置,和中点之后的位置,
知道中点与一角点,所在边的斜率,
知道此角点的另一边的斜率
知道角点,平移前的位置,
求解出角点,平移后的位置
变换坐标系,可能简单些
相关 github