android自定义可缩放,移动图像裁剪框

2023-11-11

在实际项目中,经常要制作一个简易的图像裁剪功能,即获取一张图片,并用一个遮罩层选择目标范围并截取保存的功能,如下图所示:

在此分享下该自定义视图的制作过程。

 

需求说明

整一个视图包含一个透明的遮罩层,一个透明带白色边框的矩形。要实现的功能是:

 

  • 点击矩形框外围:无任何响应
  • 点击矩形框内部:可随手指移动而移动
  • 点击矩形框的4个顶点:可进行对角顶点坐标不变的情况下的矩形的缩放,同时边框变色

 

下面是实现该功能的完整源码

/**
 * Created by Farble on 2015/3/10.
 */
public class PhotoCropView extends View {
    private static final String TAG = "PhotoCropView";
 
    private onLocationListener locationListener;/*listen to the Rect */
    private onChangeLocationlistener changeLocationlistener;/*listening position changed */
 
    private int MODE;
    private static final int MODE_OUTSIDE = 0x000000aa;/*170*/
    private static final int MODE_INSIDE = 0x000000bb;/*187*/
    private static final int MODE_POINT = 0X000000cc;/*204*/
    private static final int MODE_ILLEGAL = 0X000000dd;/*221*/
 
    private static final int minWidth = 100;/*the minimum width of the rectangle*/
    private static final int minHeight = 200;/*the minimum height of the rectangle*/
 
    private static final int START_X = 200;
    private static final int START_Y = 200;
 
    private static final float EDGE_WIDTH = 1.8f;
    private static final int ACCURACY= 15;/*touch accuracy*/
 
    private int pointPosition;/*vertex of a rectangle*/
 
    private int sX;/*start X location*/
    private int sY;/*start Y location*/
    private int eX;/*end X location*/
    private int eY;/*end Y location*/
 
    private int pressX;/*X coordinate values while finger press*/
    private int pressY;/*Y coordinate values while finger press*/
 
    private int memonyX;/*the last time the coordinate values of X*/
    private int memonyY;/*the last time the coordinate values of Y*/
 
    private int coverWidth = 300;/*width of selection box*/
    private int coverHeight = 400;/*height of selection box*/
 
    private Paint mPaint;
    private Paint mPaintLine;
    private Bitmap mBitmapCover;
    private Bitmap mBitmapRectBlack;
    private PorterDuffXfermode xfermode;/*paint mode*/
 
    public PhotoCropView(Context context) {
        super(context);
        init();
    }
 
    public PhotoCropView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
 
    public PhotoCropView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
 
    @SuppressWarnings("deprecation")
    private void init() {
        sX = START_X;
        sY = START_Y;
        WindowManager manager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        int width = manager.getDefaultDisplay().getWidth();
        int height = manager.getDefaultDisplay().getHeight();
        mBitmapCover = makeBitmap(width, height, 0x5A000000, 0, 0);
        mBitmapRectBlack = makeBitmap(coverWidth, coverHeight, 0xff000000, coverWidth, coverHeight);
 
        eX = sX + coverWidth;
        eY = sY + coverHeight;
        pressX = 0;
        pressY = 0;
 
        xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
 
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
 
        mPaintLine = new Paint();
        mPaintLine.setColor(Color.WHITE);
        mPaintLine.setStrokeWidth(2.0f);
    }
 
    /*生成bitmap*/
    private Bitmap makeBitmap(int mwidth, int mheight, int resource, int staX, int staY) {
        Bitmap bm = Bitmap.createBitmap(mwidth, mheight, Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(bm);
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
 
        p.setColor(resource);
        c.drawRect(staX, staY, mwidth, mheight, p);
        return bm;
    }
 
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setFilterBitmap(false);
        int sc = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null,
                Canvas.MATRIX_SAVE_FLAG |
                        Canvas.CLIP_SAVE_FLAG |
                        Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
                        Canvas.FULL_COLOR_LAYER_SAVE_FLAG |
                        Canvas.CLIP_TO_LAYER_SAVE_FLAG);
 
