Android自定义View实现图片裁剪功能(本地选择图片进行裁剪)

2023-10-26

使用安卓自带的裁剪工具,发现有版本兼容问题,而且图片模糊问题也不好解决,于是自己动手绘制一个裁剪工具。先看效果!

最终效果

自定义截图

实现思路

  1. 打开本地相册,获得图片Uri,Uri转为Bitmap。
  2. 用自定义View绘制可拖动选框,获得用户的裁剪意图。
  3. 用Bitmap.createBitmap(bitmap,0,0,0,0,null,false);进行裁剪。

一、打开本地相册,获得图片Uri,Uri转为Bitmap。

  1. 首先是打开相册
Intent intent = new Intent();
        intent.setAction(Intent.ACTION_PICK);
        intent.setType("image/*");
        intentActivityResultLauncher.launch(intent);
  1. 然后监听回调结果,onActivityResult被摒弃的事情不多说了,这里用registerForActivityResult。
private ActivityResultLauncher<Intent> intentActivityResultLauncher;
    private void initActivityResult() {
        intentActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
            if (result.getResultCode() == RESULT_OK) {
                Uri imageUri = Objects.requireNonNull(result.getData()).getData();
                InputStream image_stream = null;
                try {
                    image_stream = getContentResolver().openInputStream(imageUri);
                    Bitmap bitmap= BitmapFactory.decodeStream(image_stream );
                    //这里的bitmap就是我们要进行操作的位图,实现了第一步
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
            }
        });
    }

需要注意的是,这个东西一定要记得注销监听,不然会有内存泄露风险。

@Override
protected void onDestroy() {
   super.onDestroy();
   intentActivityResultLauncher.unregister();
}

2. 自定义View实现位图裁剪(直接copy去用,不需要考虑兼容性问题)

package com.example.cavasdemo;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;

import androidx.annotation.Nullable;

/**
 * 自定义View: Bitmap自定义裁剪工具
 * @Author 绝命三郎
 */
public class BitmapClippingView extends View {

    public BitmapClippingView(Context context) {
        this(context,null);
    }

    public BitmapClippingView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public BitmapClippingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    private float widthSize;//布局宽度
    private float heightSize;//布局高度

    private Bitmap bitmap;//要裁剪的位图
    private float bitmapWidth;//位图原始宽度
    private float bimapHeight;//位图原始高度
    private float proportionWidth;//比例:宽  如裁图比例3:4,此处传3
    private float proportionHeight;//比例:高  如裁图比例3:4,此处传4

    private Paint bitmapPaint;//图片画笔
    private Paint shadowPaint;//阴影画笔
    private Paint linePaint;//线条画笔

    float scaleStep;//缩放比例

    private boolean initTag=true;//用于判断是不是首次绘制
    private float leftLine=-1;//选区左线
    private float topLine=-1;//选区上线
    private float rightLine=-1;//选区右线
    private float bottomLine=-1;//选区下线

    private String focus="NONE";//事件焦点
    private final String LEFT_TOP="LEFT_TOP";//LEFT_TOP:拖动左上角
    private final String BODY="BODY";//BODY:拖动整体
    private final String RIGHT_BOTTOM="RIGHT_BOTTOM";//RIGHT_BOTTOM:拖动右下角
    private final String NONE="NONE";//NONE:释放焦点

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        widthSize=MeasureSpec.getSize(widthMeasureSpec);
        heightSize=MeasureSpec.getSize(heightMeasureSpec);

        bitmapPaint=new Paint();
        bitmapPaint.setStrokeWidth(0);

        shadowPaint=new Paint();
        shadowPaint.setColor(Color.parseColor("#57FF9800"));
        shadowPaint.setStrokeWidth(4);
        shadowPaint.setStyle(Paint.Style.FILL_AND_STROKE);

        linePaint=new Paint();
        linePaint.setColor(Color.parseColor("#FF9800"));
        linePaint.setStrokeWidth(4);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (bitmap==null)return;

        //绘制参考背景背景
        scaleStep=widthSize/bitmapWidth;
        float backgroundImgHeight=bimapHeight*scaleStep;
        heightSize=backgroundImgHeight;//把有效图像高度设置作为布局高度计算
        Rect rect=new Rect(0,0,(int)bitmapWidth,(int)bimapHeight);//裁剪图片中的部分(此处:全图)
        RectF rectF=new RectF(0,0,widthSize,backgroundImgHeight);//显示在屏幕中的什么位置
        canvas.drawBitmap(bitmap,rect,rectF,bitmapPaint);
        canvas.save();

