LazyColumn Jetpack compose Android 中的展开/折叠动画问题

发布于 2025-01-11 18:31:48 字数 6397 浏览 1 评论 0原文

我试图在 LazyColumn 中显示一个部分,其中包含使用 LazyRow 水平显示的行列表。我想要一个显示显示/隐藏的按钮,以便我可以在本节中显示最小列表而不是完整列表。我想对展开/折叠部分进行动画处理,并且当前在按钮单击上展开可以按预期工作,但折叠时,LazyCloumn 会向上滚动,这似乎会将这部分推出屏幕(如下面的视频所示)。有什么方法可以折叠,以便按钮至少被捕捉到顶部并且剩余部分被删除?这样,用户仍然可以根据需要展开列表,而不是向上滚动来查找按钮。

输入图片这里的描述

我已经尝试了以下方法,但似乎都不起作用:

  • 使用 AnimatedVisibility
  • 使用 animate*AsState 低级 API
  • 还尝试删除内容允许 LazyColumn 根据列表内容重新排序的列表中
val RandomColor
  get() = Color(Random.nextInt(256), Random.nextInt(256), Random.nextInt(256))

typealias ClickHandler = (Boolean) -> Unit

@Composable
fun DemoLayout(demoDataList: List<DemoData>, isExpanded: Boolean, clickHandler: ClickHandler) {
  LazyColumn {
    demoDataList.forEachIndexed { index, it ->
      when (it) {
        is DemoData.Header -> item(key = "cell_$index") { HeaderComposable(header = it) }
        is DemoData.BigCard -> item(key = "hero_$index") { BigCardComposable(bigCard = it) }
        is DemoData.Card -> item(key = "banner_$index") { CardComposable(card = it) }
        is DemoData.ExpandableSection -> {
          items(count = 2, key = { indexInner: Int -> "categories_first_half_$index$indexInner" }) { index ->
            Section(
              sectionInfo = it.sectionInfo[index]
            )
          }
          //Comment below and try another approach
          item(key = "first_approach_$index") {
            FirstApproach(
              expandableSection = DemoData.ExpandableSection(
                it.sectionInfo.subList(
                  3,
                  5
                )
              )
            )
          }

          //Second approach
          /*if (isExpanded)
            items(count = 3, key = { indexInner -> "categories_second_half_$index$indexInner" }) { index ->
              Section(
                sectionInfo = it.sectionInfo[index + 2]
              )
            }
          item(key = "button_$index") {
            ShowHideButton(isExpanded, clickHandler)
          }*/
        }
      }
    }
  }
}

@Composable
fun FirstApproach(expandableSection: DemoData.ExpandableSection) {
  var expanded by remember { mutableStateOf(false) }
  val density = LocalDensity.current
  Column {

    AnimatedVisibility(
      visible = expanded,
      enter = slideInVertically() +
        expandVertically(
          // Expand from the top.
          expandFrom = Alignment.Top,
          animationSpec = tween(durationMillis = 350, easing = FastOutLinearInEasing)
        ) + fadeIn(
        // Fade in with the initial alpha of 0.3f.
        initialAlpha = 0.3f
      ),
      exit = slideOutVertically(
        animationSpec = tween(durationMillis = 350, easing = FastOutLinearInEasing)
      ) + shrinkVertically(
        shrinkTowards = Alignment.Bottom,
        animationSpec = tween(durationMillis = 350, easing = FastOutLinearInEasing)
      ) + fadeOut(
        animationSpec = tween(durationMillis = 350, easing = FastOutLinearInEasing),
        targetAlpha = 0f
      )
    ) {
      Column {
        for (i in 0 until expandableSection.sectionInfo.size) {
          HeaderComposable(header = expandableSection.sectionInfo[i].header)
          InfoCardsComposable(expandableSection.sectionInfo[i].infoCard)
          DetailsCardComposable(expandableSection.sectionInfo[i].detailCard)
        }
      }
    }
    Button(
      modifier = Modifier
        .padding(top = 16.dp, start = 16.dp, end = 16.dp)
        .fillMaxWidth(),
      onClick = {
        expanded = !expanded
      }) {
      Text(text = if (expanded) "Hide" else "Show")
    }
  }
}

@Composable
fun HeaderComposable(header: DemoData.Header) {
  Row(
    modifier = Modifier
      .padding(top = 16.dp)
      .fillMaxWidth()
      .height(64.dp),
    verticalAlignment = Alignment.CenterVertically
  ) {
    Text(text = header.title, modifier = Modifier.padding(horizontal = 16.dp))
  }
}

@Composable
fun CardComposable(card: DemoData.Card) {
  Card(
    modifier = Modifier
      .padding(top = 16.dp)
      .size(164.dp),
    backgroundColor = RandomColor
  ) {
    Text(text = card.cardText, modifier = Modifier.padding(horizontal = 16.dp))
  }
}

