it编程 > App开发 > Android

Android实现图片裁剪处理的操作步骤

44人参与 2025-02-13 Android

前言

本文将介绍如何构建一个支持图片选择、裁剪(包括手动缩放和旋转)、以及保存到自定义路径的android应用demo。

步骤 1: 设置权限

首先,在androidmanifest.xml中添加必要的权限:

<uses-permission android:name="android.permission.read_external_storage" />
<uses-permission android:name="android.permission.write_external_storage" />

对于 android 6.0 (api level 23) 及以上版本,需要在运行时请求权限。

步骤 2: 创建布局文件

创建一个简单的布局文件activity_main.xml,包含一个用于显示图片的customcropimageview,以及几个按钮用于控制裁剪操作。

<?xml version="1.0" encoding="utf-8"?>
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.customcropimageview
        android:id="@+id/customcropimageview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <button
        android:id="@+id/buttonpickimage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="pick image"
        android:layout_below="@id/customcropimageview" />

    <button
        android:id="@+id/buttoncrop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="crop"
        android:layout_toendof="@id/buttonpickimage"
        android:layout_below="@id/customcropimageview" />

    <button
        android:id="@+id/buttoncancel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="cancel"
        android:layout_toendof="@id/buttoncrop"
        android:layout_below="@id/customcropimageview" />

    <button
        android:id="@+id/buttonsave"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="save"
        android:layout_toendof="@id/buttoncancel"
        android:layout_below="@id/customcropimageview" />
</relativelayout>

步骤 3: 实现自定义view customcropimageview

接下来,我们将详细实现customcropimageview,这个自定义视图负责所有与裁剪相关的交互逻辑。

customcropimageview.java