        if (initTag){
            //绘制初始状态的选框(最大选框)
            if (bitmapWidth>bimapHeight){
                //宽大于高,取高
                float checkboxHeight=backgroundImgHeight;//选框的高
                float checkboxWidth=((checkboxHeight/proportionHeight)*proportionWidth);//选框的宽
                leftLine=(widthSize/2f)-(checkboxWidth/2f);
                topLine=(heightSize/2f)-(checkboxHeight/2f);
                rightLine=(widthSize/2f)+(checkboxWidth/2f);
                bottomLine=(heightSize/2f)+(checkboxHeight/2f);
            }else {
                //高大于宽 取宽
                float checkboxWidth=widthSize;//选框的宽
                float checkboxHeight=(widthSize/proportionWidth)*proportionHeight;//选框的高
                leftLine=(widthSize/2f)-(checkboxWidth/2f);
                topLine=(heightSize/2f)-(checkboxHeight/2f);
                rightLine=(widthSize/2f)+(checkboxWidth/2f);
                bottomLine=(heightSize/2f)+(checkboxHeight/2f);
            }
            initTag=false;
        }

        //绘制选择的区域
        //绘制周边阴影部分(分四个方块)
        linePaint.setColor(Color.parseColor("#FF9800"));
        linePaint.setStrokeWidth(4);
        canvas.drawRect(0,0,leftLine,heightSize,shadowPaint);//左
        canvas.drawRect(leftLine+4,0,rightLine-4,topLine,shadowPaint);//上
        canvas.drawRect(rightLine,0,widthSize,heightSize,shadowPaint);//右
        canvas.drawRect(leftLine+4,bottomLine,rightLine-4,heightSize,shadowPaint);//下

        //绘制选区边缘线
        canvas.drawLine(leftLine,topLine,rightLine,topLine,linePaint);
        canvas.drawLine(rightLine,topLine,rightLine,bottomLine,linePaint);
        canvas.drawLine(rightLine,bottomLine,leftLine,bottomLine,linePaint);
        canvas.drawLine(leftLine,bottomLine,leftLine,topLine,linePaint);

        //绘制左上和右下调节点
        linePaint.setColor(Color.RED);
        linePaint.setStrokeWidth(6);
        canvas.drawLine(rightLine-4,bottomLine-4,rightLine-4,bottomLine-40-4,linePaint);
        canvas.drawLine(rightLine-4,bottomLine-4,rightLine-40-4,bottomLine-4,linePaint);
        canvas.drawLine(leftLine+4,topLine+4,leftLine+40+4,topLine+4,linePaint);
        canvas.drawLine(leftLine+4,topLine+4,leftLine+4,topLine+40+4,linePaint);

        //绘制焦点圆
        linePaint.setStrokeWidth(2);
        linePaint.setStyle(Paint.Style.STROKE);
        canvas.drawCircle(rightLine-4,bottomLine-4,80,linePaint);
        canvas.drawCircle(leftLine+4,topLine+4,80,linePaint);

        //绘制扇形
        linePaint.setColor(Color.parseColor("#57FF0000"));
        linePaint.setStyle(Paint.Style.FILL_AND_STROKE);
        RectF mRectF = new RectF(rightLine-4-40, bottomLine-4-40, rightLine-4+40, bottomLine-4+40);
        canvas.drawArc(mRectF, 270, 270, true, linePaint);

        linePaint.setColor(Color.parseColor("#57FF0000"));
        linePaint.setStyle(Paint.Style.FILL_AND_STROKE);
        RectF mRectF2 = new RectF(leftLine+4-40, topLine+4-40, leftLine+4+40, topLine+4+40);
        canvas.drawArc(mRectF2, 90, 270, true, linePaint);

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        if (leftLine==-1)return false;
        if (topLine==-1)return false;
        if (rightLine==-1)return false;
        if (bottomLine==-1)return false;
        if (bitmap==null)return false;

        float touchX=event.getX();
        float touchY=event.getY();

        if (event.getAction()==MotionEvent.ACTION_DOWN){
            return actionDown(touchX,touchY);
        }

        if (event.getAction()==MotionEvent.ACTION_MOVE){
            return actionMove(touchX,touchY);
        }

        if (event.getAction()==MotionEvent.ACTION_UP){
            return actionUp(touchX,touchY);
        }

