Compose笔记(五十九)--BadgedBox

这一节主要了解一下Compose中的BadgedBox,在Jetpack Compose中,BadgedBox是一个用于在任意可组合项右上角叠加徽章(Badge)的布局容器。它属于Material Design组件库的一部分,常用于在图标、头像、按钮等 UI 元素上显示通知数量、状态标记或提示信息。

API

Kotlin 复制代码
@Composable
fun BadgedBox(
    badge: @Composable () -> Unit, 
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit 
)

badge:徽章的具体内容(可自定义布局、样式)

content:被徽章标记的主组件

场景:

1 消息未读数

2 购物车商品数量

3 新功能提示

4 状态标识

栗子:

Kotlin 复制代码
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Home
import androidx.compose.material.icons.outlined.Notifications
import androidx.compose.material.icons.outlined.Person
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier

@Composable
fun TabBadgeDemo() {
    var selectedTab by remember { mutableIntStateOf(0) }
    val tabItems = listOf(
        "首页" to Icons.Outlined.Home,
        "通知" to Icons.Outlined.Notifications,
        "我的" to Icons.Outlined.Person
    )

    Scaffold(
        bottomBar = {
            NavigationBar {
                tabItems.forEachIndexed { index, (title, icon) ->
                    NavigationBarItem(
                        icon = {
                            
                            if (index == 1) {
                                BadgedBox(
                                    badge = { Badge() }, 
                                    content = { Icon(icon, contentDescription = title) }
                                )
                            } else {
                                Icon(icon, contentDescription = title)
                            }
                        },
                        label = { Text(title) },
                        selected = selectedTab == index,
                        onClick = { selectedTab = index }
                    )
                }
            }
        }
    ) { padding ->
        Box(modifier = Modifier.padding(padding)) {
            Text(
                text = "当前页面:${tabItems[selectedTab].first}",
                style = MaterialTheme.typography.bodyLarge
            )
        }
    }
}
Kotlin 复制代码
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp


