(kotlin) Android 13 高版本 图片选择、显示与裁剪功能实现

要在 Android 13 上实现图片选择、显示并添加裁剪功能,我们可以使用第三方裁剪库如 UCrop。以下是完整实现方案:
1. 添加依赖

build.gradle (Module) 中添加:

复制代码
dependencies {
    // 基础依赖
    implementation 'androidx.activity:activity-ktx:1.7.0'
    implementation 'androidx.fragment:fragment-ktx:1.5.7'
    implementation 'com.github.bumptech.glide:glide:4.14.2'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2'
    
    // 图片裁剪库
    implementation 'com.github.yalantis:ucrop:2.2.8'
    
    // 可选,用于处理Android 13+照片选择器
    implementation 'androidx.activity:activity-ktx:1.7.0'
}

ImagePickerWithCropActivity.kt

复制代码
import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import android.widget.Button
import android.widget.ImageView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.bumptech.glide.Glide
import com.bumptech.glide.signature.ObjectKey
import com.yalantis.ucrop.UCrop
import smartconnection.com.smartconnect.R
import java.io.File

class ImagePickerWithCropActivity : AppCompatActivity() {

    private lateinit var imageView: ImageView
    private lateinit var selectButton: Button

    // 临时文件用于存储裁剪后的图片
    private val cropFile by lazy {
        File(externalCacheDir, "cropped_image.jpg")
    }

    // 图片选择启动器
    private val pickImageLauncher = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result ->
        if (result.resultCode == Activity.RESULT_OK) {
            result.data?.data?.let { uri ->
                // 启动裁剪
                startCrop(uri)
            }
        }
    }

    // 裁剪结果启动器
    private val cropImageLauncher = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result ->
        if (result.resultCode == Activity.RESULT_OK) {
            result.data?.let { intent ->
                UCrop.getOutput(intent)?.let { uri ->
                    // 显示裁剪后的图片
                    displayImage(uri)
                }
            }
        } else if (result.resultCode == UCrop.RESULT_ERROR) {
            result.data?.let {
                val cropError = UCrop.getError(it)
                Toast.makeText(this, "裁剪错误: ${cropError?.message}", Toast.LENGTH_SHORT).show()
            }
        }
    }

    // 权限请求启动器
    private val requestPermissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted ->
        if (isGranted) {
            openImagePicker()
        } else {
            Toast.makeText(
                this,
                "需要权限才能访问图片",
                Toast.LENGTH_SHORT
            ).show()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_image_picker_crop)

        imageView = findViewById(R.id.imageView)
        selectButton = findViewById(R.id.selectButton)

        selectButton.setOnClickListener {
            checkPermissionAndPickImage()
        }
    }

    private fun checkPermissionAndPickImage() {
        // Android 13+使用READ_MEDIA_IMAGES,低版本使用READ_EXTERNAL_STORAGE
        val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            Manifest.permission.READ_MEDIA_IMAGES
        } else {
            Manifest.permission.READ_EXTERNAL_STORAGE
        }

        when {
            ContextCompat.checkSelfPermission(
                this,
                permission
            ) == PackageManager.PERMISSION_GRANTED -> {
                openImagePicker()
            }

            shouldShowRequestPermissionRationale(permission) -> {
                Toast.makeText(
                    this,
                    "需要权限才能访问您的图片",
                    Toast.LENGTH_SHORT
                ).show()
                requestPermissionLauncher.launch(permission)
            }

            else -> {
                requestPermissionLauncher.launch(permission)
            }
        }
    }

    private fun openImagePicker() {
        val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
        pickImageLauncher.launch(intent)
    }

    private fun startCrop(sourceUri: Uri) {
        val destinationUri = Uri.fromFile(cropFile)

        UCrop.of(sourceUri, destinationUri)
            .withAspectRatio(1f, 1f) // 1:1比例
            .withMaxResultSize(1000, 1000) // 最大尺寸
            .withOptions(getCropOptions()) // 自定义选项
            .start(this, cropImageLauncher)
    }

    private fun getCropOptions(): UCrop.Options {
        val options = UCrop.Options()
        options.setHideBottomControls(true) // 显示底部控制栏
        options.setFreeStyleCropEnabled(true) // 允许自由裁剪
        options.setStatusBarColor(ContextCompat.getColor(this, R.color.white))
        options.setToolbarColor(ContextCompat.getColor(this,  R.color.white))
        options.setToolbarTitle("图片裁剪")
        return options
    }

    private fun displayImage(uri: Uri) {
        Glide.with(this)
            .load(uri)
            .signature(ObjectKey(System.currentTimeMillis().toString())) // 使用时间戳作为签名
            .centerCrop()
            .into(imageView)
    }
}

activity_image_picker_crop.xml:

复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    android:gravity="center">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:scaleType="centerCrop"
        android:background="#f0f0f0"
        android:contentDescription="Cropped image" />

    <Button
        android:id="@+id/selectButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:text="选择并裁剪图片" />

</LinearLayout>

AndroidManifest.xml配置:

复制代码
  <!--相册-->
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    <!-- 对于 Android 13 以下版本的回退权限 -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
        android:maxSdkVersion="32" />

在 AndroidManifest.xml 中添加 UCrop 所需的配置

复制代码
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="your.package.name">

    <application>
        <!-- 其他你的Activity声明 -->
        
        <!-- UCrop 所需的Activity声明 -->
        <activity
            android:name="com.yalantis.ucrop.UCropActivity"
            android:screenOrientation="portrait"
            android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
            
        <!-- 其他配置 -->
    </application>

</manifest>
相关推荐
vvilkim6 分钟前
Python四大核心数据结构深度解析:列表、元组、字典与集合
开发语言·python
Diligent_lvan22 分钟前
通俗地讲述DDD的设计
java·开发语言·ddd设计
sxlzs_24 分钟前
Java 策略模式(二)-实战
java·开发语言·策略模式
郭涤生34 分钟前
Chapter 6: Concurrency in C++20_《C++20Get the details》_notes
开发语言·c++·笔记·c++20
倒霉蛋小马39 分钟前
【Java集合】ArrayList源码深度分析
java·开发语言
烁34739 分钟前
每日一题(小白)回溯篇4
java·开发语言·算法
White_Can1 小时前
《C++探幽:STL(string类源码的简易实现(上))》
开发语言·c++
无名之逆1 小时前
在Rust生态中探索高性能HTTP服务器:Hyperlane初体验
运维·服务器·开发语言·后端·http·rust·自动化
独好紫罗兰1 小时前
洛谷题单3-P5724 【深基4.习5】求极差 最大跨度值 最大值和最小值的差-python-流程图重构
开发语言·python·算法
朝阳同学1 小时前
C++中高精度运算问题
开发语言·c++