Можно ли изменить размер компонуемого изображения без запуска рекомпозиции

у меня естьanimateDpAsState(..), всякий раз, когда эта анимация запускается, она изменяетModifier.size(value)изImage(...)тем самым вызывая рекомпозицию.

Есть ли способ пропустить фазу композиции для этого конкретного сценария? Разрешить изображению изменять свой размер?

2 ответа

Я нашел решение! Чтобы пропустить рекомпозицию, но все же повлиять на вещи вокруг макета, вы можете сделать это на этапе макета, в противном случае переместите его на этап рисования!

Примените этот модификатор кпараметр модификатора.

      Modifier.layout { measurable, constraints ->
    val size = animationSize.toPx() // getting the size of the current animation
    val placeable = measurable.measure(Constraints.fixed(size.toInt(), size.toInt())) // setting the actual constraints of the image

    // Set the layout with the same width and height of the image.
    // Inside the layout we will place the image. This layout function is like a "box"
    layout(placeable.width,placeable.height) {
        // And then we will place the image inside the "box"
        placeable.placeRelative(0, 0) 
    }
}

Вы можете сделать это, используяModifier.drawWithContent,Modifier.drawBeheindили используя Canvas, которыйSpacerсModifier.drawBehind. Модификаторы с лямбда-триггеромLayout,Layout->DrawилиDrawпропуск фазCompositionкак в этом ответе .

Фрагмент ниже изменяет размер с анимацией, и если вы хотите, чтобы изменения размера применялись из центра, вы можете добавить перевод либо

      @Composable
private fun ImageSizeAnimationSample() {
    val painter = painterResource(id = R.drawable.landscape1)
    var enabled by remember { mutableStateOf(true) }
    val sizeDp by animateDpAsState(if (enabled) 200.dp else 0.dp)
    val density = LocalDensity.current
    val context = LocalContext.current

    SideEffect {
        println(" Composing...")
        Toast.makeText(context, "Composing...", Toast.LENGTH_SHORT).show()
    }

    Canvas(modifier = Modifier.size(200.dp)) {
        val dimension = density.run { sizeDp.toPx() }
        with(painter) {
            draw(size = Size(dimension, dimension))
        }
    }

    Button(onClick = { enabled = !enabled }) {
        Text("Enabled: $enabled")
    }
}

С переводом

      Canvas(modifier = Modifier.size(200.dp)) {
    val dimension = density.run { sizeDp.toPx() }
    with(painter) {
        translate(left = (size.width - dimension) / 2, top = (size.height - dimension) / 2) {
            draw(size = Size(dimension, dimension))
        }
    }
}

В этих примерах для анимации запускается только одна рекомпозиция, потому что

          val sizeDp by animateDpAsState(if (enabled) 200.dp else 0.dp)

читает включенное значение, но вы можете обрабатывать анимацию с помощью Animatable, которая также не вызовет никакой рекомпозиции.

      @Composable
private fun ImageSizeAnimationWithAnimatableSample() {
    val painter = painterResource(id = R.drawable.landscape1)
    val animatable = remember { Animatable(0f) }
    val coroutineScope = rememberCoroutineScope()

    val context = LocalContext.current

    SideEffect {
        println(" Composing...")
        Toast.makeText(context, "Composing...", Toast.LENGTH_SHORT).show()
    }

    Canvas(modifier = Modifier.size(200.dp)) {

        with(painter) {
            val dimension = size.width * animatable.value
            translate(left = (size.width - dimension) / 2, top = (size.height - dimension) / 2) {
                draw(size = Size(dimension, dimension))
            }
        }
    }

    Button(onClick = {
        coroutineScope.launch {
            val value = animatable.value
            if(value == 1f){
                animatable.animateTo(0f)
            }else {
                animatable.animateTo(1f)
            }
        }
    }) {
        Text("Animate")
    }
}