提问人:Rofie Sagara 提问时间:11/14/2023 更新时间:11/16/2023 访问量:55
Android 使用手势合成 Canvas 缩放图像,使其远离图像
Android compose Canvas scale image with gesture offset its far away from image
问:
我创建了裁剪图像,用画布圈起来,它在裁剪旋转、缩放和移动图像方面效果很好,但是当我尝试将图像向右或向左移动时,问题就来了,而在比例 1,4 上照片的偏移量太大,所以图像不再在圆圈内,但是当我缩放更大的数字时,比如 3,它离图像更远, 我想让用户在运动图像时不会偏移太远,直到完全不在圆圈中,但我仍然希望用户能够到达图像的边缘。
这是 Compose 文件
@Composable
fun ZoomableImage(
modifier: Modifier = Modifier,
imageUrl: Uri,
maxWidthVal: Int = 0,
maxHeightVal: Int = 0,
) {
var scale by remember { mutableStateOf(1f) }
var offset by remember { mutableStateOf(Offset(0f, 0f)) }
var rotation by remember { mutableStateOf(0f) }
var croppedImage: Bitmap? by remember { mutableStateOf(null) }
val minScale = 1f
val maxScale = 3f
var canvasSize by remember { mutableStateOf(Size(0f, 0f)) }
var originalImageBitmap by remember { mutableStateOf<ImageBitmap?>(null) }
val context = LocalContext.current
val imageLoader = ImageLoader(context)
val request = ImageRequest.Builder(context)
.allowHardware(false)
.data(imageUrl)
.target { result ->
originalImageBitmap = result.current.toBitmap().asImageBitmap()
}
.build()
LaunchedEffect(request) {
withContext(Dispatchers.IO) {
imageLoader.execute(request)
}
}
Box(
modifier = modifier
.fillMaxSize()
.pointerInput(Unit) {
detectTransformGestures { _, pan, zoom, rotationChange ->
// Zoom
scale *= zoom
scale = scale.coerceIn(minScale, maxScale)
// Pan (move)
offset = if (scale > minScale) {
val offsetX = (offset.x + pan.x * scale).coerceIn(
-maxWidthVal * (scale - 1),
maxWidthVal * (scale - 1)
)
val offsetY = (offset.y + pan.y * scale).coerceIn(
-maxHeightVal * (scale - 1),
maxHeightVal * (scale - 1)
)
Log.d("CropImage", "ZoomableImage: move $offsetX, $offsetY scale $scale")
Offset(offsetX, offsetY)
} else {
Offset(0f, 0f)
}
// Rotation cap to 360 degrees, cause if we spin more its big than 360
// make canvas fail to build up so we mod by 360
rotation = (rotation + rotationChange) % 360
Log.d("CropImage", "ZoomableImage: rotation $rotation")
croppedImage = null
}
}
.background(MaterialTheme.colorScheme.onSurface),
contentAlignment = Alignment.Center
) {
if (croppedImage == null) {
if (originalImageBitmap == null) return
Canvas(
modifier = Modifier
.fillMaxSize()
.onGloballyPositioned {
canvasSize = Size(it.size.width.toFloat(), it.size.height.toFloat())
}
.clickable {
croppedImage = getCroppedBitmap(
originalImageBitmap!!.asAndroidBitmap(),
scale,
scale,
offset.x,
offset.y,
rotation,
canvasSize
)
},
onDraw = {
val circlePath = Path().apply {
addOval(Rect(center, radius = size.width / 2))
}
val centerX = center.x - ((originalImageBitmap!!.width) / 2) + offset.x // calculate to make photo center
val centerY = center.y - ((originalImageBitmap!!.height) / 2) + offset.y // calculate to make photo center
rotate(rotation, Offset(center.x, center.y)) {
scale(scale, Offset(center.x, center.y)) {
drawImage(originalImageBitmap!!, topLeft = Offset(centerX, centerY))
}
}
// Draw the white border
val borderPaint = Paint().asFrameworkPaint()
borderPaint.color = Color.White.toArgb()
// borderPaint.style = Paint().style.Stroke
borderPaint.strokeWidth = 4f // Adjust the width as needed
drawPath(
circlePath,
color = Color.White,
colorFilter = ColorFilter.tint(Color.White),
style = Stroke(4f)
)
clipPath(circlePath, clipOp = ClipOp.Difference) {
drawRect(SolidColor(Color.Black.copy(alpha = 0.8f)))
}
})
}
}
croppedImage?.let {
Image(
painter = rememberAsyncImagePainter(it),
contentDescription = null,
modifier = Modifier
.fillMaxSize()
.background(color = Color.Blue)
.clickable { croppedImage = null },
)
}
}
此功能用于根据图像的位置将图像裁剪为圆圈
fun getCroppedBitmap(
bitmap: Bitmap,
scaleX: Float,
scaleY: Float,
translationX: Float,
translationY: Float,
rotationZ: Float,
canvasSize: Size,
): Bitmap {
Log.d("CropImage", "ZoomableImage: scale: $scaleX, translationX: $translationX, translationY: $translationY, rotationZ: $rotationZ, canvasSize: $canvasSize")
val center = Offset(canvasSize.width.toFloat() / 2, canvasSize.height.toFloat() / 2)
val circle = Rect(center = Offset(0f, 0f), radius = 500f)
val output = Bitmap.createBitmap(
canvasSize.width.toInt(),
canvasSize.height.toInt(),
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(output.asImageBitmap())
val color = -0xbdbdbe
val paint = Paint()
paint.isAntiAlias = true
paint.color = Color(color = color)
val radius = canvasSize.width / 2
val scaleValue = canvasSize.height / bitmap.height
Log.d("CropImage", "getCroppedBitmap: original radius ${canvasSize.width / 2} with size $canvasSize, new radius: $radius")
Log.d("CropImage", "getCroppedBitmap: scaleValue: $scaleValue")
Log.d("CropImage", "getCroppedBitmap: translationX: ${translationX / scaleValue}, translationY: ${translationY / scaleValue}")
canvas.drawCircle(
center = center, radius = radius,
paint = paint
)
canvas.save()
canvas.rotate(rotationZ, center.x, center.y)
canvas.scale(scaleX, scaleY, center.x, center.y)
val imageBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true).asImageBitmap()
var offsetX = center.x - ((bitmap.width) / 2) // calculate to make photo center
var offsetY = center.y - ((bitmap.height) / 2 ) // calculate to make photo center
offsetX += (translationX)
offsetY += (translationY)
paint.blendMode = BlendMode.SrcIn
Log.d("CropImage", "getCroppedBitmap: offsetX: $offsetX offsetY: $offsetY")
canvas.save()
canvas.drawImage(imageBitmap, Offset(offsetX, offsetY), paint)
canvas.restore()
return output
}
这是如何调用视图
ZoomableImage(
imageUrl = "https://picsum.photos/seed/picsum/2000/1500".toUri(),
maxWidthVal = 2000,
maxHeightVal = 1500
)
这是我的数学错误还是我做错了,如果你建议使用 lib,我们不能这样做,因为我们计划构建一些不同的东西,而这是一个需要处理的基本功能
此致敬意
答: 暂无答案
评论