实现 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
本文在开源项目:https://github.com/Android-Alvin/Android-LearningNotes 中已收录,里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中...
作者:东方教主
链接:https://juejin.cn/post/6953645225701015583