Android 照片選擇區(qū)域功能實(shí)現(xiàn)示例
實(shí)現(xiàn) Android 的照片選擇區(qū)域功能

主要有參考 pqpo/SmartCropper
1, 顯示
顯示四條邊和八個(gè)點(diǎn),
八個(gè)點(diǎn): 4 個(gè)角和 4 條邊的中點(diǎn)
/* 裁剪區(qū)域, 0, 左上 -> LeftTop, 1, 右上 -> RightTop, 2, 右下 -> RightBottom, 3, 左下 -> LeftBottom */ Point[] mCropPoints; // 4 條邊的中點(diǎn) Point[] mEdgeMidPoints;
繪制
protected void onDrawCropPoint(Canvas canvas) {
//繪制蒙版
onDrawMask(canvas);
//繪制輔助線
onDrawGuideLine(canvas);
//繪制選區(qū)線
onDrawLines(canvas);
//繪制錨點(diǎn)
onDrawPoints(canvas);
//繪制放大鏡
// ...
}
具體繪制部分:
繪制八個(gè)點(diǎn)
protected void onDrawPoints(Canvas canvas) {
if (!checkPoints(mCropPoints)) {
return;
}
// 繪制 4 個(gè)角
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();
// 中間錨點(diǎn)
// 繪制 4 條邊上的中點(diǎn)
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 條邊上的中點(diǎn)前,
先算出當(dāng)前 4 條邊上中點(diǎn)的位置
public void setEdgeMidPoints(){
// 中點(diǎn)不存在,就新建
if (mEdgeMidPoints == null){
mEdgeMidPoints = new Point[4];
for (int i = 0; i < mEdgeMidPoints.length; i++){
mEdgeMidPoints[i] = new Point();
}
}
// 維護(hù) 4 個(gè)頂點(diǎn)的位置,
// 通過(guò)頂點(diǎn)的位置,算出邊上中點(diǎn)的位置
int len = mCropPoints.length;
for (int i = 0; i < len; i++){
// 為了避免極端情況,
// 采用 ( 坐標(biāo) + 距離的一半 ) 的方式
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, 拖動(dòng)
拖動(dòng)分 2 種情況,角點(diǎn)拖拽,中點(diǎn)平移
8 個(gè)類型, 4 個(gè)角點(diǎn)拖拽,4 個(gè)中點(diǎn)平移
enum DragPointType{
LEFT_TOP,
RIGHT_TOP,
RIGHT_BOTTOM,
LEFT_BOTTOM,
TOP,
RIGHT,
BOTTOM,
LEFT;
// 判斷是角點(diǎn)拖拽,不是中點(diǎn)平移
public static boolean isEdgePoint(DragPointType type){
return type == TOP || type == RIGHT || type == BOTTOM || type == LEFT;
}
}
移動(dòng)的處理
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
boolean handle = true;
switch (action) {
case MotionEvent.ACTION_DOWN:
// 識(shí)別到,當(dāng)前點(diǎn)
mDraggingPoint = getNearbyPoint(event);
if (mDraggingPoint == null) {
handle = false;
}
break;
case MotionEvent.ACTION_MOVE:
// 移動(dòng)
toImagePointSize(mDraggingPoint, event);
break;
case MotionEvent.ACTION_UP:
// 手指抬起,
// 操作取消
mDraggingPoint = null;
break;
}
// 繪制
// 更新完位置后,刷新繪制
invalidate();
return handle || super.onTouchEvent(event);
}
識(shí)別到,當(dāng)前點(diǎn)
private Point getNearbyPoint(MotionEvent event) {
// 判斷 4 個(gè)角點(diǎn),可用
if (checkPoints(mCropPoints)) {
for (Point p : mCropPoints) {
// 找出當(dāng)前的點(diǎn)
if (isTouchPoint(p, event)) return p;
}
}
// 判斷 4 個(gè)中點(diǎn)可用
if (checkPoints(mEdgeMidPoints)) {
for (Point p : mEdgeMidPoints){
// 找出當(dāng)前的點(diǎn)
if (isTouchPoint(p, event)) return p;
}
}
return null;
}
找出當(dāng)前的點(diǎn),的方法
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 ,角點(diǎn)拖拽
先介紹 4 個(gè)角點(diǎn)拖拽
private void toImagePointSize(Point dragPoint, MotionEvent event) {
if (dragPoint == null) {
return;
}
// 找出當(dāng)前移動(dòng)類型,
// 是角點(diǎn)拖拽,還是中點(diǎn)平移
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);
// 判斷可以移動(dòng)
// ...
if (DragPointType.isEdgePoint(pointType)){
// ...
// 中點(diǎn)平移
} else {
// 角點(diǎn)拖拽
// 實(shí)現(xiàn)很簡(jiǎn)單,
// 更新就好了
dragPoint.y = y;
dragPoint.x = x;
}
}
找出當(dāng)前移動(dòng)類型,
是角點(diǎn)拖拽,還是中點(diǎn)平移
// 拿采集的點(diǎn),找出對(duì)應(yīng)的類型
private DragPointType getPointType(Point dragPoint){
if (dragPoint == null) return null;
DragPointType type;
// 看,是不是頂點(diǎn) / 角點(diǎn)
if (checkPoints(mCropPoints)) {
for (int i = 0; i < mCropPoints.length; i++) {
if (dragPoint == mCropPoints[i]) {
// 找到了,直接返回
type = DragPointType.values()[i];
return type;
}
}
}
// 看,是不是中點(diǎn)
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,中點(diǎn)平移
private void toImagePointSize(Point dragPoint, MotionEvent event) {
if (dragPoint == null) {
return;
}
DragPointType pointType = getPointType(dragPoint);
int x = // ...
int y = // ...
// 判斷可以移動(dòng)
// ...
if (DragPointType.isEdgePoint(pointType)){
// 中點(diǎn)平移,
// 拿到的是,一個(gè)偏移向量
int xoff = x - dragPoint.x;
int yoff = y - dragPoint.y;
moveEdge(pointType, xoff, yoff);
} else {
// 角點(diǎn)拖拽
// ...
}
}
拿到偏移向量,修改對(duì)應(yīng)的兩個(gè)頂點(diǎn)的坐標(biāo)
private void moveEdge(DragPointType type, int xoff, int yoff){
switch (type){
case TOP:
// 這邊的平移,比較簡(jiǎn)單
// 找到中點(diǎn),旁邊的兩個(gè)焦點(diǎn)
// 再移動(dòng)位置
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;
}
}
簡(jiǎn)單的平移代碼
拿到偏移向量, 修改坐標(biāo),完事
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;
}
中點(diǎn)平移增強(qiáng)
這里的中點(diǎn)平移,拿到平移向量后,
直接添加到中點(diǎn)旁邊的兩個(gè)角點(diǎn)上
效果增強(qiáng)為,中點(diǎn)平移,中點(diǎn)旁邊的兩個(gè)角點(diǎn)順著兩側(cè)邊,做平移
計(jì)算稍微復(fù)雜,
知道中點(diǎn)之前的位置,和中點(diǎn)之后的位置,
知道中點(diǎn)與一角點(diǎn),所在邊的斜率,
知道此角點(diǎn)的另一邊的斜率
知道角點(diǎn),平移前的位置,
求解出角點(diǎn),平移后的位置
變換坐標(biāo)系,可能簡(jiǎn)單些
相關(guān) github
到此這篇關(guān)于Android 照片選擇區(qū)域功能實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)Android 照片選擇區(qū)域內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android之在linux終端執(zhí)行shell腳本直接打印當(dāng)前運(yùn)行app的日志的實(shí)現(xiàn)方法
今天小編就為大家分享一篇關(guān)于Android之在linux終端執(zhí)行shell腳本直接打印當(dāng)前運(yùn)行app的日志的實(shí)現(xiàn)方法,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-02-02
Android仿微信實(shí)現(xiàn)首字母導(dǎo)航條
這篇文章主要為大家詳細(xì)介紹了Android仿微信實(shí)現(xiàn)首字母導(dǎo)航條的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-08-08
Android 系統(tǒng)語(yǔ)言切換監(jiān)聽(tīng)和設(shè)置實(shí)例代碼
本篇文章主要介紹了Android 系統(tǒng)語(yǔ)言切換監(jiān)聽(tīng)和設(shè)置實(shí)例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
快速解決Android適配底部返回鍵等虛擬鍵盤(pán)的問(wèn)題
今天小編就為大家分享一篇快速解決Android適配底部返回鍵等虛擬鍵盤(pán)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-07-07
Flexbox+ReclyclerView實(shí)現(xiàn)流式布局
這篇文章主要為大家詳細(xì)介紹了Flexbox+ReclyclerView實(shí)現(xiàn)流式布局,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11

