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

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

最终效果

自定义截图

实现思路
  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,做你想做的事。
  • 7
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
好的,下面是一个实现圆形边框和图片裁剪自定义View的完整代码: ``` public class CircleImageView extends ImageView { private Paint mBorderPaint; private int mBorderColor; private int mBorderWidth; private Bitmap mBitmap; private BitmapShader mBitmapShader; private int mBitmapWidth; private int mBitmapHeight; private float mRadius; private RectF mBorderRect; public CircleImageView(Context context) { super(context); init(); } public CircleImageView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public CircleImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mBorderPaint = new Paint(); mBorderPaint.setAntiAlias(true); mBorderRect = new RectF(); } @Override protected void onDraw(Canvas canvas) { if (mBitmapShader == null) { mBitmap = getBitmap(); if (mBitmap != null) { mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); mBitmapWidth = mBitmap.getWidth(); mBitmapHeight = mBitmap.getHeight(); mRadius = Math.min(mBitmapWidth, mBitmapHeight) / 2; updateShaderMatrix(); } } if (mBitmapShader != null) { mBorderPaint.setShader(mBitmapShader); canvas.drawCircle(mRadius, mRadius, mRadius, mBorderPaint); mBorderPaint.setShader(null); mBorderPaint.setColor(mBorderColor); mBorderPaint.setStrokeWidth(mBorderWidth); mBorderRect.set(0, 0, getWidth(), getHeight()); canvas.drawArc(mBorderRect, 0, 360, false, mBorderPaint); } } private void updateShaderMatrix() { float scale; float dx = 0; float dy = 0; if (mBitmapWidth * getHeight() > getWidth() * mBitmapHeight) { scale = getHeight() / (float) mBitmapHeight; dx = (getWidth() - mBitmapWidth * scale) * 0.5f; } else { scale = getWidth() / (float) mBitmapWidth; dy = (getHeight() - mBitmapHeight * scale) * 0.5f; } Matrix matrix = new Matrix(); matrix.setScale(scale, scale); matrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); mBitmapShader.setLocalMatrix(matrix); } public void setBorderColor(int borderColor) { if (borderColor == mBorderColor) { return; } mBorderColor = borderColor; invalidate(); } public void setBorderWidth(int borderWidth) { if (borderWidth == mBorderWidth) { return; } mBorderWidth = borderWidth; invalidate(); } private Bitmap getBitmap() { Drawable drawable = getDrawable(); if (drawable == null) { return null; } if (drawable instanceof BitmapDrawable) { return ((BitmapDrawable) drawable).getBitmap(); } Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); drawable.draw(canvas); return bitmap; } } ``` 使用方法: 在布局文件中添加自定义View: ``` <com.example.CircleImageView android:id="@+id/circle_image_view" android:layout_width="100dp" android:layout_height="100dp" android:src="@drawable/avatar" app:border_color="#ffffff" app:border_width="4dp" /> ``` 其中 `app:border_color` 和 `app:border_width` 分别表示边框的颜色和宽度,可以根据需要调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

绝命三郎

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值