@Composable
fun BigCardComposable(bigCard: DemoData.BigCard) {
  Card(
    modifier = Modifier
      .padding(top = 16.dp)
      .size(172.dp),
    backgroundColor = RandomColor
  ) {
    Text(text = bigCard.bigCardText, modifier = Modifier.padding(horizontal = 16.dp))
  }
}

@Composable
fun Section(sectionInfo: SectionInfo) {
  Column(
    modifier = Modifier.animateContentSize()
  ) {
    HeaderComposable(header = sectionInfo.header)
    InfoCardsComposable(sectionInfo.infoCard)
    DetailsCardComposable(sectionInfo.detailCard)
  }
}

@Composable
private fun ShowHideButton(isExpanded: Boolean, clickHandler: ClickHandler) {
  Button(
    modifier = Modifier
      .padding(top = 16.dp, start = 16.dp, end = 16.dp)
      .fillMaxWidth(),
    onClick = {
      clickHandler.invoke(
        !isExpanded
      )
    }) {
    Text(text = if (isExpanded) "Hide" else "Show")
  }
}

@Composable
fun DetailsCardComposable(detailCardsList: List<DetailCard>) {
  LazyRow(
    modifier = Modifier.padding(top = 16.dp)
  ) {
    items(detailCardsList) {
      DetailCardComposable(detailCard = it)
    }
  }
}

@Composable
fun InfoCardsComposable(infoCardsList: List<InfoCard>) {
  LazyRow(
    modifier = Modifier.padding(top = 16.dp)
  ) {
    items(infoCardsList) {
      InfoCardComposable(infoCard = it)
    }
  }
}

@Composable
fun InfoCardComposable(infoCard: InfoCard) {
  Card(
    modifier = Modifier
      .size(136.dp),
    backgroundColor = RandomColor
  ) {
    Text(text = infoCard.infoText, modifier = Modifier.padding(horizontal = 16.dp))
  }
}

@Composable
fun DetailCardComposable(detailCard: DetailCard) {
  Card(
    modifier = Modifier
      .size(156.dp),
    backgroundColor = RandomColor
  ) {
    Text(text = detailCard.detailText, modifier = Modifier.padding(horizontal = 16.dp))
  }
} 

提供了要尝试的完整代码: https://github.com/DirajHS/ComposeAnimation/tree/master

我想知道这是预期的行为还是我做错了什么? 任何关于在折叠期间将按钮扣到顶部的建议将不胜感激。

I am trying to show a section within LazyColumn which has a list of Rows that are shown horizontally using LazyRow. I would like to have a button which displays show/hide so that I can show a minimal list in this section instead of full list. I would like to animate the expand/collapse part and currently expanding on button click is working as expected but when collapsing, the LazyCloumn scrolls up which seems to push this section out of screen (as shown in the video below). Is there any way we can collapse so that the button at least gets snapped to the top and the remaining section is removed? This way, user can still expand the list if required rather than scrolling up to find the button.

enter image description here

I have tried the following but none of them seem to work:

  • Using AnimatedVisibility
  • Using animate*AsState low level API's
  • Also tried to just remove the contents from list allowing LazyColumn to re-order based on the list content
val RandomColor
  get() = Color(Random.nextInt(256), Random.nextInt(256), Random.nextInt(256))

typealias ClickHandler = (Boolean) -> Unit

@Composable
fun DemoLayout(demoDataList: List<DemoData>, isExpanded: Boolean, clickHandler: ClickHandler) {
  LazyColumn {
    demoDataList.forEachIndexed { index, it ->
      when (it) {
        is DemoData.Header -> item(key = "cell_$index") { HeaderComposable(header = it) }
        is DemoData.BigCard -> item(key = "hero_$index") { BigCardComposable(bigCard = it) }
        is DemoData.Card -> item(key = "banner_$index") { CardComposable(card = it) }
        is DemoData.ExpandableSection -> {
          items(count = 2, key = { indexInner: Int -> "categories_first_half_$index$indexInner" }) { index ->
            Section(
              sectionInfo = it.sectionInfo[index]
            )
          }
          //Comment below and try another approach
          item(key = "first_approach_$index") {
            FirstApproach(
              expandableSection = DemoData.ExpandableSection(
                it.sectionInfo.subList(
                  3,
                  5
                )
              )
            )
          }

          //Second approach
          /*if (isExpanded)
            items(count = 3, key = { indexInner -> "categories_second_half_$index$indexInner" }) { index ->
              Section(
                sectionInfo = it.sectionInfo[index + 2]
              )
            }
          item(key = "button_$index") {
            ShowHideButton(isExpanded, clickHandler)
          }*/
        }
      }
    }
  }
}

