You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android中用滑块旋转缩放图片并保存至存储的实现方案

Android Compose 实现图片旋转、缩放及保存功能

现有代码的问题点

  • 旋转初始值错误:sliderRotation初始设为1f,应该是0f(初始无旋转)
  • 滑块缺少合理范围限制:缩放和旋转滑块未设置最小值/最大值,操作体验差
  • 标题与功能不匹配:旋转滑块的标题用了“size”,应改为“旋转”
  • 图标复用不合理:两个滑块用了同一个重复图标,需区分旋转/缩放图标
  • 保存按钮无实际逻辑:缺少将编辑后的图片保存到存储的核心代码

完整实现方案

1. 调整UI与状态逻辑

修正初始状态、滑块范围,匹配标题和图标:

var sliderRotation by remember { mutableFloatStateOf(0f) } // 初始无旋转
var sliderZoom by remember { mutableFloatStateOf(1f) } // 初始缩放1倍
val imageLoader = ImageLoader.Builder(context).build()

Scaffold(
    modifier = Modifier.fillMaxSize(),
    topBar = { MainToolbar(title = R.string.edit_image) }
) { paddingValue ->
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(paddingValue),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .weight(1f)
        ) {
            AsyncImage(
                modifier = Modifier
                    .fillMaxSize()
                    .scale(sliderZoom)
                    .rotate(sliderRotation),
                contentDescription = "可编辑图片",
                model = ImageRequest.Builder(context)
                    .data(uiState.editImageUri.toString())
                    .build(),
                imageLoader = imageLoader,
            )
        }

        Column(
            modifier = Modifier
                .fillMaxWidth()
                .weight(1f),
            verticalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            // 旋转滑块
            SliderRow(
                sliderValue = sliderRotation,
                onValueChange = { sliderRotation = it },
                valueRange = 0f..360f, // 旋转范围0-360度
                title = stringResource(R.string.rotation)
            ) {
                Icon(
                    painter = painterResource(id = R.drawable.ic_rotate),
                    contentDescription = "旋转图标",
                    tint = Color.Unspecified
                )
            }
            // 缩放滑块
            SliderRow(
                sliderValue = sliderZoom,
                onValueChange = { sliderZoom = it },
                valueRange = 0.5f..3f, // 缩放范围0.5倍到3倍
                title = stringResource(R.string.zoom)
            ) {
                Icon(
                    painter = painterResource(id = R.drawable.ic_zoom),
                    contentDescription = "缩放图标",
                    tint = Color.Unspecified
                )
            }

            // 保存按钮
            Button(
                onClick = { saveEditedImage(context, uiState.editImageUri, sliderRotation, sliderZoom) },
                modifier = Modifier.align(Alignment.CenterHorizontally)
            ) {
                Text(text = stringResource(R.string.save_image))
            }
        }
    }
}

2. 实现图片保存逻辑

添加保存编辑后图片的函数,将Uri转为Bitmap,应用旋转缩放后保存到媒体库:

private fun saveEditedImage(context: Context, imageUri: Uri, rotation: Float, zoom: Float) {
    // 从Uri获取原始Bitmap
    val originalBitmap = context.contentResolver.openInputStream(imageUri)?.use {
        BitmapFactory.decodeStream(it)
    } ?: return

    // 创建变换矩阵,应用旋转和缩放
    val matrix = Matrix().apply {
        postRotate(rotation)
        postScale(zoom, zoom)
    }

    // 生成编辑后的Bitmap
    val editedBitmap = Bitmap.createBitmap(
        originalBitmap,
        0,
        0,
        originalBitmap.width,
        originalBitmap.height,
        matrix,
        true
    )

    // 准备保存到媒体库的参数
    val contentValues = ContentValues().apply {
        put(MediaStore.Images.Media.DISPLAY_NAME, "edited_image_${System.currentTimeMillis()}.jpg")
        put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/EditedImages")
            put(MediaStore.Images.Media.IS_PENDING, 1)
        }
    }

    val resolver = context.contentResolver
    val saveUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) ?: return

    // 写入Bitmap到存储
    resolver.openOutputStream(saveUri)?.use { outputStream ->
        editedBitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
    }

    // 完成保存(Android 10+)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        contentValues.put(MediaStore.Images.Media.IS_PENDING, 0)
        resolver.update(saveUri, contentValues, null, null)
    }

    // 释放Bitmap资源
    originalBitmap.recycle()
    editedBitmap.recycle()

    // 提示保存成功
    Toast.makeText(context, "图片已保存", Toast.LENGTH_SHORT).show()
}

3. 权限处理

  • Android 10及以上:无需申请WRITE_EXTERNAL_STORAGE权限,通过MediaStore即可保存
  • Android 9及以下:需在Manifest中添加权限并动态申请:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />

关键说明

  • 旋转和缩放通过Matrix实现,确保图片编辑的准确性,而非仅UI层面的视觉变换
  • 使用MediaStore保存图片,符合Android存储规范,避免私有目录访问限制
  • 及时回收Bitmap资源,防止内存泄漏

内容的提问来源于stack exchange,提问作者Saeed Noshadi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 21:53:21