首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Android Jetpack组合闪烁图像克隆

Android Jetpack组合闪烁图像克隆
EN

Stack Overflow用户
提问于 2022-03-12 01:44:44
回答 1查看 781关注 0票数 4

我有一个图像可组合,然后我检索它的边界框使用onGloballyPositioned侦听器。当我按下一个按钮时,会显示一个新的图像,它具有相同的resId以及初始位置和大小,因此它与原始图像的大小相匹配。原始图像被隐藏,而拷贝图像使用absoluteOffset改变其位置,使用宽度和高度属性改变其大小。我使用LaunchedEffect生成从0f到1f的浮点值,然后使用它们来更改复制图像的位置和大小。

结果如下:

一切正常工作,除了有一些闪烁的事实,因为我们隐藏原始和立即显示复制图像,可能有一个空的框架,当两个图像被重新组合在同一时间。因此,原来的图像是隐藏的,但复制的图像仍然没有显示,所以有一个框架,两个图像都是不可见的。

是否有一种方法可以在隐藏原始图像之前设置图像重新组合的顺序,以便复制的图像获得其可见状态?

我看到了在来自here的列/行中使用键的方法。但我不太确定这是否相关。

我的另一个想法是使用不透明动画,这样就会有延迟,比如

代码语言:javascript
运行
复制
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 

我也知道我可以用单一的图像来达到同样的效果,但是我想要有单独的图像,这不是组合导航的一部分。因此,如果我转换到另一个目的地,我希望图像被转移到该目的地与平滑的动画。

以下是源代码:

代码语言:javascript
运行
复制
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
    }
}
EN

回答 1

Stack Overflow用户

发布于 2022-03-12 16:04:13

我设法解决了这个问题,将200 to的延迟应用于过渡动画,同时也将同样的延迟应用于导航转换动画!

在这200 on中,我启动了另一个动画,该动画将共享(复制)图像的不透明度从0,1更改为0。因此,基本上,我在这200 on中显示共享图像,并将其绘制在项目(原始)图像的顶部。然后,在最后一个框架中,我隐藏项目(原始)图像,并且只隐藏它显示的转换图像。

然后,在这200 its的延迟之后,我开始将共享(复制)图像转换到它的新位置。这里有简单的图表来演示动画,在200 is的延迟和700 is的持续时间。

代码语言:javascript
运行
复制
@Composable
fun SharedImage(viewModel: ViewModel) {

    // opacity animation for the shared image
    // if triggered from Home -> change the opacity of the shared image [0,1]
    // if triggered from Detail -> change the opacity of the shared image [1,0]
    LaunchedEffect(viewModel.changeSharedImagePositionFrom.value) {

        val duration: Int
        val delay: Int
        val opacityFrom: Float
        val opacityTo: Float

        if (viewModel.changeSharedImagePositionFrom.value is Screen.Home) {
            duration = 200
            delay = 0
            opacityFrom = 0f
            opacityTo = 1f
        } else {
            duration = 200
            delay = 700 + 200
            opacityFrom = 1f
            opacityTo = 0f
        }

        val animation = TargetBasedAnimation(
            animationSpec = tween(duration, delay),
            typeConverter = Float.VectorConverter,
            initialValue = opacityFrom,
            targetValue = opacityTo
        )

        var playTime = 0L
        val startTime = withFrameNanos { it }
        do {
            playTime = withFrameNanos { it } - startTime
            val animationValue = animation.getValueFromNanos(playTime)
            viewModel.setSharedImageOpacity(animationValue)

        } while (playTime <= animation.durationNanos)

        // on last frame set item opacity to 0
        if (viewModel.changeSharedImagePositionFrom.value is Screen.Home) {
            viewModel.setItemImageOpacity(0f)
        }
    }

    var left by remember { mutableStateOf(0f) }
    var top by remember { mutableStateOf(0f) }
    var width by remember { mutableStateOf(0f) }

    // transition animation for the shared image
    // it changes the position and size of the shared image
    LaunchedEffect(viewModel.changeSharedImagePositionFrom.value) {

        val animation = TargetBasedAnimation(
            animationSpec = tween(700, 200),
            typeConverter = Float.VectorConverter,
            initialValue = 0f,
            targetValue = 1f
        )

        val from = if (viewModel.changeSharedImagePositionFrom.value is Screen.Home) {
            viewModel.sharedImageFromCoord.value
        } else viewModel.sharedImageToCoord.value

        val to = if (viewModel.changeSharedImagePositionFrom.value is Screen.Home) {
            viewModel.sharedImageToCoord.value
        } else viewModel.sharedImageFromCoord.value

        // offset and size for changing the shared image position and size
        val fromLeft = from.left
        val fromTop = from.top
        val fromSize = from.width
        val toLeft = to.left
        val toTop = to.top
        val toSize = to.width

        var playTime = 0L
        val startTime = withFrameNanos { it }
        do {
            playTime = withFrameNanos { it } - startTime
            val animationValue = animation.getValueFromNanos(playTime)
            left = fromLeft + animationValue * (toLeft - fromLeft)
            top = fromTop + animationValue * (toTop - fromTop)
            width = fromSize + animationValue * (toSize - fromSize)

        } while (playTime <= animation.durationNanos)

        // on last frame set item opacity to 1
        if (viewModel.changeSharedImagePositionFrom.value is Screen.Detail) {
            viewModel.setItemImageOpacity(1f)
            viewModel.setEnableItemsScroll(true)
        }
    }

    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() }),
        alpha = viewModel.sharedImageOpacity.value
    )

}

这是结果

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71446368

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档