Android實現(xiàn)圖片裁剪處理的操作步驟
前言
本文將介紹如何構(gòu)建一個支持圖片選擇、裁剪(包括手動縮放和旋轉(zhuǎn))、以及保存到自定義路徑的Android應(yīng)用demo。
步驟 1: 設(shè)置權(quán)限
首先,在AndroidManifest.xml
中添加必要的權(quán)限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
對于 Android 6.0 (API level 23) 及以上版本,需要在運行時請求權(quán)限。
步驟 2: 創(chuàng)建布局文件
創(chuàng)建一個簡單的布局文件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: 實現(xiàn)自定義View CustomCropImageView
接下來,我們將詳細實現(xiàn)CustomCropImageView
,這個自定義視圖負責(zé)所有與裁剪相關(guān)的交互邏輯。
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(); // 用于變換(縮放、旋轉(zhuǎn))圖像的矩陣 private RectF mRect = new RectF(); // 定義裁剪框的位置和大小 private float[] mLastTouchPos = new float[2]; // 上次觸摸位置,用于計算移動距離 private float[] mCurrentPos = new float[2]; // 當前觸摸位置,用于更新圖像位置 private float mRotation = 0f; // 圖像的旋轉(zhuǎn)角度 private boolean mIsDragging = false; // 標記是否正在拖動圖像 private ScaleGestureDetector mScaleDetector; // 檢測多點觸控縮放手勢 private GestureDetector mGestureDetector; // 檢測單點觸控手勢(如點擊) // 構(gòu)造函數(shù),初始化自定義視圖 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) { // 繪制背景蒙層,使非裁剪區(qū)域變暗 drawOverlay(canvas); // 保存當前Canvas狀態(tài),以便稍后恢復(fù) canvas.save(); // 將Canvas原點移動到裁剪框中心,進行旋轉(zhuǎn)操作 canvas.translate(mRect.centerX(), mRect.centerY()); canvas.rotate(mRotation); // 移回原點以繪制旋轉(zhuǎn)后的圖像 canvas.translate(-mRect.centerX(), -mRect.centerY()); // 使用變換矩陣繪制圖像 canvas.drawBitmap(mBitmap, mMatrix, null); // 恢復(fù)Canvas到之前的狀態(tài) canvas.restore(); // 繪制裁剪框,讓用戶知道哪里會被裁剪 drawCropBox(canvas); } } private void drawOverlay(Canvas canvas) { Paint paint = new Paint(); // 設(shè)置半透明黑色作為蒙層顏色 paint.setColor(Color.argb(128, 0, 0, 0)); // 填充整個視圖為半透明黑色 canvas.drawRect(0, 0, getWidth(), getHeight(), paint); // 創(chuàng)建一個路徑,添加裁剪框形狀 Path path = new Path(); path.addRect(mRect, Path.Direction.CW); // 使用canvas.clipPath剪切出裁剪框區(qū)域,使其透明 // 注意:Region.Op.DIFFERENCE在API 26以上已被棄用,應(yīng)考慮使用其他方式實現(xiàn)相同效果 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); // 只繪制邊框,不填充內(nèi)部 paint.setStrokeWidth(5); // 邊框?qū)挾? paint.setColor(Color.BLUE); // 裁剪框顏色設(shè)置為藍色 // 繪制裁剪框矩形 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) { // 更新當前位置,并根據(jù)位移調(diào)整矩陣和平移裁剪框 mCurrentPos[0] = event.getX(); mCurrentPos[1] = event.getY(); updateMatrix(); invalidate(); // 請求重新繪制界面 } break; case MotionEvent.ACTION_UP: // 結(jié)束拖動 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) { // 獲取縮放因子并應(yīng)用到矩陣上,保持縮放中心點不變 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() { // 重置矩陣和旋轉(zhuǎn)角度,以及裁剪框位置 mMatrix.reset(); mRotation = 0f; mRect.set(/* default values */); invalidate(); // 請求重繪 } // 設(shè)置要裁剪的圖片 public void setImageBitmap(Bitmap bitmap) { mBitmap = bitmap; // 根據(jù)新圖片尺寸調(diào)整裁剪框大小 updateCropBoxSize(); requestLayout(); // 請求布局更新 invalidate(); // 請求重繪 } private void updateCropBoxSize() { // 根據(jù)所選圖片的尺寸設(shè)置合適的裁剪框大小 int width = mBitmap.getWidth(); int height = mBitmap.getHeight(); float aspectRatio = (float) width / height; // 設(shè)定裁剪框的初始尺寸為圖片的中心區(qū)域,同時確保其寬高比與原始圖片一致 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() { // 創(chuàng)建一個新的位圖來容納裁剪結(jié)果 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
,它允許用戶通過觸摸屏交互來裁剪圖片。該視圖支持基本的手勢操作,包括拖動、縮放和雙擊重置。此外,還提供了設(shè)置圖片和獲取裁剪后圖片的方法。
步驟 4: 更新Activity邏輯
現(xiàn)在我們將更新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); // 檢查并請求存儲權(quán)限 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,可以在這里啟動裁剪活動 // 這里我們假設(shè)CustomCropImageView已經(jīng)包含了所有裁剪功能 // 因此不需要啟動新的活動。 } private void cancelCrop() { // 重置CustomCropImageView的狀態(tài) 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) { // 權(quán)限授予成功,可以繼續(xù)進行圖片選擇等操作 } else { // 用戶拒絕了權(quán)限,需要提示用戶或者禁用相關(guān)功能 } } } }
總結(jié)
通過上述步驟,我們完成了一個具有裁剪功能的Android demo。該應(yīng)用允許用戶從相冊或相機選擇圖片,在界面上進行裁剪、旋轉(zhuǎn)和縮放,并最終將處理過的圖片保存到指定位置。
以上就是Android實現(xiàn)圖片裁剪處理的操作步驟的詳細內(nèi)容,更多關(guān)于Android圖片裁剪處理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
android中DatePicker和TimePicker的使用方法詳解
這篇文章主要介紹了android中DatePicker和TimePicker的使用方法,是Android中常用的功能,需要的朋友可以參考下2014-07-07Android中l(wèi)istview嵌套scrollveiw沖突的解決方法
這篇文章主要為大家詳細介紹了Android中l(wèi)istview嵌套scrollveiw沖突的解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-01-01基于Android AppWidgetProvider的使用介紹
本篇文章小編為大家介紹,基于Android AppWidgetProvider的使用。需要的朋友參考下2013-04-04Android RecyclerView item選中放大被遮擋問題詳解
這篇文章主要介紹了Android RecyclerView item選中放大被遮擋問題詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04Android如何在root設(shè)備上開啟ViewServer詳解
這篇文章主要給大家介紹了關(guān)于Android中如何在root設(shè)備上開啟ViewServer的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對各位Android開發(fā)者具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧。2017-12-12