@Composable
fun FirstApproach(expandableSection: DemoData.ExpandableSection) {
  var expanded by remember { mutableStateOf(false) }
  val density = LocalDensity.current
  Column {

    AnimatedVisibility(
      visible = expanded,
      enter = slideInVertically() +
        expandVertically(
          // Expand from the top.
          expandFrom = Alignment.Top,
          animationSpec = tween(durationMillis = 350, easing = FastOutLinearInEasing)
        ) + fadeIn(
        // Fade in with the initial alpha of 0.3f.
        initialAlpha = 0.3f
      ),
      exit = slideOutVertically(
        animationSpec = tween(durationMillis = 350, easing = FastOutLinearInEasing)
      ) + shrinkVertically(
        shrinkTowards = Alignment.Bottom,
        animationSpec = tween(durationMillis = 350, easing = FastOutLinearInEasing)
      ) + fadeOut(
        animationSpec = tween(durationMillis = 350, easing = FastOutLinearInEasing),
        targetAlpha = 0f
      )
    ) {
      Column {
        for (i in 0 until expandableSection.sectionInfo.size) {
          HeaderComposable(header = expandableSection.sectionInfo[i].header)
          InfoCardsComposable(expandableSection.sectionInfo[i].infoCard)
          DetailsCardComposable(expandableSection.sectionInfo[i].detailCard)
        }
      }
    }
    Button(
      modifier = Modifier
        .padding(top = 16.dp, start = 16.dp, end = 16.dp)
        .fillMaxWidth(),
      onClick = {
        expanded = !expanded
      }) {
      Text(text = if (expanded) "Hide" else "Show")
    }
  }
}

@Composable
fun HeaderComposable(header: DemoData.Header) {
  Row(
    modifier = Modifier
      .padding(top = 16.dp)
      .fillMaxWidth()
      .height(64.dp),
    verticalAlignment = Alignment.CenterVertically
  ) {
    Text(text = header.title, modifier = Modifier.padding(horizontal = 16.dp))
  }
}

@Composable
fun CardComposable(card: DemoData.Card) {
  Card(
    modifier = Modifier
      .padding(top = 16.dp)
      .size(164.dp),
    backgroundColor = RandomColor
  ) {
    Text(text = card.cardText, modifier = Modifier.padding(horizontal = 16.dp))
  }
}

@Composable
fun BigCardComposable(bigCard: DemoData.BigCard) {
  Card(
    modifier = Modifier
      .padding(top = 16.dp)
      .size(172.dp),
    backgroundColor = RandomColor
  ) {
    Text(text = bigCard.bigCardText, modifier = Modifier.padding(horizontal = 16.dp))
  }
}

@Composable
fun Section(sectionInfo: SectionInfo) {
  Column(
    modifier = Modifier.animateContentSize()
  ) {
    HeaderComposable(header = sectionInfo.header)
    InfoCardsComposable(sectionInfo.infoCard)
    DetailsCardComposable(sectionInfo.detailCard)
  }
}

@Composable
private fun ShowHideButton(isExpanded: Boolean, clickHandler: ClickHandler) {
  Button(
    modifier = Modifier
      .padding(top = 16.dp, start = 16.dp, end = 16.dp)
      .fillMaxWidth(),
    onClick = {
      clickHandler.invoke(
        !isExpanded
      )
    }) {
    Text(text = if (isExpanded) "Hide" else "Show")
  }
}

@Composable
fun DetailsCardComposable(detailCardsList: List<DetailCard>) {
  LazyRow(
    modifier = Modifier.padding(top = 16.dp)
  ) {
    items(detailCardsList) {
      DetailCardComposable(detailCard = it)
    }
  }
}

@Composable
fun InfoCardsComposable(infoCardsList: List<InfoCard>) {
  LazyRow(
    modifier = Modifier.padding(top = 16.dp)
  ) {
    items(infoCardsList) {
      InfoCardComposable(infoCard = it)
    }
  }
}

@Composable
fun InfoCardComposable(infoCard: InfoCard) {
  Card(
    modifier = Modifier
      .size(136.dp),
    backgroundColor = RandomColor
  ) {
    Text(text = infoCard.infoText, modifier = Modifier.padding(horizontal = 16.dp))
  }
}

@Composable
fun DetailCardComposable(detailCard: DetailCard) {
  Card(
    modifier = Modifier
      .size(156.dp),
    backgroundColor = RandomColor
  ) {
    Text(text = detailCard.detailText, modifier = Modifier.padding(horizontal = 16.dp))
  }
} 

Complete code to try out is available here: https://github.com/DirajHS/ComposeAnimation/tree/master

I would like to know if this is the expected behavior or am I doing something wrong?
Any suggestions on snapping the button to the top during collapse would be much appreciated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文