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 颜色对比:确保徽章文字与背景有足够对比度

相关推荐
阿巴斯甜2 天前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker2 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95272 天前
Andorid Google 登录接入文档
android
黄林晴2 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab2 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇2 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_3 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android