Jetpack 撰写动画 - 如何为启动画面制作动画,然后将其转换为应用栏?

问题描述 投票:0回答:0

我正在尝试显示启动画面,其中元素将进入动画以及首次加载数据的时间,然后将其最小化到顶部应用程序栏并显示数据。

这里我面临两个问题:

  1. 我没能在一个动画中做到这一点,所以我有 2 个组件:
    SplashScreen
    SplashAppBar
    - 后者的初始状态是第一个的结束状态。
  2. 如果数据加载早于
    SplashScreen
    动画完成,那么它会闪烁,因为它没有达到结束状态。

这就是我取得的成就——它有点管用,但我认为代码不是很好: https://www.youtube.com/embed/HOvhsFU2tMI

我这样创建了

SplashScreen

@Composable
fun SplashScreen(
    modifier: Modifier = Modifier,
) {
    ConstraintLayout(
        modifier = modifier
            .fillMaxSize()
            .background(
                Brush.verticalGradient(
                    listOf(
                        Color.Black,
                        BlueGray500
                    )
                )
            ),
    ) {
        var visible by remember { mutableStateOf(false) }
        val density = LocalDensity.current
        LaunchedEffect(key1 = true) {
            visible = true
        }
        val (image, text) = createRefs()

        AnimatedVisibility(
            visible = visible,
            enter = slideInVertically(
                animationSpec = tween(
                    durationMillis = 2000,
                    easing = EaseOutBounce
                )
            ) {
                with(density) { -500.dp.roundToPx() }
            } + fadeIn(initialAlpha = 0.3f),
            modifier = Modifier
                .constrainAs(image) {
                    top.linkTo(parent.top)
                    bottom.linkTo(parent.bottom)
                    start.linkTo(parent.start)
                    end.linkTo(parent.end)
                }
        ) {
            Image(
                painter = painterResource(R.drawable.foodie_logo),
                contentDescription = "Foodie logo",
                modifier = Modifier.size(200.dp)
            )
        }
        AnimatedVisibility(
            visible = visible,
            enter = slideInVertically(
                animationSpec = tween(
                    durationMillis = 1000,
                    easing = EaseOutCirc
                )
            ) {
                with(density) { 200.dp.roundToPx() }
            } + fadeIn(initialAlpha = 0.3f),
            modifier = Modifier
                .constrainAs(text) {
                    top.linkTo(image.bottom)
                    start.linkTo(image.start)
                    end.linkTo(image.end)
                }
        ) {
            Text(
                text = "Foodie",
                style = MaterialTheme.typography.h1,
                color = Color.White,
            )
        }
    }
}

SplashAppBar
这样:

@OptIn(ExperimentalMotionApi::class)
@Composable
fun SplashAppBar() {

    var collapsed by remember { mutableStateOf(false) }
    LaunchedEffect(key1 = true) {
        collapsed = 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)
    )

    MotionLayout(
        motionScene = MotionScene(content = motionScene),
        progress = progress,
        modifier = Modifier
            .fillMaxWidth()
            .height(motionHeight)
    ) {

        Box(
            modifier = Modifier
                .layoutId("background")
                .fillMaxSize()
                .background(
                    Brush.verticalGradient(
                        listOf(
                            Color.Black,
                            BlueGray500
                        )
                    )
                ),
        )

        Text(
            text = "Foodie",
            style = if (progress > 0.5f) MaterialTheme.typography.h6 else MaterialTheme.typography.h1,
            color = Color.White,
            modifier = Modifier.layoutId("title"),
        )
        Image(
            painter = painterResource(R.drawable.foodie_logo),
            contentDescription = "Foodie logo",
            modifier = Modifier.layoutId("logo"),
        )
    }
}

splash_app_bar_motion_scene

{
  ConstraintSets: {
    start: {
      logo:{
        width: 200,
        height: 200,
        start: ['parent', 'start'],
        end: ['parent', 'end'],
        top: ['background', 'top'],
        bottom: ['background', 'bottom']
      },
      background: {
        width: 'spread',
        height: 'spread',
        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: ['background', 'start', 16],
        top: ['background', 'top', 12],
        bottom: ['background', 'bottom', 12],
      },
      background: {
        width: 'spread',
        height: 56,
        start: ['parent', 'start'],
        end: ['parent', 'end'],
        top: ['parent', 'top'],
      },
      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],
            scaleX: [1, 0.5, 0, 0.5, 1],
            scaleY: [1, 0.5, 0, 0.5, 1]
          }
        ]
      }
    }
  }
}

我呈现它们的方式是:

@Composable
fun MainView(
    uiState: UiState<Data>,
) {
    Column {
        if (uiState.initialized) SplashAppBar()
        else SplashScreen()

        when {
            !uiState.initialized -> Unit
            uiState.errorMessage.isNotEmpty() -> ErrorRetryView()
            uiState.successData != null -> SuccessView(uiState.successData)
            uiState.loading -> LoadingView()
            else -> RetryView()
        }
    }
}
android-jetpack-compose android-animation android-motionlayout jetpack-compose-animation jetpack-compose
© www.soinside.com 2019 - 2024. All rights reserved.