From c39cecf343d1ab6d0c69cc4b0a885e655ba9dba1 Mon Sep 17 00:00:00 2001 From: xfy Date: Fri, 24 Jul 2026 11:09:33 +0800 Subject: [PATCH] =?UTF-8?q?fix(photo-editor):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E6=97=8B=E8=BD=AC=E7=85=A7=E7=89=87=E8=BF=87=E7=A8=8B=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E9=94=99=E4=BD=8D=E9=BB=91=E8=BE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 根因:isSwapped 原先直接基于目标旋转角度 state.rotationDegrees 判定,导致在旋转动画起始阶段(如 0° 向 90° 动画时)提前将布局 measure 尺寸转置,Fit 适配产生了内缩黑边 Padding。 - 修复:基于当前动画渲染角度 displayRotation.value 动态计算最近的 90° 基准角 (baseRotation),使布局尺寸转置与旋转动画平滑同步,彻底消除错位黑边。 - 测试:在 RotationGeometryTest 中补充 baseRotation、offsetDegrees、isSwapped 动态角度过渡断言。 --- .../plus/rua/project/RotationGeometry.kt | 20 +++++++++++++ .../plus/rua/project/ui/PhotoEditorScreen.kt | 5 ++-- .../plus/rua/project/RotationGeometryTest.kt | 30 +++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/core/src/main/kotlin/plus/rua/project/RotationGeometry.kt b/core/src/main/kotlin/plus/rua/project/RotationGeometry.kt index 69dfb26..7184976 100644 --- a/core/src/main/kotlin/plus/rua/project/RotationGeometry.kt +++ b/core/src/main/kotlin/plus/rua/project/RotationGeometry.kt @@ -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] 后仍完整覆盖容器」所需的最小缩放因子。 * diff --git a/core/src/main/kotlin/plus/rua/project/ui/PhotoEditorScreen.kt b/core/src/main/kotlin/plus/rua/project/ui/PhotoEditorScreen.kt index 7a7c601..3e7eee3 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/PhotoEditorScreen.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/PhotoEditorScreen.kt @@ -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( diff --git a/core/src/test/kotlin/plus/rua/project/RotationGeometryTest.kt b/core/src/test/kotlin/plus/rua/project/RotationGeometryTest.kt index 5c06b54..a871763 100644 --- a/core/src/test/kotlin/plus/rua/project/RotationGeometryTest.kt +++ b/core/src/test/kotlin/plus/rua/project/RotationGeometryTest.kt @@ -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) + } }