feat(date-recorder): 详情页图片灯箱,支持缩放/旋转手势
点击详情页大图弹出全屏深色遮罩,集成 panpf/zoomimage 的 SketchZoomAsyncImage,支持双指缩放、平移、捻合旋转(松手吸附 0/90/180/270°)与双击复位,点空白或返回键关闭。 新增 zoomimage-compose-sketch4 1.6.0 依赖(与项目 sketch 同作者, SketchZoomAsyncImage 是 AsyncImage 超集,额外提供缩放/旋转/大图分片)。
This commit is contained in:
parent
d47a38bc19
commit
cec8debff0
@ -105,6 +105,9 @@ dependencies {
|
|||||||
implementation(libs.camera.view)
|
implementation(libs.camera.view)
|
||||||
implementation(libs.androidx.exifinterface)
|
implementation(libs.androidx.exifinterface)
|
||||||
|
|
||||||
|
// 日期记录器:详情页灯箱图片缩放/旋转(与 sketch 同作者 panpf,SketchZoomAsyncImage 是 AsyncImage 超集)
|
||||||
|
implementation(libs.zoomimage.compose.sketch4)
|
||||||
|
|
||||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:${libs.versions.kotlin.get()}")
|
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:${libs.versions.kotlin.get()}")
|
||||||
testImplementation(libs.kotlinx.coroutines.test)
|
testImplementation(libs.kotlinx.coroutines.test)
|
||||||
testImplementation(libs.room.testing)
|
testImplementation(libs.room.testing)
|
||||||
|
|||||||
72
core/src/main/kotlin/plus/rua/project/ui/ImageLightbox.kt
Normal file
72
core/src/main/kotlin/plus/rua/project/ui/ImageLightbox.kt
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package plus.rua.project.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import com.github.panpf.sketch.rememberAsyncImageState
|
||||||
|
import com.github.panpf.zoomimage.SketchZoomAsyncImage
|
||||||
|
import com.github.panpf.zoomimage.rememberSketchZoomState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全屏图片灯箱,支持双指缩放、平移与旋转查看。
|
||||||
|
*
|
||||||
|
* 在全屏深色遮罩中居中显示图片,底层用 zoomimage 的 [SketchZoomAsyncImage]
|
||||||
|
* 提供手势:双指缩放、单指平移、双指捻合自由旋转(松手后吸附到最近的
|
||||||
|
* 0/90/180/270°)、双击复位。点击图片外的空白区域或按返回键触发 [onDismiss]。
|
||||||
|
*
|
||||||
|
* sketch 的 [rememberAsyncImageState] 负责加载,zoomimage 的
|
||||||
|
* [rememberSketchZoomState] 负责缩放/平移/旋转——后者是 sketch `AsyncImage`
|
||||||
|
* 之外新增的必需参数。
|
||||||
|
*
|
||||||
|
* @param photoUri 图片 URI;为 null 时直接返回不渲染
|
||||||
|
* @param contentDescription 无障碍描述,传 null 表示纯装饰
|
||||||
|
* @param onDismiss 关闭回调(点击空白区域或按返回键时触发)
|
||||||
|
* @param modifier 布局修饰符
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun ImageLightbox(
|
||||||
|
photoUri: String?,
|
||||||
|
contentDescription: String?,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
if (photoUri == null) return
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
properties = DialogProperties(
|
||||||
|
decorFitsSystemWindows = false,
|
||||||
|
usePlatformDefaultWidth = false
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(Color.Black)
|
||||||
|
.clickable(
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
indication = null,
|
||||||
|
onClick = onDismiss
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
val asyncImageState = rememberAsyncImageState()
|
||||||
|
val zoomState = rememberSketchZoomState()
|
||||||
|
SketchZoomAsyncImage(
|
||||||
|
uri = photoUri,
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
state = asyncImageState,
|
||||||
|
zoomState = zoomState,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,7 @@
|
|||||||
package plus.rua.project.ui
|
package plus.rua.project.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@ -81,6 +83,7 @@ fun RecordDetailScreen(
|
|||||||
)
|
)
|
||||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
var showDeleteDialog by remember { mutableStateOf(false) }
|
var showDeleteDialog by remember { mutableStateOf(false) }
|
||||||
|
var showLightbox by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// 删除完成后自动返回
|
// 删除完成后自动返回
|
||||||
if (state.deleted) {
|
if (state.deleted) {
|
||||||
@ -142,6 +145,7 @@ fun RecordDetailScreen(
|
|||||||
|
|
||||||
else -> DetailContent(
|
else -> DetailContent(
|
||||||
state = state,
|
state = state,
|
||||||
|
onPhotoClick = { showLightbox = true },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
@ -165,11 +169,20 @@ fun RecordDetailScreen(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showLightbox) {
|
||||||
|
ImageLightbox(
|
||||||
|
photoUri = state.photoUri,
|
||||||
|
contentDescription = state.record?.title,
|
||||||
|
onDismiss = { showLightbox = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DetailContent(
|
private fun DetailContent(
|
||||||
state: DateRecordDetailUiState,
|
state: DateRecordDetailUiState,
|
||||||
|
onPhotoClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val record = state.record ?: return
|
val record = state.record ?: return
|
||||||
@ -183,6 +196,11 @@ private fun DetailContent(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clip(RoundedCornerShape(12.dp))
|
.clip(RoundedCornerShape(12.dp))
|
||||||
|
.clickable(
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
indication = null,
|
||||||
|
onClick = onPhotoClick
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||||
|
|||||||
@ -22,6 +22,7 @@ sketch = "4.4.0"
|
|||||||
spotless = "8.8.0"
|
spotless = "8.8.0"
|
||||||
tyme4kt = "1.5.0"
|
tyme4kt = "1.5.0"
|
||||||
versions = "0.54.0"
|
versions = "0.54.0"
|
||||||
|
zoomimage = "1.6.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
|
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
|
||||||
@ -57,6 +58,7 @@ room-testing = { module = "androidx.room:room-testing", version.ref = "room" }
|
|||||||
sketch-animated-webp = { module = "io.github.panpf.sketch4:sketch-animated-webp", version.ref = "sketch" }
|
sketch-animated-webp = { module = "io.github.panpf.sketch4:sketch-animated-webp", version.ref = "sketch" }
|
||||||
sketch-compose = { module = "io.github.panpf.sketch4:sketch-compose", version.ref = "sketch" }
|
sketch-compose = { module = "io.github.panpf.sketch4:sketch-compose", version.ref = "sketch" }
|
||||||
tyme4kt = { module = "cn.6tail:tyme4kt", version.ref = "tyme4kt" }
|
tyme4kt = { module = "cn.6tail:tyme4kt", version.ref = "tyme4kt" }
|
||||||
|
zoomimage-compose-sketch4 = { module = "io.github.panpf.zoomimage:zoomimage-compose-sketch4", version.ref = "zoomimage" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user