设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间08 现在时间08 缓存数据 请试着,为欲望和懒惰松绑。你的人生,总要自己渡河摇舟。 ​早安,又是新的一天。

请试着,为欲望和懒惰松绑。你的人生,总要自己渡河摇舟。 ​早安,又是新的一天。

查看: 811|回复: 0

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

[复制链接]

  离线 

TA的专栏

  • 打卡等级:热心大叔
  • 打卡总天数:228
  • 打卡月天数:0
  • 打卡总奖励:3327
  • 最近打卡:2025-03-22 00:11:05
等级头衔

等級:晓枫资讯-上等兵

在线时间
0 小时

积分成就
威望
0
贡献
384
主题
363
精华
0
金钱
4497
积分
806
注册时间
2023-1-8
最后登录
2025-5-31

发表于 2025-5-31 06:48:55 | 显示全部楼层 |阅读模式
前言

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

步骤 1: 设置权限

首先,在
  1. AndroidManifest.xml
复制代码
中添加必要的权限:
  1. <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  2. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
复制代码
对于 Android 6.0 (API level 23) 及以上版本,需要在运行时请求权限。

步骤 2: 创建布局文件

创建一个简单的布局文件
  1. activity_main.xml
复制代码
,包含一个用于显示图片的
  1. CustomCropImageView
复制代码
,以及几个按钮用于控制裁剪操作。
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.     android:layout_width="match_parent"
  4.     android:layout_height="match_parent">

  5.     <com.example.CustomCropImageView
  6.         android:id="@+id/customCropImageView"
  7.         android:layout_width="match_parent"
  8.         android:layout_height="wrap_content" />

  9.     <Button
  10.         android:id="@+id/buttonPickImage"
  11.         android:layout_width="wrap_content"
  12.         android:layout_height="wrap_content"
  13.         android:text="Pick Image"
  14.         android:layout_below="@id/customCropImageView" />

  15.     <Button
  16.         android:id="@+id/buttonCrop"
  17.         android:layout_width="wrap_content"
  18.         android:layout_height="wrap_content"
  19.         android:text="Crop"
  20.         android:layout_toEndOf="@id/buttonPickImage"
  21.         android:layout_below="@id/customCropImageView" />

  22.     <Button
  23.         android:id="@+id/buttonCancel"
  24.         android:layout_width="wrap_content"
  25.         android:layout_height="wrap_content"
  26.         android:text="Cancel"
  27.         android:layout_toEndOf="@id/buttonCrop"
  28.         android:layout_below="@id/customCropImageView" />

  29.     <Button
  30.         android:id="@+id/buttonSave"
  31.         android:layout_width="wrap_content"
  32.         android:layout_height="wrap_content"
  33.         android:text="Save"
  34.         android:layout_toEndOf="@id/buttonCancel"
  35.         android:layout_below="@id/customCropImageView" />
  36. </RelativeLayout>
复制代码
步骤 3: 实现自定义View CustomCropImageView

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

