修复共享元素动画首帧为上一个书籍封面的问题

This commit is contained in:
HapeLee committed 2026-05-23 03:57:40 +08:00
1 parent 26167c8528
commit d8977337fc
1 file changed
+63 -12
@@ -7,8 +7,10 @@ import android.text.StaticLayout
import android.text.TextPaint import android.text.TextPaint
import android.text.TextUtils import android.text.TextUtils
import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.EnterExitState
import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.core.animateFloat
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
@@ -25,6 +27,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.key import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@@ -47,6 +50,9 @@ import io.legado.app.ui.theme.LegadoTheme
import org.koin.compose.koinInject import org.koin.compose.koinInject
import io.legado.app.model.BookCover as BookCoverModel import io.legado.app.model.BookCover as BookCoverModel
private const val SharedCoverRadiusCacheMaxSize = 256
private val sharedCoverRadiusCache = mutableStateMapOf<String, Dp>()
@OptIn(ExperimentalSharedTransitionApi::class) @OptIn(ExperimentalSharedTransitionApi::class)
@Composable @Composable
fun CoilBookCover( fun CoilBookCover(
@@ -81,6 +87,14 @@ fun CoilBookCover(
mutableStateOf(sharedCoverKey != null && finalPath != null) mutableStateOf(sharedCoverKey != null && finalPath != null)
} }
val transitionRadius = rememberSharedCoverTransitionRadius(
sharedCoverKey = sharedCoverKey,
radius = radius,
animatedVisibilityScope = animatedVisibilityScope
)
val shape = remember(transitionRadius) { RoundedCornerShape(transitionRadius) }
key(path, sharedCoverKey) {
Box( Box(
modifier = modifier modifier = modifier
.aspectRatio(5f / 7f) .aspectRatio(5f / 7f)
@@ -90,26 +104,26 @@ fun CoilBookCover(
Modifier.sharedElement( Modifier.sharedElement(
sharedContentState = rememberSharedContentState(sharedCoverKey), sharedContentState = rememberSharedContentState(sharedCoverKey),
animatedVisibilityScope = animatedVisibilityScope, animatedVisibilityScope = animatedVisibilityScope,
renderInOverlayDuringTransition = true clipInOverlayDuringTransition = OverlayClip(shape)
) )
} else Modifier } else Modifier
} }
) )
.then( .then(
if (CoverConfig.coverShowShadow) { if (CoverConfig.coverShowShadow) {
Modifier.shadow(4.dp, RoundedCornerShape(radius)) Modifier.shadow(4.dp, shape)
} else Modifier } else Modifier
) )
.background( .background(
if (!hasCustomDefault && !isOnlineCoverLoaded) { if (!hasCustomDefault && !isOnlineCoverLoaded) {
LegadoTheme.colorScheme.surfaceContainerLow LegadoTheme.colorScheme.surfaceContainerLow
} else Color.Transparent, } else Color.Transparent,
RoundedCornerShape(radius) shape
) )
.clip(RoundedCornerShape(radius)) .clip(shape)
) { ) {
Box(modifier = Modifier.fillMaxSize()) {
if (hasCustomDefault && !isOnlineCoverLoaded) { if (hasCustomDefault && !isOnlineCoverLoaded) {
key(randomPath) {
AsyncImage( AsyncImage(
model = buildCoverImageRequest( model = buildCoverImageRequest(
context = context, context = context,
@@ -124,13 +138,10 @@ fun CoilBookCover(
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.clip(RoundedCornerShape(radius))
) )
} }
}
if (finalPath != null) { if (finalPath != null) {
key(finalPath) {
AsyncImage( AsyncImage(
model = buildCoverImageRequest( model = buildCoverImageRequest(
context = context, context = context,
@@ -143,9 +154,7 @@ fun CoilBookCover(
contentDescription = null, contentDescription = null,
imageLoader = koinInject(), imageLoader = koinInject(),
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
modifier = Modifier modifier = Modifier.fillMaxSize(),
.fillMaxSize()
.clip(RoundedCornerShape(4.dp)),
onSuccess = { onSuccess = {
isOnlineCoverLoaded = true isOnlineCoverLoaded = true
onLoadFinish?.invoke() onLoadFinish?.invoke()
@@ -155,12 +164,12 @@ fun CoilBookCover(
onLoadFinish?.invoke() onLoadFinish?.invoke()
} }
) )
}
} else { } else {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
onLoadFinish?.invoke() onLoadFinish?.invoke()
} }
} }
}
if (showLoadingPlaceholder && !isOnlineCoverLoaded) { if (showLoadingPlaceholder && !isOnlineCoverLoaded) {
if (!hasCustomDefault) { if (!hasCustomDefault) {
@@ -181,6 +190,48 @@ fun CoilBookCover(
} }
} }
} }
}
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
private fun rememberSharedCoverTransitionRadius(
sharedCoverKey: String?,
radius: Dp,
animatedVisibilityScope: AnimatedVisibilityScope?
): Dp {
if (sharedCoverKey == null || animatedVisibilityScope == null) {
return radius
}
val transition = animatedVisibilityScope.transition
val startRadius = sharedCoverRadiusCache[sharedCoverKey] ?: radius
val animatedRadiusValue by transition.animateFloat(
label = "book-cover-corner-radius"
) { state ->
if (state == EnterExitState.Visible) radius.value else startRadius.value
}
LaunchedEffect(
sharedCoverKey,
radius,
transition.currentState,
transition.targetState
) {
if (
transition.currentState == EnterExitState.Visible &&
transition.targetState == EnterExitState.Visible
) {
sharedCoverRadiusCache[sharedCoverKey] = radius
if (sharedCoverRadiusCache.size > SharedCoverRadiusCacheMaxSize) {
sharedCoverRadiusCache.keys
.firstOrNull { it != sharedCoverKey }
?.let(sharedCoverRadiusCache::remove)
}
}
}
return animatedRadiusValue.dp
}
@Composable @Composable
private fun CoverTextOverlay( private fun CoverTextOverlay(