        canvas.drawBitmap(mBitmapCover, 0, 0, mPaint);
        mPaint.setXfermode(xfermode);
        canvas.drawBitmap(mBitmapRectBlack, sX, sY, mPaint);
        if (locationListener != null) {
            locationListener.locationRect(sX, sY, eX, eY);
        }
        mPaint.setXfermode(null);
        canvas.restoreToCount(sc);
        canvas.drawLine((float) sX - EDGE_WIDTH, (float) sY - EDGE_WIDTH, (float) eX + EDGE_WIDTH, (float) sY - EDGE_WIDTH, mPaintLine);/*up -*/
        canvas.drawLine((float) sX - EDGE_WIDTH, (float) eY + EDGE_WIDTH, (float) eX + EDGE_WIDTH, (float) eY + EDGE_WIDTH, mPaintLine);/*down -*/
        canvas.drawLine((float) sX - EDGE_WIDTH, (float) sY - EDGE_WIDTH, (float) sX - EDGE_WIDTH, (float) eY + EDGE_WIDTH, mPaintLine);/*left |*/
        canvas.drawLine((float) eX + EDGE_WIDTH, (float) sY - EDGE_WIDTH, (float) eX + EDGE_WIDTH, (float) eY + EDGE_WIDTH, mPaintLine);/*righ |*/
        
    }
 
    @SuppressWarnings("NullableProblems")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (changeLocationlistener != null) {
                    changeLocationlistener.locationChange("change self");
                } else {
                    changeLocationlistener = null;
                }
 
                memonyX = (int) event.getX();
                memonyY = (int) event.getY();
                checkMode(memonyX, memonyY);
                break;
            case MotionEvent.ACTION_MOVE: {
                switch (MODE) {
                    case MODE_ILLEGAL:
                        pressX = (int) event.getX();
                        pressY = (int) event.getY();
                        recoverFromIllegal(pressX, pressY);
                        postInvalidate();
                        break;
                    case MODE_OUTSIDE:
                        //do nothing;
                        break;
                    case MODE_INSIDE:
                        pressX = (int) event.getX();
                        pressY = (int) event.getY();
                        moveByTouch(pressX, pressY);
                        postInvalidate();
                        break;
                    default:
                        /*MODE_POINT*/
                        pressX = (int) event.getX();
                        pressY = (int) event.getY();
                        mPaintLine.setColor(getContext().getResources().getColor(R.color.orange));
                        moveByPoint(pressX, pressY);
                        postInvalidate();
                        break;
                }
            }
            break;
            case MotionEvent.ACTION_UP:
                mPaintLine.setColor(Color.WHITE);
                postInvalidate();
                break;
            default:
                break;
        }
        return true;
    }
 
    /*从非法状态恢复,这里处理的是达到最小值后能拉伸放大*/
    private void recoverFromIllegal(int rx, int ry) {
        if ((rx > sX && ry > sY) && (rx < eX && ry < eY)) {
            MODE = MODE_ILLEGAL;
        } else {
            MODE = MODE_POINT;
        }
    }
 
    private void checkMode(int cx, int cy) {
        if (cx > sX && cx < eX && cy > sY && cy < eY) {
            MODE = MODE_INSIDE;
        } else if (nearbyPoint(cx, cy) < 4) {
            MODE = MODE_POINT;
        } else {
            MODE = MODE_OUTSIDE;
        }
    }
 
    /*判断点(inX,inY)是否靠近矩形的4个顶点*/
    private int nearbyPoint(int inX, int inY) {
        if ((Math.abs(sX - inX) <= ACCURACY && (Math.abs(inY - sY) <= ACCURACY))) {/*left-up angle*/
            pointPosition = 0;
            return 0;
        }
        if ((Math.abs(eX - inX) <= ACCURACY && (Math.abs(inY - sY) <= ACCURACY))) {/*right-up  angle*/
            pointPosition = 1;
            return 1;
        }
        if ((Math.abs(sX - inX) <= ACCURACY && (Math.abs(inY - eY) <= ACCURACY))) {/*left-down angle*/
            pointPosition = 2;
            return 2;
        }
        if ((Math.abs(eX - inX) <= ACCURACY && (Math.abs(inY - eY) <= ACCURACY))) {/*right-down angle*/
            pointPosition = 3;
            return 3;
        }
        pointPosition = 100;
        return 100;
    }
 
    /*刷新矩形的坐标*/
    private void refreshLocation(int isx, int isy, int iex, int iey) {
        this.sX = isx;
        this.sY = isy;
        this.eX = iex;
        this.eY = iey;
    }
 
    /*矩形随手指移动*/
    private void moveByTouch(int mx, int my) {/*move center point*/
        int dX = mx - memonyX;
        int dY = my - memonyY;
 
        sX += dX;
        sY += dY;
 
        eX = sX + coverWidth;
        eY = sY + coverHeight;
 
        memonyX = mx;
        memonyY = my;
 
    }
 
    /*检测矩形是否达到最小值*/
    private boolean checkLegalRect(int cHeight, int cWidth) {
        return (cHeight > minHeight && cWidth > minWidth);
    }
 
    /*点击顶点附近时的缩放处理*/
    @SuppressWarnings("SuspiciousNameCombination")
    private void moveByPoint(int bx, int by) {
        switch (pointPosition) {
            case 0:/*left-up*/
                coverWidth = Math.abs(eX - bx);
                coverHeight = Math.abs(eY - by);
                //noinspection SuspiciousNameCombination
                if (!checkLegalRect(coverWidth, coverHeight)) {
                    MODE = MODE_ILLEGAL;
                } else {
                    mBitmapRectBlack = null;
                    mBitmapRectBlack = makeBitmap(coverWidth, coverHeight, 0xff000000, coverWidth, coverHeight);
                    refreshLocation(bx, by, eX, eY);
                }
                break;
            case 1:/*right-up*/
                coverWidth = Math.abs(bx - sX);
                coverHeight = Math.abs(eY - by);
                if (!checkLegalRect(coverWidth, coverHeight)) {
                    MODE = MODE_ILLEGAL;
                } else {
                    mBitmapRectBlack = null;
                    mBitmapRectBlack = makeBitmap(coverWidth, coverHeight, 0xff000000, coverWidth, coverHeight);
                    refreshLocation(sX, by, bx, eY);
                }
                break;
            case 2:/*left-down*/
                coverWidth = Math.abs(eX - bx);
                coverHeight = Math.abs(by - sY);
                if (!checkLegalRect(coverWidth, coverHeight)) {
                    MODE = MODE_ILLEGAL;
                } else {
                    mBitmapRectBlack = null;
                    mBitmapRectBlack = makeBitmap(coverWidth, coverHeight, 0xff000000, coverWidth, coverHeight);
                    refreshLocation(bx, sY, eX, by);
                }
                break;
            case 3:/*right-down*/
                coverWidth = Math.abs(bx - sX);
                coverHeight = Math.abs(by - sY);
                if (!checkLegalRect(coverWidth, coverHeight)) {
                    MODE = MODE_ILLEGAL;
                } else {
                    mBitmapRectBlack = null;
                    mBitmapRectBlack = makeBitmap(coverWidth, coverHeight, 0xff000000, coverWidth, coverHeight);
                    refreshLocation(sX, sY, bx, by);
                }
                break;
            default:
                break;
        }
    }
 
    public void setLocationListener(onLocationListener locationListener) {
        this.locationListener = locationListener;
    }
 
    public interface onLocationListener {
        public void locationRect(int startX, int startY, int endX, int endY);
    }
 
    public interface onChangeLocationlistener {
        @SuppressWarnings("SameParameterValue")
        public void locationChange(String msg);
    }
}

