fix(photo-editor): 修复旋转照片过程中的错位黑边
- 根因:isSwapped 原先直接基于目标旋转角度 state.rotationDegrees 判定,导致在旋转动画起始阶段(如 0° 向 90° 动画时)提前将布局 measure 尺寸转置,Fit 适配产生了内缩黑边 Padding。 - 修复:基于当前动画渲染角度 displayRotation.value 动态计算最近的 90° 基准角 (baseRotation),使布局尺寸转置与旋转动画平滑同步,彻底消除错位黑边。 - 测试:在 RotationGeometryTest 中补充 baseRotation、offsetDegrees、isSwapped 动态角度过渡断言。
This commit is contained in:
parent
b13e018503
commit
c39cecf343
@ -25,6 +25,26 @@ object RotationGeometry {
|
||||
fun stableAspect(srcAspect: Float, rotationDegrees: Int): Float =
|
||||
if (rotationDegrees % 180 == 0) srcAspect else 1f / srcAspect
|
||||
|
||||
/**
|
||||
* 计算当前动画角度 [angle] 对应的最近 90° 倍数基准角度。
|
||||
*
|
||||
* 动态基准角度避免了在动画刚开始时以终点角度直接切换转置状态导致的错位黑边。
|
||||
*/
|
||||
fun baseRotation(angle: Float): Int =
|
||||
kotlin.math.round(angle / 90f).toInt() * 90
|
||||
|
||||
/**
|
||||
* 当前动画角度 [angle] 相对最近基准角度 [baseRotation] 的偏移角度 [-45°, 45°]。
|
||||
*/
|
||||
fun offsetDegrees(angle: Float): Float =
|
||||
angle - baseRotation(angle)
|
||||
|
||||
/**
|
||||
* 判断在给定的 90° 倍数基准角度下,宽高尺寸是否转置。
|
||||
*/
|
||||
fun isSwapped(baseRotation: Int): Boolean =
|
||||
baseRotation % 180 != 0
|
||||
|
||||
/**
|
||||
* 让「与容器同比例的图片矩形旋转 [offsetDegrees] 后仍完整覆盖容器」所需的最小缩放因子。
|
||||
*
|
||||
|
||||
@ -380,14 +380,15 @@ private fun EditableImage(
|
||||
}
|
||||
) {
|
||||
val angle = displayRotation.value
|
||||
val offset = angle - state.rotationDegrees
|
||||
val baseRotation = RotationGeometry.baseRotation(angle)
|
||||
val isSwapped = RotationGeometry.isSwapped(baseRotation)
|
||||
val offset = RotationGeometry.offsetDegrees(angle)
|
||||
val scale = RotationGeometry.coverScale(offset, containerAspect)
|
||||
Image(
|
||||
bitmap = state.sourceBitmap.asImageBitmap(),
|
||||
contentDescription = "编辑中的照片",
|
||||
modifier = Modifier
|
||||
.layout { measurable, constraints ->
|
||||
val isSwapped = (state.rotationDegrees % 180 != 0)
|
||||
val targetWidth = if (isSwapped) constraints.maxHeight else constraints.maxWidth
|
||||
val targetHeight = if (isSwapped) constraints.maxWidth else constraints.maxHeight
|
||||
val placeable = measurable.measure(
|
||||
|
||||
@ -7,6 +7,7 @@ import kotlin.math.sin
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.assertFalse
|
||||
|
||||
/**
|
||||
* [RotationGeometry] 单元测试。
|
||||
@ -101,4 +102,33 @@ class RotationGeometryTest {
|
||||
)
|
||||
}
|
||||
}
|
||||
@Test
|
||||
fun baseRotation_and_isSwapped_dynamicAngleTransition() {
|
||||
assertEquals(0, RotationGeometry.baseRotation(0f))
|
||||
assertEquals(0, RotationGeometry.baseRotation(20f))
|
||||
assertEquals(0, RotationGeometry.baseRotation(44.9f))
|
||||
|
||||
assertEquals(90, RotationGeometry.baseRotation(45.1f))
|
||||
assertEquals(90, RotationGeometry.baseRotation(80f))
|
||||
assertEquals(90, RotationGeometry.baseRotation(134f))
|
||||
|
||||
assertEquals(180, RotationGeometry.baseRotation(135.1f))
|
||||
assertEquals(270, RotationGeometry.baseRotation(270f))
|
||||
|
||||
assertEquals(-90, RotationGeometry.baseRotation(-45.1f))
|
||||
assertEquals(-180, RotationGeometry.baseRotation(-135.1f))
|
||||
|
||||
assertFalse(RotationGeometry.isSwapped(0))
|
||||
assertTrue(RotationGeometry.isSwapped(90))
|
||||
assertFalse(RotationGeometry.isSwapped(180))
|
||||
assertTrue(RotationGeometry.isSwapped(270))
|
||||
assertTrue(RotationGeometry.isSwapped(-90))
|
||||
assertFalse(RotationGeometry.isSwapped(-180))
|
||||
|
||||
assertEquals(0f, RotationGeometry.offsetDegrees(0f), 1e-5f)
|
||||
assertEquals(20f, RotationGeometry.offsetDegrees(20f), 1e-5f)
|
||||
assertEquals(-44.9f, RotationGeometry.offsetDegrees(45.1f), 1e-3f)
|
||||
assertEquals(-10f, RotationGeometry.offsetDegrees(80f), 1e-5f)
|
||||
assertEquals(0f, RotationGeometry.offsetDegrees(90f), 1e-5f)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user