CustomCropImageView.java
  1. ```java
  2. import android.content.Context;
  3. import android.graphics.Bitmap;
  4. import android.graphics.Canvas;
  5. import android.graphics.Color;
  6. import android.graphics.Matrix;
  7. import android.graphics.Paint;
  8. import android.graphics.Path;
  9. import android.graphics.PorterDuff;
  10. import android.graphics.PorterDuffXfermode;
  11. import android.graphics.RectF;
  12. import android.util.AttributeSet;
  13. import android.view.GestureDetector;
  14. import android.view.MotionEvent;
  15. import android.view.ScaleGestureDetector;
  16. import android.widget.FrameLayout;

  17. public class CustomCropImageView extends FrameLayout {

  18.     // 成员变量定义
  19.     private Bitmap mBitmap; // 要裁剪的图片
  20.     private Matrix mMatrix = new Matrix(); // 用于变换(缩放、旋转)图像的矩阵
  21.     private RectF mRect = new RectF(); // 定义裁剪框的位置和大小
  22.     private float[] mLastTouchPos = new float[2]; // 上次触摸位置,用于计算移动距离
  23.     private float[] mCurrentPos = new float[2]; // 当前触摸位置,用于更新图像位置
  24.     private float mRotation = 0f; // 图像的旋转角度
  25.     private boolean mIsDragging = false; // 标记是否正在拖动图像
  26.     private ScaleGestureDetector mScaleDetector; // 检测多点触控缩放手势
  27.     private GestureDetector mGestureDetector; // 检测单点触控手势(如点击)

  28.     // 构造函数,初始化自定义视图
  29.     public CustomCropImageView(Context context, AttributeSet attrs) {
  30.         super(context, attrs);
  31.         // 初始化手势检测器
  32.         mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
  33.         mGestureDetector = new GestureDetector(context, new GestureListener());
  34.     }

  35.     @Override
  36.     protected void onDraw(Canvas canvas) {
  37.         super.onDraw(canvas);

  38.         if (mBitmap != null) {
  39.             // 绘制背景蒙层,使非裁剪区域变暗
  40.             drawOverlay(canvas);

  41.             // 保存当前Canvas状态,以便稍后恢复
  42.             canvas.save();
  43.             // 将Canvas原点移动到裁剪框中心,进行旋转操作
  44.             canvas.translate(mRect.centerX(), mRect.centerY());
  45.             canvas.rotate(mRotation);
  46.             // 移回原点以绘制旋转后的图像
  47.             canvas.translate(-mRect.centerX(), -mRect.centerY());
  48.             // 使用变换矩阵绘制图像
  49.             canvas.drawBitmap(mBitmap, mMatrix, null);
  50.             // 恢复Canvas到之前的状态
  51.             canvas.restore();

  52.             // 绘制裁剪框,让用户知道哪里会被裁剪
  53.             drawCropBox(canvas);
  54.         }
  55.     }

  56.     private void drawOverlay(Canvas canvas) {
  57.         Paint paint = new Paint();
  58.         // 设置半透明黑色作为蒙层颜色
  59.         paint.setColor(Color.argb(128, 0, 0, 0));
  60.         // 填充整个视图为半透明黑色
  61.         canvas.drawRect(0, 0, getWidth(), getHeight(), paint);

  62.         // 创建一个路径,添加裁剪框形状
  63.         Path path = new Path();
  64.         path.addRect(mRect, Path.Direction.CW);
  65.         // 使用canvas.clipPath剪切出裁剪框区域,使其透明
  66.         // 注意:Region.Op.DIFFERENCE在API 26以上已被弃用,应考虑使用其他方式实现相同效果
  67.         canvas.clipPath(path, Region.Op.DIFFERENCE);
  68.         canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
  69.     }

  70.     private void drawCropBox(Canvas canvas) {
  71.         Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); // 抗锯齿
  72.         paint.setStyle(Paint.Style.STROKE); // 只绘制边框,不填充内部
  73.         paint.setStrokeWidth(5); // 边框宽度
  74.         paint.setColor(Color.BLUE); // 裁剪框颜色设置为蓝色
  75.         // 绘制裁剪框矩形
  76.         canvas.drawRect(mRect, paint);
  77.     }

  78.     @Override
  79.     public boolean onTouchEvent(MotionEvent event) {
  80.         // 分别将事件传递给缩放和手势检测器
  81.         mScaleDetector.onTouchEvent(event);
  82.         mGestureDetector.onTouchEvent(event);

  83.         switch (event.getAction()) {
  84.             case MotionEvent.ACTION_DOWN:
  85.                 // 记录按下时的坐标,开始拖动
  86.                 mLastTouchPos[0] = event.getX();
  87.                 mLastTouchPos[1] = event.getY();
  88.                 mIsDragging = true;
  89.                 break;
  90.             case MotionEvent.ACTION_MOVE:
  91.                 if (mIsDragging) {
  92.                     // 更新当前位置,并根据位移调整矩阵和平移裁剪框
  93.                     mCurrentPos[0] = event.getX();
  94.                     mCurrentPos[1] = event.getY();
  95.                     updateMatrix();
  96.                     invalidate(); // 请求重新绘制界面
  97.                 }
  98.                 break;
  99.             case MotionEvent.ACTION_UP:
  100.                 // 结束拖动
  101.                 mIsDragging = false;
  102.                 break;
  103.         }
  104.         return true;
  105.     }

  106.     private void updateMatrix() {
  107.         // 更新矩阵以反映图像的新位置
  108.         mMatrix.setTranslate(mCurrentPos[0] - mLastTouchPos[0], mCurrentPos[1] - mLastTouchPos[1]);
  109.         // 同步裁剪框的位置
  110.         mRect.offset(mCurrentPos[0] - mLastTouchPos[0], mCurrentPos[1] - mLastTouchPos[1]);
  111.     }

  112.     private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
  113.         @Override
  114.         public boolean onScale(ScaleGestureDetector detector) {
  115.             // 获取缩放因子并应用到矩阵上,保持缩放中心点不变
  116.             float scaleFactor = detector.getScaleFactor();
  117.             mMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
  118.             invalidate(); // 请求重绘以反映变化
  119.             return true;
  120.         }
  121.     }

  122.     private class GestureListener extends GestureDetector.SimpleOnGestureListener {
  123.         @Override
  124.         public boolean onDoubleTap(MotionEvent e) {
  125.             // 双击时重置所有变换
  126.             resetTransformations();
  127.             return true;
  128.         }
  129.     }

  130.     private void resetTransformations() {
  131.         // 重置矩阵和旋转角度,以及裁剪框位置
  132.         mMatrix.reset();
  133.         mRotation = 0f;
  134.         mRect.set(/* default values */);
  135.         invalidate(); // 请求重绘
  136.     }

  137.     // 设置要裁剪的图片
  138.     public void setImageBitmap(Bitmap bitmap) {
  139.         mBitmap = bitmap;
  140.         // 根据新图片尺寸调整裁剪框大小
  141.         updateCropBoxSize();
  142.         requestLayout(); // 请求布局更新
  143.         invalidate(); // 请求重绘
  144.     }

  145.     private void updateCropBoxSize() {
  146.         // 根据所选图片的尺寸设置合适的裁剪框大小
  147.         int width = mBitmap.getWidth();
  148.         int height = mBitmap.getHeight();
  149.         float aspectRatio = (float) width / height;

  150.         // 设定裁剪框的初始尺寸为图片的中心区域,同时确保其宽高比与原始图片一致
  151.         float rectWidth = Math.min(getWidth(), getHeight() * aspectRatio);
  152.         float rectHeight = Math.min(getHeight(), getWidth() / aspectRatio);
  153.         mRect.set((getWidth() - rectWidth) / 2, (getHeight() - rectHeight) / 2, (getWidth() + rectWidth) / 2, (getHeight() + rectHeight) / 2);
  154.     }

  155.     // 获取裁剪后的图片
  156.     public Bitmap getCroppedBitmap() {
  157.         // 创建一个新的位图来容纳裁剪结果
  158.         Bitmap croppedBitmap = Bitmap.createBitmap(
  159.             (int)mRect.width(),
  160.             (int)mRect.height(),
  161.             Bitmap.Config.ARGB_8888
  162.         );
  163.         Canvas canvas = new Canvas(croppedBitmap);
  164.         // 平移画布以对齐裁剪框左上角
  165.         canvas.translate(-mRect.left, -mRect.top);
  166.         // 绘制变换后的原始图片到新的位图中
  167.         canvas.drawBitmap(mBitmap, mMatrix, null);
  168.         return croppedBitmap;
  169.     }
  170. }
复制代码
自定义的
  1. CustomCropImageView
复制代码
,它允许用户通过触摸屏交互来裁剪图片。该视图支持基本的手势操作,包括拖动、缩放和双击重置。此外,还提供了设置图片和获取裁剪后图片的方法。

步骤 4: 更新Activity逻辑

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

MainActivity.java
  1. import android.Manifest;
  2. import android.content.Intent;
  3. import android.content.pm.PackageManager;
  4. import android.graphics.Bitmap;
  5. import android.net.Uri;
  6. import android.os.Bundle;
  7. import android.provider.MediaStore;
  8. import androidx.annotation.NonNull;
  9. import androidx.annotation.Nullable;
  10. import androidx.appcompat.app.AppCompatActivity;
  11. import androidx.core.app.ActivityCompat;
  12. import androidx.core.content.ContextCompat;

  13. import java.io.File;
  14. import java.io.FileOutputStream;
  15. import java.io.IOException;

  16. public class MainActivity extends AppCompatActivity {

  17.     private static final int PICK_IMAGE_REQUEST = 1;
  18.     private static final int REQUEST_PERMISSIONS = 2;
  19.     private CustomCropImageView customCropImageView;
  20.     private Uri imageUri;

  21.     @Override
  22.     protected void onCreate(Bundle savedInstanceState) {
  23.         super.onCreate(savedInstanceState);
  24.         setContentView(R.layout.activity_main);

  25.         customCropImageView = findViewById(R.id.customCropImageView);

  26.         // 检查并请求存储权限
  27.         if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
  28.                 != PackageManager.PERMISSION_GRANTED) {
  29.             ActivityCompat.requestPermissions(this,
  30.                     new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE},
  31.                     REQUEST_PERMISSIONS);
  32.         }

  33.         findViewById(R.id.buttonPickImage).setOnClickListener(v -> pickImage());
  34.         findViewById(R.id.buttonCrop).setOnClickListener(v -> cropImage());
  35.         findViewById(R.id.buttonCancel).setOnClickListener(v -> cancelCrop());
  36.         findViewById(R.id.buttonSave).setOnClickListener(v -> saveImage());
  37.     }

  38.     private void pickImage() {
  39.         Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
  40.         intent.setType("image/*");
  41.         startActivityForResult(intent, PICK_IMAGE_REQUEST);
  42.     }

  43.     private void cropImage() {
  44.         // 如果使用第三方库如uCrop,可以在这里启动裁剪活动
  45.         // 这里我们假设CustomCropImageView已经包含了所有裁剪功能
  46.         // 因此不需要启动新的活动。
  47.     }

  48.     private void cancelCrop() {
  49.         // 重置CustomCropImageView的状态
  50.         customCropImageView.resetTransformations();
  51.     }

  52.     private void saveImage() {
  53.         Bitmap bitmap = customCropImageView.getCroppedBitmap(); // 获取裁剪后的位图
  54.         try {
  55.             File path = new File(getExternalFilesDir(null), "custom_folder");
  56.             if (!path.exists()) {
  57.                 path.mkdirs();
  58.             }
  59.             File file = new File(path, "cropped_image.jpg");
  60.             FileOutputStream out = new FileOutputStream(file);
  61.             bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
  62.             out.flush();
  63.             out.close();
  64.             // 提示用户图片已保存
  65.         } catch (IOException e) {
  66.             e.printStackTrace();
  67.             // 处理保存失败的情况
  68.         }
  69.     }

  70.     @Override
  71.     protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
  72.         super.onActivityResult(requestCode, resultCode, data);
  73.         if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK && data != null && data.getData() != null) {
  74.             imageUri = data.getData();
  75.             try {
  76.                 // 将选择的图片加载到CustomCropImageView中
  77.                 Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), imageUri);
  78.                 customCropImageView.setImageBitmap(bitmap);
  79.             } catch (IOException e) {
  80.                 e.printStackTrace();
  81.             }
  82.         }
  83.     }

  84.     @Override
  85.     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  86.         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  87.         if (requestCode == REQUEST_PERMISSIONS) {
  88.             if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
  89.                 // 权限授予成功,可以继续进行图片选择等操作
  90.             } else {
  91.                 // 用户拒绝了权限,需要提示用户或者禁用相关功能
  92.             }
  93.         }
  94.     }
  95. }
复制代码
总结

通过上述步骤,我们完成了一个具有裁剪功能的Android demo。该应用允许用户从相册或相机选择图片,在界面上进行裁剪、旋转和缩放,并最终将处理过的图片保存到指定位置。
以上就是Android实现图片裁剪处理的操作步骤的详细内容,更多关于Android图片裁剪处理的资料请关注晓枫资讯其它相关文章!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表