Android 照片选择区域功能实现示例

实现 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

demo 的 gradle 配置

到此这篇关于Android 照片选择区域功能实现示例的文章就介绍到这了,更多相关Android 照片选择区域内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android WebView支持input file启用相机/选取照片功能

    webview要调起input-file拍照或者选取文件功能,可以在webview.setWebChromeClient方法中重写指定的方法,来拦截webview的input事件,并做我们相应的操作. Android代码 webView.setWebChromeClient(new WebChromeClient() { @Override public void onProgressChanged(WebView view, int newProgress) { if (newProgress

  • Android开发从相册中选取照片的示例代码

    最近项目在做一个功能:就是需要从用户选择头像跳转到相册选择图片,这应该是一个很简单的需求,但是在网上搜了一下有好多都讲的很乱,其实用几十行代码就可以实现的为什么要说的那么复杂呢,下面就简单说一下喽. 下面说两种方法分别是直接选择相册返回,另外一种为选择相册之后进行裁剪.先上效果图 (1)直接选择相册后返回 第一步就是主要跳转的相册 //在这里跳转到手机系统相册里面 Intent intent = new Intent( Intent.ACTION_PICK, android.provider.M

  • Android 照片选择区域功能实现示例

    实现 Android 的照片选择区域功能 主要有参考 pqpo/SmartCropper 1, 显示 显示四条边和八个点, 八个点: 4 个角和 4 条边的中点 /* 裁剪区域, 0, 左上 -> LeftTop, 1, 右上 -> RightTop, 2, 右下 -> RightBottom, 3, 左下 -> LeftBottom */ Point[] mCropPoints; // 4 条边的中点 Point[] mEdgeMidPoints; 绘制 protected vo

  • Android实现登录功能demo示例

    本文实例讲述了Android实现登录功能的方法.分享给大家供大家参考,具体如下: 安卓,在小编实习之前的那段岁月里面,小编都没有玩儿过,如果说玩儿过,那就是安卓手机了,咳咳,敲登录的时候有种特别久违的熟悉,这种熟悉的感觉就和当时敲机房收费系统一样,那叫一个艰难啊,不过小编相信,在小编的IT成长之路上,正是因为有了这些艰难险阻陪伴着小编一起成长,才让小编更加勇敢坚强,勇敢的面对一个又一个bug,坚强的敲完一行行代码,经过了几天的研究登录一条线的功能已经实现,现在小编就来简单的总结一下,还请小伙伴们

  • arcgis android之定位功能的示例代码

    关于定位的功能,开发,很早之前就有做过百度的定位功能.起初是有想法把百度的Loc V3.2的定位SDK整合进来用.但是终归是想法,但是知道昨天,我问技术群,里面的一位朋友就说起了百度地位SDK整合进来的实现方法.顿时,我就思考了一会,随后就是很激动地操作起来.根据朋友给的一个demo.做了两天,终于算是真正将功能实现了.至于界面的美观或者样式的显示这个就偷懒掉了. http://developer.baidu.com/map/sdk-android.htm 这个是百度的SDK.帮助文档. 第一次

  • Android实现显示和隐藏密码功能的示例代码

    在前端中我们知道用javascript就可以可以很容易实现,那么在Android中怎么实现这个功能呢? Java代码 package com.example.test2; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.text.method.HideReturnsTransformationMethod; import android.text.method.Pa

  • Android自定义View实现照片裁剪框与照片裁剪功能

    本文所需要实现的就是这样一种有逼格的效果: 右上角加了个图片框,按下确定可以裁剪正方形区域里的图片并显示在右上角. 实现思路: 1:首先需要自定义一个ZoomImageView来显示我们需要的图片,这个View需要让图片能够以合适的位置展现在当前布局的图片展示区域内(合适的位置值的是:如果图片长度大于屏幕,则压缩图片长度至屏幕宽度,高度等比压缩并居中显示,如果图片高度大于屏幕,则压缩图片高度至屏幕高度,长度等比压缩并居中显示.): 2:然后需要实现这个拖动的框框,该框框实现的功能有四点:拖动.扩

  • Android仿微信发送语音消息的功能及示例代码

    微信的发送语音是有一个向上取消的,我们使用onTouchListener来监听手势,然后做出相应的操作就行了. 直接上代码: //语音操作对象 private MediaPlayer mPlayer = null; private MediaRecorder mRecorder = null; //语音文件保存路径 private String FileName = null; FileName = Environment.getExternalStorageDirectory().getAbs

  • Android 自定义LineLayout实现满屏任意拖动功能的示例代码

    1.前言 在开发中,会有需求实现控件在屏幕随意拖动,这就需要自定义View,然后在OnTouchEvent事件中,处理MotionEvent.ACTION_MOVE事件,然后通过坐标点传值给onlayout方法,来实现控件的任意拖动,具体代码如下: import android.content.Context; import android.util.AttributeSet; import android.view.Display; import android.view.MotionEven

  • Android实现强制下线功能的示例代码

    一.回顾 上次连载写了两个类,一个类ActivityCollector.java用于管理所有的活动:一个类是BaseActivity.java作为所有活动的父类: 还有一个放在layout目录中的登录界面login.xml 二.登录页面的活动 接下来写一个登录页面的活动,继承自BaseActivity.java package com.example.broadcastbestpractice; import android.content.Intent; import android.os.B

  • Android Filterable实现Recyclerview筛选功能的示例代码

    原先碰到筛选这种功能时,后端的接口都会让上传一个字段,根据字段来返回相应的数据.后来一次和别人对接时,接口直接返回全部数据,而且还要实现筛选功能.我...我说不就是一条sql语句的事,改接口多方便,我苦心劝导,然后被怼回来,切,不就是筛选嘛,求人不如自己搞. 1. 效果图 2. 思路 既然是筛选,那就少不了比较.也没有什么好的办法,无非就是循环对比,然后将适配器进行数据更新.页面刷新即可.但筛选的调用要方便,怎么比较才方便我们调用呢?偶然间看到了Filterable,使Adapter继承自该接口

  • Android kotlin使用注解实现防按钮连点功能的示例

    SingleClick: @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.FUNCTION) annotation class SingleClick( // 点击间隔时间,毫秒 val value: Long = 500 ) SingleClickAspect: import android.os.SystemClock import org.aspectj.lang.ProceedingJoinPoint im

随机推荐