        return true;
    }

    //抬起
    private boolean actionUp(float touchX, float touchY) {
        Log.d("fxHou","抬起X="+touchX+"   touchY="+touchY);
        Log.d("fxHou","释放焦点");
        focus=NONE;//释放焦点
        return true;
    }

    //移动
    private boolean actionMove(float touchX, float touchY) {
        Log.d("fxHou","滑动X="+touchX+"   touchY="+touchY);
        if (focus.equals(LEFT_TOP)){
            //移动边线
            leftLine=touchX;
            topLine=bottomLine-(((rightLine-leftLine)/proportionWidth)*proportionHeight);//约束比例

            //限制最小矩形 宽
            if (rightLine-leftLine<100){
                leftLine=rightLine-100;
                topLine=bottomLine-(((rightLine-leftLine)/proportionWidth)*proportionHeight);
                //重绘
                postInvalidate();
                return true;
            }

            //限制最小矩形 高
            if (bottomLine-topLine<100){
                topLine=bottomLine-100;
                leftLine=rightLine-((bottomLine-topLine)/proportionHeight)*proportionWidth;
                //重绘
                postInvalidate();
                return true;
            }

            //防止超出边界
            if (leftLine<0){
                leftLine=0;
                topLine=bottomLine-(((rightLine-leftLine)/proportionWidth)*proportionHeight);
            }

            //防止超出边界
            if (topLine<0){
                topLine=0;
                leftLine=rightLine-((bottomLine-topLine)/proportionHeight)*proportionWidth;
            }

            //重绘
            postInvalidate();
            return true;
        }else if (focus.equals(RIGHT_BOTTOM)){
            //移动边线
            rightLine=touchX;
            bottomLine=topLine+(((rightLine-leftLine)/proportionWidth)*proportionHeight);//约束比例

            //限制最小矩形 宽
            if (rightLine-leftLine<100){
                rightLine=leftLine+100;
                bottomLine=topLine+(((rightLine-leftLine)/proportionWidth)*proportionHeight);
                //重绘
                postInvalidate();
                return true;
            }

            //限制最小矩形 高
            if (bottomLine-topLine<100){
                bottomLine=topLine+100;
                rightLine=leftLine+(((bottomLine-topLine)/proportionHeight)*proportionWidth);
                //重绘
                postInvalidate();
                return true;
            }

            //防止超出边界
            if (rightLine>widthSize){
                rightLine=widthSize;
                bottomLine=topLine+(((rightLine-leftLine)/proportionWidth)*proportionHeight);
            }

            //防止超出边界
            if (bottomLine>heightSize){
                bottomLine=heightSize;
                rightLine=leftLine+(((bottomLine-topLine)/proportionHeight)*proportionWidth);
            }
            //重绘
            postInvalidate();
            return true;
        }else if (focus.equals(BODY)){
            float moveX=touchX-downX;
            float moveY=touchY-downY;
            leftLine=downLeftLine+moveX;
            rightLine=downRightLine+moveX;
            topLine=downTopLine+moveY;
            bottomLine=downBottomLine+moveY;

            if (leftLine<0){
                rightLine=(rightLine-leftLine);
                leftLine=0;

                if (topLine<0){
                    bottomLine=bottomLine-topLine;
                    topLine=0;
                    //重绘
                    postInvalidate();
                    return true;
                }

                if (bottomLine>heightSize){
                    topLine=heightSize-(bottomLine-topLine);
                    bottomLine=heightSize;
                    //重绘
                    postInvalidate();
                    return true;
                }

                //重绘
                postInvalidate();
                return true;
            }

            if (rightLine>widthSize){
                leftLine=widthSize-(rightLine-leftLine);
                rightLine=widthSize;

                if (topLine<0){
                    bottomLine=bottomLine-topLine;
                    topLine=0;
                    //重绘
                    postInvalidate();
                    return true;
                }

                if (bottomLine>heightSize){
                    topLine=heightSize-(bottomLine-topLine);
                    bottomLine=heightSize;
                    //重绘
                    postInvalidate();
                    return true;
                }

                //重绘
                postInvalidate();
                return true;
            }

            if (topLine<0){
                bottomLine=bottomLine-topLine;
                topLine=0;
                //重绘
                postInvalidate();
                return true;
            }

            if (bottomLine>heightSize){
                topLine=heightSize-(bottomLine-topLine);
                bottomLine=heightSize;
                //重绘
                postInvalidate();
                return true;
            }
            //重绘
            postInvalidate();
            return true;
        }
        return true;
    }

    //按下
    private float downX,downY,downLeftLine,downTopLine,downRightLine,downBottomLine;
    private boolean actionDown(float touchX, float touchY) {
        downX=touchX;
        downY=touchY;
        downLeftLine=leftLine;
        downTopLine=topLine;
        downRightLine=rightLine;
        downBottomLine=bottomLine;
        Log.d("fxHou","按下X="+touchX+"   touchY="+touchY);
        boolean condition1=touchX>leftLine-40 && touchX<leftLine+40;
        boolean condition2=touchY>topLine-40 && touchY<topLine+40;
        if (condition1 && condition2){
            Log.d("fxHou","左上获得焦点");
            focus=LEFT_TOP;//左上获得焦点
            return true;
        }

        boolean condition3=touchX>rightLine-40 && touchX<rightLine+40;
        boolean condition4=touchY>bottomLine-40 && touchY<bottomLine+40;
        if (condition3 && condition4){
            Log.d("fxHou","右下获得焦点");
            focus=RIGHT_BOTTOM;//右下获得焦点
            return true;
        }

        boolean condition5=touchX>leftLine && touchX<rightLine;
        boolean condition6=touchY>topLine && touchY<bottomLine;
        if (condition5 && condition6){
            Log.d("fxHou","整体获得焦点");
            focus=BODY;//整体获得焦点
            return true;
        }

        return true;
    }

    /**
     * 设置要裁剪的位图
     * @param bitmap 要裁剪的位图
     * @param proportionWidth  比例:宽  如裁图比例3:4,此处传3
     * @param proportionHeight 比例:高  如裁图比例3:4,此处传4
     */
    public void setBitmap(Bitmap bitmap,int proportionWidth,int proportionHeight){
        this.bitmap=bitmap;
        bitmapWidth=bitmap.getWidth();
        bimapHeight=bitmap.getHeight();
        this.proportionWidth=proportionWidth;
        this.proportionHeight=proportionHeight;
        initTag=true;
        postInvalidate();
    }

    /**
     * 获取裁剪后的位图
     * @param context
     * @param minPixelWidth 限制最小宽度(像素)
     * @param minPixelHeight 限制最小高度(像素)
     * @return 裁切后的位图
     */
    public Bitmap getBitmap(Context context,int minPixelWidth,int minPixelHeight){
        if (bitmap==null)return null;
        int startX= (int) (leftLine/scaleStep);
        int startY= (int) (topLine/scaleStep);
        int cutWidth=(int) ((rightLine/scaleStep)-(leftLine/scaleStep));
        int cutHeight=(int) (bottomLine/scaleStep-topLine/scaleStep);

        Bitmap newBitmap=Bitmap.createBitmap(bitmap,startX,startY,cutWidth,cutHeight,null,false);

        if (newBitmap.getWidth()<minPixelWidth || newBitmap.getHeight()<minPixelHeight){
            Toast.makeText(context, "图片太模糊了", Toast.LENGTH_SHORT).show();
            return null;
        }

        return newBitmap;
    }
}

