You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Jetpack Compose创建可动态底部加卡的牌组(避免全量重绘)

解决方案:Jetpack Compose 高效实现卡片牌组功能

核心思路

要实现「底部加卡不重绘整个牌组、保持顶部卡片UI状态」的需求,关键是分离可变状态,只让需要更新的UI部分响应状态变化:

  • 用 Compose 状态变量仅管理「当前顶部可见卡片」和「牌组数量(仅用于视觉堆叠效果)」
  • 牌组的底层卡片数据用普通集合存储,修改时不会触发Compose重组
  • 顶部卡片UI仅依赖顶部卡片的状态,底部加卡操作不影响此状态,因此不会触发顶部卡片重绘

代码实现(基础版)

1. 定义卡片数据类

data class Card(val id: Int, val content: String)

2. 牌组核心Composable

@Composable
fun CardDeck() {
    // 仅用State管理顶部可见卡片,只有它变化时才会触发顶部UI重绘
    val topCard = remember { mutableStateOf(Card(1, "Card 1")) }
    // 普通MutableList存储牌组其他卡片,修改它不会触发Compose重组
    val deck = remember { mutableListOf<Card>() }

    // 初始化牌组(默认添加5张底卡)
    LaunchedEffect(Unit) {
        repeat(5) {
            deck.add(Card(it + 2, "Card ${it + 2}"))
        }
    }

    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        // 顶部可交互卡片,仅依赖topCard.value
        Card(
            modifier = Modifier
                .size(200.dp, 300.dp)
                .clickable {
                    // 抽卡逻辑:取出新顶部卡片,同时在底部新增一张
                    if (deck.isNotEmpty()) {
                        topCard.value = deck.removeFirst()
                        deck.add(Card(deck.size + topCard.value.id + 1, "Card ${deck.size + topCard.value.id + 1}"))
                    }
                },
            elevation = 8.dp
        ) {
            Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                Text(text = topCard.value.content, fontSize = 24.sp)
            }
        }

        Spacer(modifier = Modifier.height(20.dp))

        // 手动添加底部卡片按钮,测试不重绘顶部
        Button(onClick = {
            deck.add(Card(deck.size + topCard.value.id + 1, "Card ${deck.size + topCard.value.id + 1}"))
        }) {
            Text("Add Card to Bottom")
        }
    }
}

进阶版:MVVM架构封装

将数据逻辑抽离到ViewModel,更易于维护和测试:

class CardDeckViewModel : ViewModel() {
    private val _topCard = mutableStateOf(Card(1, "Card 1"))
    val topCard: State<Card> = _topCard

    private val deck = mutableListOf<Card>()

    init {
        repeat(5) {
            deck.add(Card(it + 2, "Card ${it + 2}"))
        }
    }

    // 抽卡操作:更新顶部卡片,同时底部加新卡
    fun drawCard() {
        if (deck.isNotEmpty()) {
            _topCard.value = deck.removeFirst()
            deck.add(Card(deck.size + _topCard.value.id + 1, "Card ${deck.size + _topCard.value.id + 1}"))
        }
    }

    // 手动添加底部卡片
    fun addCardToBottom() {
        deck.add(Card(deck.size + _topCard.value.id + 1, "Card ${deck.size + _topCard.value.id + 1}"))
    }
}

@Composable
fun CardDeckScreen(viewModel: CardDeckViewModel = viewModel()) {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Card(
            modifier = Modifier
                .size(200.dp, 300.dp)
                .clickable { viewModel.drawCard() },
            elevation = 8.dp
        ) {
            Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                Text(text = viewModel.topCard.value.content, fontSize = 24.sp)
            }
        }

        Spacer(modifier = Modifier.height(20.dp))

        Button(onClick = { viewModel.addCardToBottom() }) {
            Text("Add Card to Bottom")
        }
    }
}

附加:堆叠视觉效果实现

如果需要显示牌组堆叠的视觉效果(仅做装饰,不影响交互),可以用deckSize状态跟踪牌组数量,仅让堆叠背景卡片响应此变化:

@Composable
fun StackedCardDeck() {
    val topCard = remember { mutableStateOf(Card(1, "Card 1")) }
    val deckSize = remember { mutableStateOf(5) }
    val deck = remember { mutableListOf<Card>() }

    LaunchedEffect(Unit) {
        repeat(5) {
            deck.add(Card(it + 2, "Card ${it + 2}"))
        }
    }

    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        // 堆叠背景卡片,仅依赖deckSize,底部加卡时仅重绘这些卡片
        repeat(minOf(deckSize.value, 3)) { index ->
            Card(
                modifier = Modifier
                    .size(200.dp, 300.dp)
                    .offset(y = (index * 10).dp),
                elevation = (8 - index * 2).dp,
                colors = CardDefaults.cardColors(containerColor = Color.Gray.copy(alpha = 0.3f))
            ) {}
        }

        // 顶部可交互卡片,不受deckSize变化影响
        Card(
            modifier = Modifier
                .size(200.dp, 300.dp)
                .clickable {
                    if (deck.isNotEmpty()) {
                        topCard.value = deck.removeFirst()
                        deck.add(Card(deck.size + topCard.value.id + 1, "Card ${deck.size + topCard.value.id + 1}"))
                    }
                },
            elevation = 8.dp
        ) {
            Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                Text(text = topCard.value.content, fontSize = 24.sp)
            }
        }

        Button(
            modifier = Modifier.align(Alignment.BottomCenter).padding(bottom = 20.dp),
            onClick = {
                deck.add(Card(deck.size + topCard.value.id + 1, "Card ${deck.size + topCard.value.id + 1}"))
                deckSize.value += 1
            }
        ) {
            Text("Add Card to Bottom")
        }
    }
}

关键优势说明

  1. 避免不必要重绘:仅顶部卡片的状态变化会触发其UI重绘,底部加卡操作修改的是普通集合,不会触发任何重组
  2. 保持顶部UI状态:顶部卡片UI仅依赖自身状态,底部加卡时不会干扰其动画、交互状态
  3. 性能高效:减少了Compose的重组范围,提升了UI响应速度

内容的提问来源于stack exchange,提问作者Yosuke Sakurai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 04:50:26