```java
import android.content.context;
import android.graphics.bitmap;
import android.graphics.canvas;
import android.graphics.color;
import android.graphics.matrix;
import android.graphics.paint;
import android.graphics.path;
import android.graphics.porterduff;
import android.graphics.porterduffxfermode;
import android.graphics.rectf;
import android.util.attributeset;
import android.view.gesturedetector;
import android.view.motionevent;
import android.view.scalegesturedetector;
import android.widget.framelayout;

public class customcropimageview extends framelayout {

    // 成员变量定义
    private bitmap mbitmap; // 要裁剪的图片
    private matrix mmatrix = new matrix(); // 用于变换(缩放、旋转)图像的矩阵
    private rectf mrect = new rectf(); // 定义裁剪框的位置和大小
    private float[] mlasttouchpos = new float[2]; // 上次触摸位置,用于计算移动距离
    private float[] mcurrentpos = new float[2]; // 当前触摸位置,用于更新图像位置
    private float mrotation = 0f; // 图像的旋转角度
    private boolean misdragging = false; // 标记是否正在拖动图像
    private scalegesturedetector mscaledetector; // 检测多点触控缩放手势
    private gesturedetector mgesturedetector; // 检测单点触控手势(如点击)

    // 构造函数,初始化自定义视图
    public customcropimageview(context context, attributeset attrs) {
        super(context, attrs);
        // 初始化手势检测器
        mscaledetector = new scalegesturedetector(context, new scalelistener());
        mgesturedetector = new gesturedetector(context, new gesturelistener());
    }

    @override
    protected void ondraw(canvas canvas) {
        super.ondraw(canvas);

        if (mbitmap != null) {
            // 绘制背景蒙层,使非裁剪区域变暗
            drawoverlay(canvas);

            // 保存当前canvas状态,以便稍后恢复
            canvas.save();
            // 将canvas原点移动到裁剪框中心,进行旋转操作
            canvas.translate(mrect.centerx(), mrect.centery());
            canvas.rotate(mrotation);
            // 移回原点以绘制旋转后的图像
            canvas.translate(-mrect.centerx(), -mrect.centery());
            // 使用变换矩阵绘制图像
            canvas.drawbitmap(mbitmap, mmatrix, null);
            // 恢复canvas到之前的状态
            canvas.restore();

            // 绘制裁剪框,让用户知道哪里会被裁剪
            drawcropbox(canvas);
        }
    }

    private void drawoverlay(canvas canvas) {
        paint paint = new paint();
        // 设置半透明黑色作为蒙层颜色
        paint.setcolor(color.argb(128, 0, 0, 0));
        // 填充整个视图为半透明黑色
        canvas.drawrect(0, 0, getwidth(), getheight(), paint);

        // 创建一个路径,添加裁剪框形状
        path path = new path();
        path.addrect(mrect, path.direction.cw);
        // 使用canvas.clippath剪切出裁剪框区域,使其透明
        // 注意:region.op.difference在api 26以上已被弃用,应考虑使用其他方式实现相同效果
        canvas.clippath(path, region.op.difference);
        canvas.drawcolor(color.transparent, porterduff.mode.clear);
    }

    private void drawcropbox(canvas canvas) {
        paint paint = new paint(paint.anti_alias_flag); // 抗锯齿
        paint.setstyle(paint.style.stroke); // 只绘制边框,不填充内部
        paint.setstrokewidth(5); // 边框宽度
        paint.setcolor(color.blue); // 裁剪框颜色设置为蓝色
        // 绘制裁剪框矩形
        canvas.drawrect(mrect, paint);
    }

    @override
    public boolean ontouchevent(motionevent event) {
        // 分别将事件传递给缩放和手势检测器
        mscaledetector.ontouchevent(event);
        mgesturedetector.ontouchevent(event);

        switch (event.getaction()) {
            case motionevent.action_down:
                // 记录按下时的坐标,开始拖动
                mlasttouchpos[0] = event.getx();
                mlasttouchpos[1] = event.gety();
                misdragging = true;
                break;
            case motionevent.action_move:
                if (misdragging) {
                    // 更新当前位置,并根据位移调整矩阵和平移裁剪框
                    mcurrentpos[0] = event.getx();
                    mcurrentpos[1] = event.gety();
                    updatematrix();
                    invalidate(); // 请求重新绘制界面
                }
                break;
            case motionevent.action_up:
                // 结束拖动
                misdragging = false;
                break;
        }
        return true;
    }

    private void updatematrix() {
        // 更新矩阵以反映图像的新位置
        mmatrix.settranslate(mcurrentpos[0] - mlasttouchpos[0], mcurrentpos[1] - mlasttouchpos[1]);
        // 同步裁剪框的位置
        mrect.offset(mcurrentpos[0] - mlasttouchpos[0], mcurrentpos[1] - mlasttouchpos[1]);
    }

    private class scalelistener extends scalegesturedetector.simpleonscalegesturelistener {
        @override
        public boolean onscale(scalegesturedetector detector) {
            // 获取缩放因子并应用到矩阵上,保持缩放中心点不变
            float scalefactor = detector.getscalefactor();
            mmatrix.postscale(scalefactor, scalefactor, detector.getfocusx(), detector.getfocusy());
            invalidate(); // 请求重绘以反映变化
            return true;
        }
    }

    private class gesturelistener extends gesturedetector.simpleongesturelistener {
        @override
        public boolean ondoubletap(motionevent e) {
            // 双击时重置所有变换
            resettransformations();
            return true;
        }
    }

    private void resettransformations() {
        // 重置矩阵和旋转角度,以及裁剪框位置
        mmatrix.reset();
        mrotation = 0f;
        mrect.set(/* default values */);
        invalidate(); // 请求重绘
    }

    // 设置要裁剪的图片
    public void setimagebitmap(bitmap bitmap) {
        mbitmap = bitmap;
        // 根据新图片尺寸调整裁剪框大小
        updatecropboxsize();
        requestlayout(); // 请求布局更新
        invalidate(); // 请求重绘
    }

    private void updatecropboxsize() {
        // 根据所选图片的尺寸设置合适的裁剪框大小
        int width = mbitmap.getwidth();
        int height = mbitmap.getheight();
        float aspectratio = (float) width / height;

        // 设定裁剪框的初始尺寸为图片的中心区域,同时确保其宽高比与原始图片一致
        float rectwidth = math.min(getwidth(), getheight() * aspectratio);
        float rectheight = math.min(getheight(), getwidth() / aspectratio);
        mrect.set((getwidth() - rectwidth) / 2, (getheight() - rectheight) / 2, (getwidth() + rectwidth) / 2, (getheight() + rectheight) / 2);
    }

    // 获取裁剪后的图片
    public bitmap getcroppedbitmap() {
        // 创建一个新的位图来容纳裁剪结果
        bitmap croppedbitmap = bitmap.createbitmap(
            (int)mrect.width(),
            (int)mrect.height(),
            bitmap.config.argb_8888
        );
        canvas canvas = new canvas(croppedbitmap);
        // 平移画布以对齐裁剪框左上角
        canvas.translate(-mrect.left, -mrect.top);
        // 绘制变换后的原始图片到新的位图中
        canvas.drawbitmap(mbitmap, mmatrix, null);
        return croppedbitmap;
    }
}

自定义的customcropimageview,它允许用户通过触摸屏交互来裁剪图片。该视图支持基本的手势操作,包括拖动、缩放和双击重置。此外,还提供了设置图片和获取裁剪后图片的方法。

步骤 4: 更新activity逻辑

现在我们将更新mainactivity.java,以加载图片到自定义视图,并处理裁剪后的保存逻辑。

mainactivity.java

import android.manifest;
import android.content.intent;
import android.content.pm.packagemanager;
import android.graphics.bitmap;
import android.net.uri;
import android.os.bundle;
import android.provider.mediastore;
import androidx.annotation.nonnull;
import androidx.annotation.nullable;
import androidx.appcompat.app.appcompatactivity;
import androidx.core.app.activitycompat;
import androidx.core.content.contextcompat;

import java.io.file;
import java.io.fileoutputstream;
import java.io.ioexception;

public class mainactivity extends appcompatactivity {

    private static final int pick_image_request = 1;
    private static final int request_permissions = 2;
    private customcropimageview customcropimageview;
    private uri imageuri;

    @override
    protected void oncreate(bundle savedinstancestate) {
        super.oncreate(savedinstancestate);
        setcontentview(r.layout.activity_main);

        customcropimageview = findviewbyid(r.id.customcropimageview);

        // 检查并请求存储权限
        if (contextcompat.checkselfpermission(this, manifest.permission.write_external_storage)
                != packagemanager.permission_granted) {
            activitycompat.requestpermissions(this,
                    new string[]{manifest.permission.read_external_storage, manifest.permission.write_external_storage},
                    request_permissions);
        }

        findviewbyid(r.id.buttonpickimage).setonclicklistener(v -> pickimage());
        findviewbyid(r.id.buttoncrop).setonclicklistener(v -> cropimage());
        findviewbyid(r.id.buttoncancel).setonclicklistener(v -> cancelcrop());
        findviewbyid(r.id.buttonsave).setonclicklistener(v -> saveimage());
    }

    private void pickimage() {
        intent intent = new intent(intent.action_get_content);
        intent.settype("image/*");
        startactivityforresult(intent, pick_image_request);
    }

    private void cropimage() {
        // 如果使用第三方库如ucrop,可以在这里启动裁剪活动
        // 这里我们假设customcropimageview已经包含了所有裁剪功能
        // 因此不需要启动新的活动。
    }

    private void cancelcrop() {
        // 重置customcropimageview的状态
        customcropimageview.resettransformations();
    }

    private void saveimage() {
        bitmap bitmap = customcropimageview.getcroppedbitmap(); // 获取裁剪后的位图
        try {
            file path = new file(getexternalfilesdir(null), "custom_folder");
            if (!path.exists()) {
                path.mkdirs();
            }
            file file = new file(path, "cropped_image.jpg");
            fileoutputstream out = new fileoutputstream(file);
            bitmap.compress(bitmap.compressformat.jpeg, 90, out);
            out.flush();
            out.close();
            // 提示用户图片已保存
        } catch (ioexception e) {
            e.printstacktrace();
            // 处理保存失败的情况
        }
    }

    @override
    protected void onactivityresult(int requestcode, int resultcode, @nullable intent data) {
        super.onactivityresult(requestcode, resultcode, data);
        if (requestcode == pick_image_request && resultcode == result_ok && data != null && data.getdata() != null) {
            imageuri = data.getdata();
            try {
                // 将选择的图片加载到customcropimageview中
                bitmap bitmap = mediastore.images.media.getbitmap(getcontentresolver(), imageuri);
                customcropimageview.setimagebitmap(bitmap);
            } catch (ioexception e) {
                e.printstacktrace();
            }
        }
    }

    @override
    public void onrequestpermissionsresult(int requestcode, @nonnull string[] permissions, @nonnull int[] grantresults) {
        super.onrequestpermissionsresult(requestcode, permissions, grantresults);
        if (requestcode == request_permissions) {
            if (grantresults.length > 0 && grantresults[0] == packagemanager.permission_granted) {
                // 权限授予成功,可以继续进行图片选择等操作
            } else {
                // 用户拒绝了权限,需要提示用户或者禁用相关功能
            }
        }
    }
}

总结

通过上述步骤,我们完成了一个具有裁剪功能的android demo。该应用允许用户从相册或相机选择图片,在界面上进行裁剪、旋转和缩放,并最终将处理过的图片保存到指定位置。

以上就是android实现图片裁剪处理的操作步骤的详细内容,更多关于android图片裁剪处理的资料请关注代码网其它相关文章!

(0)
打赏 微信扫一扫 微信扫一扫

您想发表意见!!点此发布评论

推荐阅读

Android使用WebView加载播放视频流及实现相关功能

02-13

Android四种方式刷新View的操作方法

02-13

四种Flutter子页面向父组件传递数据的方法介绍

02-13

Unity读取Android外部文件的实现

02-13

Android kotlin语言实现删除文件的解决方案

02-13

Android 系统签名 keytool-importkeypair的操作步骤

02-13

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论