Tengo una imagen componible, luego recupero su cuadro de límite usando el oyente onGloballyPositioned
. Cuando presiono un botón, se muestra una nueva imagen, que tiene el mismo resId y la misma posición y tamaño inicial, por lo que coincide con el tamaño de la imagen original. Y la imagen original se oculta, mientras que la copia de la imagen la cambia de ubicación usando absoluteOffset
y su tamaño usando los atributos de ancho y alto. Estoy usando LaunchedEffect para generar valores flotantes de 0f a 1f, y luego los uso para cambiar la posición y el tamaño de la imagen copiada.
Aquí está el resultado:
Todo funciona bien, excepto el hecho de que hay algún parpadeo, ya que ocultamos el original y mostramos la imagen de la copia inmediatamente, y probablemente haya un marco vacío, cuando ambas imágenes se recomponen al mismo tiempo. Entonces, la imagen original está oculta, pero la imagen copiada aún no se muestra, por lo que hay un marco donde ambas imágenes son invisibles.
¿Hay alguna manera de establecer el orden en que se recomponen las imágenes, de modo que la imagen copiada obtenga su estado visible, antes de que se oculte la imagen original?
Vi que hay una forma de usar la clave dentro de las columnas/filas desde aquí . Pero no estoy tan seguro de que esté relacionado.
La otra idea que tengo es usar la animación de opacidad, por lo que puede haber un retraso, algo así como
time | Original Image (opacity) | Copy Image (opacity) -------|---------------------------|----------------------- 0s | 1 | 0 0.2s | 0.75 | 0.25 0.4s | 0.5 | 0.5 0.6s | 0.25 | 0.75 0.8s | 0.0 | 1
También sé que puedo usar una sola imagen para lograr el mismo efecto, pero quiero tener una imagen separada, que no sea parte de la navegación de redacción. Entonces, si hago la transición a otro destino, quiero que la imagen se transfiera a ese destino con una animación fluida.
Aquí está el código fuente:
import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.compose.animation.core.TargetBasedAnimation import androidx.compose.animation.core.VectorConverter import androidx.compose.animation.core.tween import androidx.compose.foundation.Image import androidx.compose.foundation.layout.* import androidx.compose.material.Button import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Rect import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import com.slaviboy.myapplication.ui.theme.MyApplicationTheme class MainActivity : ComponentActivity() { val viewModel by viewModels<ViewModel>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { val left = with(LocalDensity.current) { 200.dp.toPx() } val top = with(LocalDensity.current) { 300.dp.toPx() } val width = with(LocalDensity.current) { 100.dp.toPx() } viewModel.setSharedImageToCoord(Rect(left, top, left + width, top + width)) Box(modifier = Modifier.fillMaxSize()) { if (!viewModel.isSharedImageVisible.value) { Image(painter = painterResource(id = viewModel.setSharedImageResId.value), contentDescription = null, contentScale = ContentScale.FillWidth, modifier = Modifier .width(130.dp) .height(130.dp) .onGloballyPositioned { coordinates -> coordinates.parentCoordinates ?.localBoundingBoxOf(coordinates, false) ?.let { viewModel.setSharedImageFromCoord(it) } }) } SharedImage(viewModel) } Button(onClick = { viewModel.setIsSharedImageVisible(true) viewModel.triggerAnimation() }) { } } } } @Composable fun SharedImage(viewModel: ViewModel) { var left by remember { mutableStateOf(0f) } var top by remember { mutableStateOf(0f) } var width by remember { mutableStateOf(330f) } val anim = remember { TargetBasedAnimation( animationSpec = tween(1700, 0), typeConverter = Float.VectorConverter, initialValue = 0f, targetValue = 1f ) } var playTime by remember { mutableStateOf(0L) } LaunchedEffect(viewModel.triggerAnimation.value) { val from = viewModel.sharedImageFromCoord.value val to = viewModel.sharedImageToCoord.value val fromLeft = from.left val fromTop = from.top val fromSize = from.width val toLeft = to.left val toTop = to.top val toSize = to.width val startTime = withFrameNanos { it } do { playTime = withFrameNanos { it } - startTime val animationValue = anim.getValueFromNanos(playTime) left = fromLeft + animationValue * (toLeft - fromLeft) top = fromTop + animationValue * (toTop - fromTop) width = fromSize + animationValue * (toSize - fromSize) } while (playTime < anim.durationNanos) } if (viewModel.isSharedImageVisible.value) { Image( painterResource(id = viewModel.setSharedImageResId.value), contentDescription = null, modifier = Modifier .absoluteOffset { IntOffset(left.toInt(), top.toInt()) } .width( with(LocalDensity.current) { width.toDp() } ) .height( with(LocalDensity.current) { width.toDp() } ) ) } } class ViewModel : androidx.lifecycle.ViewModel() { private val _isSharedImageVisible = mutableStateOf(false) val isSharedImageVisible: State<Boolean> = _isSharedImageVisible fun setIsSharedImageVisible(isSharedImageVisible: Boolean) { _isSharedImageVisible.value = isSharedImageVisible } private val _sharedImageFromCoord = mutableStateOf(Rect.Zero) val sharedImageFromCoord: State<Rect> = _sharedImageFromCoord fun setSharedImageFromCoord(sharedImageFromCoord: Rect) { _sharedImageFromCoord.value = sharedImageFromCoord } private val _sharedImageToCoord = mutableStateOf(Rect.Zero) val sharedImageToCoord: State<Rect> = _sharedImageToCoord fun setSharedImageToCoord(sharedImageToCoord: Rect) { _sharedImageToCoord.value = sharedImageToCoord } private val _setSharedImageResId = mutableStateOf(R.drawable.ic_launcher_background) val setSharedImageResId: State<Int> = _setSharedImageResId fun setSharedImageResId(setSharedImageResId: Int) { _setSharedImageResId.value = setSharedImageResId } private val _triggerAnimation = mutableStateOf(false) val triggerAnimation: State<Boolean> = _triggerAnimation fun triggerAnimation() { _triggerAnimation.value = !_triggerAnimation.value } }