Создание анимации Jetpack. Могу ли я использовать AnimatedVisibility внутри MotionLayout?

Я пытаюсь отобразить экран-заставку, где элементы будут вводить анимацию, и когда данные будут загружены в первый раз, затем сверните их в верхнюю панель приложения и отобразите данные.

Редактировать:

Мне удалось поместить обе анимации на один экран, но я не уверен, можно ли это использовать.AnimatedVisibilityвнутриMotionLayout.

МойSplashAppBarвыглядит так:

      fun SplashAppBar(
    collapsed: Boolean,
    hideEnterAnimation: Boolean = true,
) {
    val context = LocalContext.current
    val motionScene = remember {
        context.resources.openRawResource(R.raw.splash_app_bar_motion_scene).readBytes()
            .decodeToString()
    }

    val progress by animateFloatAsState(
        targetValue = if (collapsed) 1f else 0f,
        tween(2000)
    )
    val motionHeight by animateDpAsState(
        targetValue = if (collapsed) 56.dp else LocalConfiguration.current.screenHeightDp.dp,
        tween(2000)
    )
    val fontSize by animateIntAsState(
        targetValue = if (collapsed) 20 else 96,
        tween(2000)
    )

    var visible by remember { mutableStateOf(hideEnterAnimation) }
    val density = LocalDensity.current
    LaunchedEffect(key1 = true) {
        visible = true
    }

    MotionLayout(
        motionScene = MotionScene(content = motionScene),
        progress = progress,
        modifier = Modifier
            .fillMaxWidth()
            .height(motionHeight)
            .background(
                Brush.verticalGradient(
                    listOf(
                        Color.Black,
                        BlueGray500
                    )
                ),
            )
    ) {
        AnimatedVisibility(
            visible = visible,
            enter = slideInVertically(
                animationSpec = tween(
                    durationMillis = 1000,
                    easing = EaseOutCirc
                )
            ) {
                with(density) { 200.dp.roundToPx() }
            } + fadeIn(initialAlpha = 0.3f),
            modifier = Modifier.layoutId("title"),
        ) {
            Text(
                text = "Foodie",
                style = if (progress > 0.5f) MaterialTheme.typography.h6 else MaterialTheme.typography.h1,
                fontSize = fontSize.sp,
                color = Color.White,
            )
        }
        AnimatedVisibility(
            visible = visible,
            enter = slideInVertically(
                animationSpec = tween(
                    durationMillis = 2000,
                    easing = EaseOutBounce
                )
            ) {
                with(density) { -500.dp.roundToPx() }
            } + fadeIn(initialAlpha = 0.3f),
            modifier = Modifier.layoutId("logo"),
        ) {
            Image(
                painter = painterResource(R.drawable.foodie_logo),
                contentDescription = "Foodie logo",
            )
        }
    }
}

Сsplash_app_bar_motion_scene:

      {
  ConstraintSets: {
    start: {
      logo:{
        width: 200,
        height: 200,
        start: ['parent', 'start'],
        end: ['parent', 'end'],
        top: ['parent', 'top'],
        bottom: ['parent', 'bottom']
      },
      title: {
        start: ['logo', 'start'],
        end: ['logo', 'end'],
        top: ['logo', 'bottom']
      }
    },
    end: {
      logo:{
        width: 24,
        height: 24,
        start: ['parent', 'start', 16],
        top: ['parent', 'top', 12],
        bottom: ['parent', 'bottom', 12],
      },
      title: {
        start: ['logo', 'end', 16],
        top: ['logo', 'top'],
        bottom: ['logo', 'bottom']
      }
    }
  },
  Transitions: {
    default: {
      from: 'start',
      to: 'end',
      pathMotionArc: 'startVertical',
      KeyFrames: {
        KeyAttributes: [
          {
            target: ['logo'],
            frames: [0, 100],
            rotationZ: [0,  360]
          },
          {
            target: ['title'],
            frames: [0, 20, 50, 80, 100],
            translationY: [0,-100, -45, -10, 0],
            translationX: [0, 0, -80, -85, 0],
          }
        ]
      }
    }
  }
}

Я представляю это так:

      @Composable
fun MainView(
    uiState: UiState<Data>,
) {
    Column {
        SplashAppBar(
            collapsed = uiState.initialized,
            hideEnterAnimation = false,
        )

        when {
            !uiState.initialized -> Unit
            uiState.errorMessage.isNotEmpty() -> ErrorRetryView()
            uiState.successData != null -> SuccessView(uiState.successData)
            uiState.loading -> LoadingView()
            else -> RetryView()
        }
    }
}

1 ответ

Если вы уже используете ConstraintLayout, рассмотрите возможность MotionLayout. Это обеспечивает огромные возможности настройки. Вот пример эффективного кодирования мухи. Скоро это будет в разделе «Примеры Compose MotionLayout »).

      @OptIn(ExperimentalMotionApi::class)
@Preview(group = "motion101")
@Composable
fun M3MultiState() {
    val titleId = "title"


    var scene = MotionScene() {
        val titleRef = createRefFor(titleId)
        val a = constraintSet {
            constrain(titleRef) {
                centerHorizontallyTo(parent,0f)
                centerVerticallyTo(parent,0f)
            }
        }

        val b = constraintSet {
            constrain(titleRef) {
                centerHorizontallyTo(parent,1f)
                 centerVerticallyTo(parent,0f)
            }
        }
        val c = constraintSet {
            constrain(titleRef) {
                centerHorizontallyTo(parent,1f)
                centerVerticallyTo(parent,1f)
            }
        }
        transition(  a,b,"loading") {
        }
        transition(  b,c,"normal") {
        }
    }
    val painter = painterResource(id = R.drawable.pepper)

    var transitionName by remember {
        mutableStateOf("loading")
    }
    var animateToEnd by remember { mutableStateOf(true) }
    val progress = remember { Animatable(0f) }
    LaunchedEffect(animateToEnd) {
        val result = progress.animateTo(
            if (animateToEnd) 1f else 0f,
            animationSpec = tween(5000)
        )
        transitionName = "normal"
        progress.snapTo(0f)
        progress.animateTo(
            if (animateToEnd) 1f else 0f,
            animationSpec = tween(5000)
        )
    }
    MotionLayout(
        modifier = Modifier
            .background(Color(0xFF221010))
            .fillMaxSize()
            .padding(1.dp),
        motionScene = scene,
        transitionName = transitionName,
        progress = progress.value
    ) {

        Text(
            modifier = Modifier.layoutId(titleId),
            text = transitionName,
            fontSize = 30.sp,
            color = Color.White
        )

    }
}

Вот пример с тремя состояниями с использованием MotionLayout. Ключевым моментом здесь является то, что LaunchedEffect выполняет оба перехода. Вам нужно будет заблокировать второй переход до загрузки данных.