简要说明

1.可移动的透明矩形框通过PorterDuffXfermode(在另一篇博文中有介绍,可点击这里查看)来实现

2.矩形边框的移动,缩放主要由onTouch事件做处理

3.onLocationListener 用于侦听矩形的坐标(最终可通过实现内部方法确定图像需要截取的位置)

3.onLocationListener 用于侦听矩形的坐标(最终可通过实现内部方法确定图像需要截取的位置)

如何使用PhotoCropView
在布局文件中导入
 

<com.xxx.PhotoCropView
        android:id="@+id/test"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"/>

绑定并设置侦听器:

mCropView = (PhotoCropView)mActivity.findViewById(R.id.photo_crop_photocrop);
mCropView .setLocationListener(this);

获取坐标信息:

@Override
    public void locationRect(int startX, int startY, int endX, int endY) {
        Log.d("[ "+startX+"--"+startY+"--"+endX+"--"+endY+" ]");
    }
扩展该视图
 

在此基础上可进行进一步的扩展,如:

1.进一步修改边框的色值做警示之用

2.舍弃边框改为增加4个边角

等等,可自行在onDraw()方法及外部方法中进行扩展如:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setFilterBitmap(false);
        int sc = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null,
                Canvas.MATRIX_SAVE_FLAG |
                        Canvas.CLIP_SAVE_FLAG |
                        Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
                        Canvas.FULL_COLOR_LAYER_SAVE_FLAG |
                        Canvas.CLIP_TO_LAYER_SAVE_FLAG);
 
        canvas.drawBitmap(mBitmapCover, 0, 0, mPaint);
        mPaint.setXfermode(xfermode);
        canvas.drawBitmap(mBitmapRectBlack, sX, sY, mPaint);
        if (locationListener != null) {
            locationListener.locationRect(sX, sY, eX, eY);
        }
        mPaint.setXfermode(null);
        canvas.restoreToCount(sc);
        /*在此添加4个边角...*/
    }

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

