我有一个图像组合,使用
以下是结果: 一切都运行良好,除了有些闪烁。因为我们立即隐藏原始图像并显示副本图像,可能存在空白帧,在同时重新组合两个图像时。所以原始图像被隐藏,但副本图像仍未显示,因此存在一个帧,其中两个图像都不可见。
我能否设置图像重新组合的顺序,使得在隐藏原始图像之前,复制图像可以获得其可见状态?
我看到可以在列/行中使用键的方法,详情请参见这里,但我不确定它是否相关。
我想到的另一个想法是使用透明度动画,以便存在一定的延迟。
onGloballyPositioned
监听器检索其边界框。当我按下按钮时,会显示一个新的图像,它具有相同的resId
、初始位置和大小,以便与原始图像的大小匹配。而原始图像被隐藏,复制图像使用absoluteOffset
改变其定位,并使用宽度和高度属性更改其大小。我使用LaunchedEffect
生成从0f到1f的浮点值,然后使用它们来更改复制图像的位置和大小。以下是结果: 一切都运行良好,除了有些闪烁。因为我们立即隐藏原始图像并显示副本图像,可能存在空白帧,在同时重新组合两个图像时。所以原始图像被隐藏,但副本图像仍未显示,因此存在一个帧,其中两个图像都不可见。
我能否设置图像重新组合的顺序,使得在隐藏原始图像之前,复制图像可以获得其可见状态?
我看到可以在列/行中使用键的方法,详情请参见这里,但我不确定它是否相关。
我想到的另一个想法是使用透明度动画,以便存在一定的延迟。
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
我知道可以使用单个图像来实现同样的效果,但我想要一个独立的图片,它不是组合导航的一部分。因此,如果我切换到另一个目标,我希望图片能够平滑地动画到那个目标。
这是源代码:
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
}
}