data class ChatSession(
    val id: Int,
    val name: String,
    val lastMessage: String,
    val unreadCount: Int,
    val isMuted: Boolean,
    val isPinned: Boolean,
    val timestamp: String
)

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun  BadgedBoxDemo() {
    
    val chatSessions = remember {
        mutableStateListOf(
            ChatSession(1, "项目组", "大家记得参加明天的评审会", 3, false, true, "10:30"),
            ChatSession(2, "张三", "方案已更新,请看附件", 1, false, false, "09:15"),
            ChatSession(3, "家人群", "周末聚餐地点定好了吗?", 5, true, false, "昨天"),
            ChatSession(4, "客服中心", "您的订单已发货", 0, false, false, "周三"),
            ChatSession(5, "老板", "周一提交周报", 2, false, true, "上周")
        )
    }

    
    val totalUnread by remember(chatSessions) {
        derivedStateOf { chatSessions.sumOf { it.unreadCount } }
    }

    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("消息") },
                actions = {
                    
                    BadgedBox(
                        badge = {
                            
                            Badge(
                                containerColor = MaterialTheme.colorScheme.error,
                                contentColor = Color.White
                            ) {
                                Text(
                                    text = if (totalUnread > 99) "99+" else totalUnread.toString(),
                                    fontSize = 12.sp,
                                    fontWeight = FontWeight.Bold
                                )
                            }
                        },
                        content = {
                            
                            BadgedBox(
                                badge = {
                                    if (chatSessions.any { it.isMuted && it.unreadCount > 0 }) {
                                        Box(
                                            modifier = Modifier
                                                .size(8.dp)
                                                .clip(CircleShape)
                                                .background(Color.Gray)
                                                .offset(x = 8.dp, y = -8.dp)
                                        )
                                    }
                                },
                                content = {
                                    IconButton(onClick = { /* 全局设置 */ }) {
                                        Icon(Icons.Outlined.Settings, contentDescription = "设置")
                                    }
                                }
                            )
                        }
                    )
                }
            )
        },
        floatingActionButton = {
           
            BadgedBox(
                badge = {
                    if (chatSessions.any { it.isPinned && it.unreadCount > 0 }) {
                        Badge(
                            containerColor = MaterialTheme.colorScheme.primary,
                        ) {
                            Icon(
                                Icons.Outlined.Star,
                                contentDescription = "重要消息",
                                modifier = Modifier.size(12.dp)
                            )
                        }
                    }
                },
                content = {
                    FloatingActionButton(onClick = { /* 新建消息 */ }) {
                        Icon(Icons.Outlined.Add, contentDescription = "新建")
                    }
                }
            )
        }
    ) { padding ->
        LazyColumn(
            modifier = Modifier
                .fillMaxSize()
                .padding(padding)
                .padding(horizontal = 8.dp),
            verticalArrangement = Arrangement.spacedBy(4.dp)
        ) {
            items(chatSessions) { session ->
               
                Card(
                    modifier = Modifier
                        .fillMaxWidth()
                        .clickable {
                            
                            chatSessions[chatSessions.indexOf(session)] =
                                session.copy(unreadCount = 0)
                        },
                    shape = RoundedCornerShape(12.dp),
                    elevation = CardDefaults.cardElevation(2.dp)
                ) {
                    Row(
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(12.dp),
                        verticalAlignment = Alignment.CenterVertically
                    ) {
                        
                        Box(
                            modifier = Modifier
                                .size(48.dp)
                                .clip(CircleShape)
                                .background(MaterialTheme.colorScheme.primaryContainer),
                            contentAlignment = Alignment.Center
                        ) {
                            Text(
                                text = session.name.first().toString(),
                                fontSize = 18.sp,
                                fontWeight = FontWeight.Bold
                            )
                        }

                        Spacer(modifier = Modifier.width(12.dp))

                        
                        Column(
                            modifier = Modifier.weight(1f)
                        ) {
                            Row(
                                horizontalArrangement = Arrangement.SpaceBetween,
                                modifier = Modifier.fillMaxWidth()
                            ) {
                                Text(
                                    text = session.name,
                                    fontWeight = if (session.unreadCount > 0) FontWeight.Bold else FontWeight.Normal
                                )
                                Text(
                                    text = session.timestamp,
                                    fontSize = 12.sp,
                                    color = MaterialTheme.colorScheme.onSurfaceVariant
                                )
                            }
                            Text(
                                text = session.lastMessage,
                                fontSize = 14.sp,
                                color = if (session.unreadCount > 0)
                                    MaterialTheme.colorScheme.onSurface
                                else
                                    MaterialTheme.colorScheme.onSurfaceVariant,
                                maxLines = 1,
                                overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis
                            )
                        }

                        Spacer(modifier = Modifier.width(8.dp))

                        
                        Column(
                            horizontalAlignment = Alignment.End,
                            verticalArrangement = Arrangement.Top
                        ) {
                            
                            if (session.unreadCount > 0) {
                                Badge(
                                    containerColor = if (session.isPinned)
                                        MaterialTheme.colorScheme.primary
                                    else
                                        MaterialTheme.colorScheme.error,
                                    modifier = Modifier.offset(y = (-4).dp)
                                ) {
                                    Text(
                                        text = if (session.unreadCount > 9) "9+" else session.unreadCount.toString(),
                                        fontSize = 12.sp
                                    )
                                }
                            }

                            
                            when {
                                session.isPinned -> {
                                    Icon(
                                        Icons.Outlined.PushPin,
                                        contentDescription = "置顶",
                                        modifier = Modifier.size(16.dp),
                                        tint = MaterialTheme.colorScheme.primary
                                    )
                                }
                                session.isMuted -> {
                                    Icon(
                                        Icons.Outlined.VolumeOff,
                                        contentDescription = "静音",
                                        modifier = Modifier.size(16.dp),
                                        tint = Color.Gray
                                    )
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

注意:

1 不要滥用:徽章应只用于重要状态提示,避免界面杂乱

2 尺寸控制:徽章不宜过大

3 颜色对比:确保徽章文字与背景有足够对比度

相关推荐
用户41659673693551 小时前
ExoPlayer 播放花屏与跳跃?我们如何像 QuickTime 一样优雅处理音频时间戳错误
android
Y***h1871 小时前
MySQL不使用子查询的原因
android·数据库·mysql
p***93031 小时前
Java进阶之泛型
android·前端·后端
优选资源分享1 小时前
维克日记 v1.5.0:本地隐私日记工具
笔记·实用工具·本地日记
雷工笔记1 小时前
MES学习笔记之MES系统的作用和定位及与SCADA的关系
大数据·笔记·学习
3***16101 小时前
MySQL中ON DUPLICATE KEY UPDATE的介绍与使用、批量更新、存在即更新不存在则插入
android·数据库·mysql
雷工笔记1 小时前
MES学习笔记之MES常见的类别
笔记·学习
L***86531 小时前
MySQL中between and的基本用法、范围查询
android·数据库·mysql
丝斯20112 小时前
AI学习笔记整理(20)—— AI核心技术(深度学习4)
人工智能·笔记·学习