android自定义可缩放,移动图像裁剪框 的相关文章

  • 华为OD题目:快递投放问题

    华为OD题目 快递投放问题 有N个快递站点用字符串标识 某些站点之间有道路连接 每个站点有一些包裹要运输 每个站点间的包裹不重复 路上有检查站会导致部分货物无法通行 计算哪些货物无法正常投递 输入描述 1 第一行输入M N M个包裹N个道路
  • python中的random和range

    random import random print random randint 1 10 产生 1 到 10 的一个整数型随机数 包括1和10 print random random 产生 0 到 1 之间的随机浮点数 print ra
  • 靶场 : upload-labs1-10

    搭建 用phpstudy搭建的 搭建很简单 下载源码 放置在phpstudy的根目录下 在phpstudy中创建 步骤 这里上传的文件内容是一句话木马 pass 1 上传一个php文件试一试 传不上的 使用的白名单过滤的 我们考虑一下是什么
  • 12个超好用的配色网站

    每次做海报做ppt的时候总是为配色发愁 到底怎样才能调出好看的配色方案呢 调着调着感觉自己已经是个色盲了 今天小编就给你们带来了福利 12个超好用的配色网站推荐 01 Material Palette 网站地址 http www mater
  • 【SpringCloud】四、Spring Cloud Config

    Spring Cloud Config 前言 一 什么是配置中心 1 为什么需要分布式配置中心 2 常用分布式配置中心框架 二 什么是Spring Cloud Config 1 Springcloud config 的工作原理 2 构建 S
  • truffle教程

    直接在geth的控制台通过solc进行编译部署的示例已经很多了 比如这篇博客 此处不再赘述 本文主要演示怎样通过truffle部署以太坊智能合约 truffle是一个以太坊智能合约开发框架 它会帮你做很多琐碎的事情 安装使用都很简单 1 安
  • element-ui页面加载正确,页码错误

    期望效果 进入详情前第二页 从详情返回后还在第二页 先说我之前的误区 哈哈 之前想的是路由跳转时传参到详情页 详情页跳回时再传回来 绕了一圈 并不是最好的解决方案 解决办法 sessionStorage或localStorage 本质上最正
  • 深入理解 ES6 Promise

    https segmentfault com a 1190000020934044 引语 最近一段时间在重温ES6 Promise应该是是ES6新特性中非常重要的一部分内容 其实Promise在我日常开发中已经用得比较多 但大多数时候只是知
  • 智能优化算法之粒子群算法(PSO)的实现(Python附源码)

    一 粒子群算法的实现思路 粒子群算法 Particle Swarm Optimization PSO 是于1995年被Kennedy等人提出的一种模拟自然界中鸟群进行觅食过程的一种群智能优化算法 该算法将待求解问题的每一个候选解视作鸟群中的
  • 不得不会的代码注释工具——doxygen

    不得不会的代码注释工具 doxygen 下载 官网下载二进制或者直接用yum或apt工具下载 使用流程 进入项目目录生成doxygen配置文件 doxygen g 修改doxygen配置文件 程序文档输出目录 OUTPUT DIRECTOR
  • 获取List中的数据的实现方法

    大家都知道List是很好的存储数据的集合类 但是获取读取list中数据的方法你知道几种呢 一下是我知道的方法分享给大家 List
  • 黑客帝国全集故事情节解析

    黑客帝国全集故事情节解析 转载自 http www cnitblog com CreatorChen archive 2007 09 27 34083 html 一 前言 从 Matrix I 到 Matrix III 整整四年 一对名叫沃
  • 使用JMeter模拟多IP发送请求(IP欺骗)

    文章目录 一 IP欺骗的使用场景 二 JMeter如何设置IP欺骗 1 获取可用IP 2 配置IP 3 JMeter中配置IP欺骗 三 注意 前言 如果精通LoadRunner 可以直接使用LoadRunner进行IP欺骗 这里介绍如何使用
  • CTF练题(6)棋盘密码解密

    借一道题引出棋盘解密 题目详情如下 题目来自 青少年CTF训练平台 原中学生CTF平台 青少年CTF qsnctf com 题目给予的密文如下 解题 1 使用棋盘密码解码网站进行解密 网站如下 棋盘密码在线加密解密 千千秀字 qqxiuzi
  • 图像分类:Pytorch图像分类之--LetNet模型

    文章目录 前言 LetNet简介 程序的实现 model py的实现 LetNet模型操作流程 经过Conv卷积后输出尺寸的计算公式如下 Conv2d 函数介绍 MaxPool2d 函数介绍 Tensor的展平 view train py
  • oracle 关联加索引,给left join关联关系字段加索引

    给left join关联关系字段加索引 2018 07 04 left join是相当耗资源的操作 如果关联的字段没有索引的话 速度是很慢的 所以如果有left join的话 最好用索引字段取关联 创建索引会消耗大量资源 会导致数据库死锁
  • MySQL 联表查询重复数据并删除(子查询删除记录) SQL优化

    1 业务逻辑及表介绍 数据库表介绍 table a 主表 小表 表数据不可重复 table b 流水表 大表 记录审核流水数据 注 两表表结构大致一致 流水表增加一个审核状态的字段 业务逻辑 主表保存唯一数据 流水表记录审核流水数据 用于后
  • CSDN高校俱乐部“名师高校行”——贵州遵义站

    高校俱乐部简介 CSDN高校俱乐部是由CSDN发起 大学生自发加入的IT技术学习型组织 俱乐部至今已收到数百所高校成立技术社团的申请 已在全国100多所高校建立技术社团 俱乐部由此搭建起行业与校园之间的桥梁 为各高校社团提供大咖直播 线下巡
  • java之歌_【码歌】老薛带你编写第一个Java HelloWorld

    码歌 老薛带你编写第一个Java HelloWorld 书接上文 这次主要分享如何编写第一个Java的Hello World程序 以及在编写过程当中会出现哪些问题和如何解决 以及最重要的错误整理 需要转载请注明出处 https www ji
  • eclipse中导入外部的web项目后,Java类的第一行package处出现红线或者jsp页面第一行处出现红线错误的解决方案

    问题 你是否有过这样的经历 eclipse中导入一个web项目后 然后什么都没做出现了Java类的第一行 package处出现红线 或者jsp页面第一行 处出现红线错误 话不多说 请看图片 1 Java类的第一行 package处出现红线

随机推荐