使用方法

<com.example.cavasdemo.BitmapClippingView
            android:id="@+id/my_cavas"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
BitmapClippingView my_cavas=(BitmapClippingView)findViewById(R.id.my_cavas);
//把刚刚本地相册拿到的bitmap传进去,用户进行自定义裁剪
my_cavas.setBitmap(bitmap,3,4);
//获取裁剪后的结果
Bitmap resulBitmap=my_cavas.getBitmap(context,600,800);

最后:拿到裁剪后的resulBitmap,做你想做的事。

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

Android自定义View实现图片裁剪功能(本地选择图片进行裁剪) 的相关文章

随机推荐

  • 海明校验码

    1 海明码的特点 其中m表示数据位的位数 k表示海明校验码的位数 k位海明校验码一共可以表示种校验信息结果 其中有一种要用来表示没有出错的情况 则其余还剩 1种结果 为了使校验结果可以指出任一位出错的位置 则需要满足以上不等式 2 举例说明
  • 树莓派搭建K8S集群

    最近学习k8s知识 想用树莓派搭建集群 在网找了不少 就发现一篇文章可以搭建成功香橙派4和树莓派4B构建K8S集群实践之一 K8S安装 参考了不少 这里主要记录下遇到的一些问题 参考的文章 是香橙派和树莓派 我这里全是树莓派 所以是树莓派路
  • js判断Android、iOS或浏览器

    第一种 通过判断浏览器的userAgent 用正则来判断是否是ios和Android客户端 代码如下
  • Python 八大排序算法合集

    1 选择排序 选择排序 升序 不稳定排序 原理 给定一个列表 经过第一轮比较后 找到最小值 与第一个位置交换 接着对不包括第一个元素的剩下的元素 找到最小值 与第二个位置交换 重复该过程 直到进行比较的记录只有一个为止 以 list 5 4
  • 关于STM32F0407译出错问题

    嵌入式编译出错问题 关于STM32F0407译出错问题 OBJ BEEP axf Error L6218E Undefined symbol TIM ClearITPendingBit referred from main o OBJ BE
  • Android系统开发之修改Captive Potal Service(消灭感叹号)

    本文原作者 长鸣鸟 未经同意 转载不带名的严重鄙视 谷歌在Android5 0之后的版本加入了CaptivePotalLogin服务 本服务的功能是检查网络连接互联网情况 主要针对于Wi Fi 不让Android设备自动连接那些不能联网的无
  • 查看应用程序依赖库

    1 ldd 如果是用x86架构编译的话 ldd可查看依赖的动态库 ldd a out linux vdso so 1 gt 0x00007fff13cd9000 libc so 6 gt lib x86 64 linux gnu libc
  • 不知道怎么开发VR游戏?Unity5.3官方VR教程重磅登场-系列3 VR中的交互方式

    不知道怎么开发VR游戏 Unity5 3官方VR教程重磅登场 系列3 VR中的交互方式 王寒 4 个月前 https zhuanlan zhihu com p 20505470 概览 在VR项目中 我们需要在用户 凝视 某个物体时将其激活
  • h3c端口映射本地主机或服务器

    本地打开网站或服务器记住端口xxx 进入h3c服务器 进入内部服务器做端口映射 接口选择 wan口 使用当前外部IP 外部端口建议使用高数字端口YYYY 内部IP地址为服务器或网站所在的IP地址 内部端口为使用的端口xxx
  • chatgpt和copilot有关系吗

    chatgpt和copilot之间并没有直接的关系 chatgpt是一个开源的聊天机器人项目 是由谷歌开发的深度学习模型GPT 2 Generative Pre training Transformer 2 提供自然语言生成能力的一个实现
  • 学习笔记-Matlab算法篇-动态规划

    动态规划 01介绍 介绍 动态规划 dynamic programming 是运筹学的一个分支 是求解决策过程 decision process 最优化的数学方法 动态规划是求解某类问题的一种方法 是考察问题的一种途径 而不是一种特殊算法
  • weex实践初探

    weex是阿里2016年开源的项目 号称通过撰写HTML CSS JavaScript来开发原生android ios的UI界面 并且接近原生的性能体验 写一次 多端编译 一直是无线移动追求的目标 既然阿里牛皮吹得这么大 本人也非常迫切体验
  • EncodedResource类解读

    EncodedResource类解读 EncodedResource介绍 EncodedResource是spring中Resource编码相关的封装类 EncodedResource里面封装了一个Resource成员属性 其实主要功能就是
  • MySQL索引类型与索引原理

    1 索引类型 索引可以提升查询速度 会影响where查询 以及order by排序 MySQL索引类型如下 从索引存储结构划分 B Tree索引 Hash索引 FULLTEXT全文索引 R Tree索引 从应用层次划分 普通索引 唯一索引
  • Uncaught ReferenceError: xxx is not defined at HTMLInputElement.onclick已解决

    触发标签的onclick事件报错如下 Uncaught ReferenceError http is not defined at HTMLInputElement onclick list do pageType initialize 2
  • Flutter1.0入门基础

    Flutter1 0入门基础 注 原课程视频是基于Flutter1的 目标 开发入门 工具 环境搭建 入门必备 开发技巧 导航框架 常用功能 开发流程 网络 数据存储 列表 Flutter与Native混编 工程封装 模块开发 AI结合 项
  • java初始化map的四种方式

    第一种 最常见的方式 新建Map对象 public class Demo private static final Map
  • extern指针和数组的用法

    对extern我们先来一段直白的告白 extern是计算机语言中的一个函数 可置于变量或者函数前 以表示变量或者函数的定义在别的文件中 提示编译器遇到此变量或函数时 在其它模块中寻找其定义 另外 extern也可用来进行链接指定 来自百度百
  • 毕业设计:电子/通信/物联网/计算机专业选题目参考(嵌入式linux/单片机STM32/web/图像)

    本文推荐的毕业设计题目涉及以下技术 嵌入式Linux 单片机STM32 Opencv Qt Web 百度AI YOLO 目标检测 深度学习 等 适用于 电子信息 通信 物联网 计算机 等专业的毕业设计题目 支持服务 题目定制 选题答疑 代做
  • Android自定义View实现图片裁剪功能(本地选择图片进行裁剪)

    使用安卓自带的裁剪工具 发现有版本兼容问题 而且图片模糊问题也不好解决 于是自己动手绘制一个裁剪工具 先看效果 最终效果 自定义截图 实现思路 打开本地相册 获得图片Uri Uri转为Bitmap 用自定义View绘制可拖动选框 获得用户的