
作为一名前端开发工程师,在掌握了 Compose 的基础组件、布局系统、状态管理和 Material 3 组件库后,我开始探索如何设计和开发自定义组件。本文将从前端开发者的视角,深入解析 Compose 自定义组件的设计原则、实现模式和性能优化技术。
在前端开发中,我们经常需要封装可复用的组件。无论是 React 的自定义 Hooks、Vue 的 Composables,还是 Web Components,组件化都是现代前端开发的核心思想。Jetpack Compose 同样提供了强大的组件化能力,但它的设计理念和实现方式与前端框架有所不同。
通过本模块的学习,我将探索:
| 前端开发 | Compose | 说明 | 
|---|---|---|
| 
 | 
 | 组件配置和数据传递 | 
| 
 | 
 | 内容插槽设计 | 
| 
 | 
 | 缓存和优化 | 
| 
 | 
 | 派生状态计算 | 
| 
 | 
 | 副作用处理 | 
| 
 | 
 | 防抖实现 | 
React 组件设计:
// React 自定义组件
function StatusBadge({ 
  text, 
  status = 'default',
  size = 'medium',
  icon,
  onClick 
}) {
  return (
    <div 
      className={`badge badge-${status} badge-${size}`}
      onClick={onClick}
    >
      {icon && <Icon name={icon} />}
      <span>{text}</span>
    </div>
  );
}Compose 组件设计:
// Compose 自定义组件
@Composable
fun StatusBadge(
    text: String,
    status: BadgeStatus = BadgeStatus.Default,
    size: BadgeSize = BadgeSize.Medium,
    modifier: Modifier = Modifier,
    icon: ImageVector? = null,
    onClick: (() -> Unit)? = null
) {
    // 组件实现
}核心差异:
在深入学习之前,让我们先运行示例项目,直观感受自定义组件的效果。
git clone https://github.com/easonxie/learn-jetpack-compose.git
cd learn-jetpack-composeWeb 版本(推荐给前端开发者):
# 启动 Web 开发服务器
./gradlew :lesson-05-custom-components:wasmJsBrowserDevelopmentRun
# 访问地址
http://localhost:8080
开发模式(自动重新加载):
./gradlew :lesson-05-custom-components:wasmJsBrowserDevelopmentRun --continuous参数化设计是指通过参数控制组件的外观和行为,使组件具有高度的可配置性和复用性。
前端对比:
让我们看一个完整的参数化组件示例:
/**
 * 自定义状态徽章组件
 * 展示参数化设计原则
 */
@Composable
fun StatusBadge(
    text: String,                                    // 必需参数:显示文本
    status: BadgeStatus = BadgeStatus.Default,       // 可选参数:状态类型
    size: BadgeSize = BadgeSize.Medium,              // 可选参数:尺寸大小
    modifier: Modifier = Modifier,                   // 可选参数:样式修饰符
    icon: ImageVector? = null,                       // 可选参数:图标
    onClick: (() -> Unit)? = null                    // 可选参数:点击回调
) {
    val colors = getBadgeColors(status)
    val dimensions = getBadgeDimensions(size)
    
    // 动画效果
    val animatedColor by animateColorAsState(
        targetValue = colors.backgroundColor,
        animationSpec = tween(300)
    )
    
    Box(
        modifier = modifier
            .background(
                color = animatedColor,
                shape = RoundedCornerShape(dimensions.cornerRadius)
            )
            .border(
                width = dimensions.borderWidth,
                color = colors.borderColor,
                shape = RoundedCornerShape(dimensions.cornerRadius)
            )
            .then(
                if (onClick != null) {
                    Modifier.clickable { onClick() }
                } else Modifier
            )
            .padding(
                horizontal = dimensions.horizontalPadding,
                vertical = dimensions.verticalPadding
            )
    ) {
        Row(
            horizontalArrangement = Arrangement.spacedBy(dimensions.iconSpacing),
            verticalAlignment = Alignment.CenterVertically
        ) {
            // 可选图标
            if (icon != null) {
                Icon(
                    imageVector = icon,
                    contentDescription = null,
                    tint = colors.contentColor,
                    modifier = Modifier.size(dimensions.iconSize)
                )
            }
            
            // 文本内容
            ChineseText(
                text = text,
                color = colors.contentColor,
                fontSize = dimensions.fontSize,
                fontWeight = FontWeight.Medium
            )
        }
    }
}使用枚举类型:
/**
 * 徽章状态枚举
 */
enum class BadgeStatus {
    Default, Success, Warning, Error, Info
}
/**
 * 徽章尺寸枚举
 */
enum class BadgeSize {
    Small, Medium, Large
}前端对比:
// TypeScript 类型定义
type BadgeStatus = 'default' | 'success' | 'warning' | 'error' | 'info';
type BadgeSize = 'small' | 'medium' | 'large';
interface BadgeProps {
  text: string;
  status?: BadgeStatus;
  size?: BadgeSize;
  icon?: string;
  onClick?: () => void;
}优势:
颜色配置:
/**
 * 徽章颜色配置
 */
data class BadgeColors(
    val backgroundColor: Color,
    val contentColor: Color,
    val borderColor: Color
)
@Composable
private fun getBadgeColors(status: BadgeStatus): BadgeColors {
    return when (status) {
        BadgeStatus.Success -> BadgeColors(
            backgroundColor = Color(0xFFE8F5E8),
            contentColor = Color(0xFF2E7D32),
            borderColor = Color(0xFF4CAF50)
        )
        BadgeStatus.Warning -> BadgeColors(
            backgroundColor = Color(0xFFFFF3E0),
            contentColor = Color(0xFFEF6C00),
            borderColor = Color(0xFFFF9800)
        )
        // ... 其他状态
    }
}尺寸配置:
/**
 * 徽章尺寸配置
 */
data class BadgeDimensions(
    val fontSize: TextUnit,
    val horizontalPadding: Dp,
    val verticalPadding: Dp,
    val cornerRadius: Dp,
    val borderWidth: Dp,
    val iconSize: Dp,
    val iconSpacing: Dp
)
private fun getBadgeDimensions(size: BadgeSize): BadgeDimensions {
    return when (size) {
        BadgeSize.Small -> BadgeDimensions(
            fontSize = 12.sp,
            horizontalPadding = 8.dp,
            verticalPadding = 4.dp,
            cornerRadius = 8.dp,
            borderWidth = 1.dp,
            iconSize = 12.dp,
            iconSpacing = 4.dp
        )
        // ... 其他尺寸
    }
}// 基础用法
StatusBadge(
    text = "成功",
    status = BadgeStatus.Success
)
// 带图标
StatusBadge(
    text = "警告",
    status = BadgeStatus.Warning,
    icon = Icons.Default.Warning
)
// 不同尺寸
StatusBadge(
    text = "大尺寸",
    status = BadgeStatus.Info,
    size = BadgeSize.Large,
    icon = Icons.Default.Star
)
// 可点击
var clickCount by remember { mutableIntStateOf(0) }
StatusBadge(
    text = "点击次数: $clickCount",
    status = BadgeStatus.Success,
    icon = Icons.Default.TouchApp,
    onClick = { clickCount++ }
)Slot API 是 Compose 中实现组合模式的核心机制,允许调用者提供自定义内容。
前端对比:
| 前端框架 | 插槽机制 | 说明 | 
|---|---|---|
| React | 
 | 通过 props 传递组件 | 
| Vue | 
 | 具名插槽和作用域插槽 | 
| Compose | 
 | 类型安全的插槽设计 | 
React 示例:
// React 组件插槽
function Card({ header, children, footer }) {
  return (
    <div className="card">
      <div className="header">{header}</div>
      <div className="content">{children}</div>
      <div className="footer">{footer}</div>
    </div>
  );
}
// 使用
<Card
  header={<h2>标题</h2>}
  footer={<button>确定</button>}
>
  <p>内容</p>
</Card>Vue 示例:
<!-- Vue 组件插槽 -->
<template>
  <div class="card">
    <div class="header">
      <slot name="header"></slot>
    </div>
    <div class="content">
      <slot></slot>
    </div>
    <div class="footer">
      <slot name="footer"></slot>
    </div>
  </div>
</template>
<!-- 使用 -->
<Card>
  <template #header>
    <h2>标题</h2>
  </template>
  <p>内容</p>
  <template #footer>
    <button>确定</button>
  </template>
</Card>Compose Slot API 设计:
/**
 * 可扩展卡片组件
 * 展示组合模式和 Slot API 设计
 */
@Composable
fun ExpandableCard(
    title: String,
    modifier: Modifier = Modifier,
    subtitle: String? = null,
    icon: ImageVector? = null,
    expandedInitially: Boolean = false,
    // Slot API:操作区域插槽
    actions: @Composable RowScope.() -> Unit = {},
    // Slot API:内容区域插槽
    content: @Composable ColumnScope.() -> Unit
) {
    var isExpanded by remember { mutableStateOf(expandedInitially) }
    
    Card(
        modifier = modifier.fillMaxWidth(),
        elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
    ) {
        Column {
            // 头部区域
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .clickable { isExpanded = !isExpanded }
                    .padding(16.dp),
                horizontalArrangement = Arrangement.SpaceBetween,
                verticalAlignment = Alignment.CenterVertically
            ) {
                Row(
                    modifier = Modifier.weight(1f),
                    horizontalArrangement = Arrangement.spacedBy(12.dp),
                    verticalAlignment = Alignment.CenterVertically
                ) {
                    // 可选图标
                    if (icon != null) {
                        Icon(
                            imageVector = icon,
                            contentDescription = null,
                            tint = MaterialTheme.colorScheme.primary,
                            modifier = Modifier.size(24.dp)
                        )
                    }
                    
                    // 标题和副标题
                    Column {
                        ChineseText(
                            text = title,
                            style = MaterialTheme.typography.titleMedium,
                            fontWeight = FontWeight.Bold
                        )
                        if (subtitle != null) {
                            ChineseText(
                                text = subtitle,
                                style = MaterialTheme.typography.bodySmall,
                                color = MaterialTheme.colorScheme.onSurfaceVariant
                            )
                        }
                    }
                }
                
                Row(
                    horizontalArrangement = Arrangement.spacedBy(8.dp),
                    verticalAlignment = Alignment.CenterVertically
                ) {
                    // 自定义操作区域(Slot API)
                    actions()
                    
                    // 展开/收起图标
                    Icon(
                        imageVector = if (isExpanded) Icons.Default.ExpandLess 
                                     else Icons.Default.ExpandMore,
                        contentDescription = if (isExpanded) "收起" else "展开"
                    )
                }
            }
            
            // 可展开内容区域
            AnimatedVisibility(
                visible = isExpanded,
                enter = fadeIn() + expandVertically(),
                exit = fadeOut() + shrinkVertically()
            ) {
                Column(
                    modifier = Modifier.padding(
                        start = 16.dp,
                        end = 16.dp,
                        bottom = 16.dp
                    )
                ) {
                    HorizontalDivider()
                    Spacer(modifier = Modifier.height(16.dp))
                    
                    // 内容区域(Slot API)
                    content()
                }
            }
        }
    }
}基础用法:
ExpandableCard(
    title = "基础可扩展卡片",
    subtitle = "这是一个简单的可扩展卡片示例",
    icon = Icons.Default.Info,
    expandedInitially = true
) {
    ChineseText(
        text = "这里是卡片的详细内容。可扩展卡片允许用户查看摘要信息,并根据需要展开查看更多详细信息。",
        style = MaterialTheme.typography.bodyMedium
    )
}带操作按钮的用法:
ExpandableCard(
    title = "带操作的可扩展卡片",
    subtitle = "展示 Slot API 的强大功能",
    icon = Icons.Default.Settings,
    // 使用 actions 插槽添加自定义操作
    actions = {
        IconButton(onClick = { /* 编辑操作 */ }) {
            Icon(
                imageVector = Icons.Default.Edit,
                contentDescription = "编辑"
            )
        }
        IconButton(onClick = { /* 分享操作 */ }) {
            Icon(
                imageVector = Icons.Default.Share,
                contentDescription = "分享"
            )
        }
    }
) {
    // 使用 content 插槽添加自定义内容
    Column(
        verticalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        ChineseText(
            text = "通过 Slot API,我们可以在不修改组件内部实现的情况下,插入自定义的操作按钮和内容。",
            style = MaterialTheme.typography.bodyMedium
        )
        
        // 嵌套使用其他组件
        TagGroup(
            tags = listOf("Slot API", "组合模式", "可扩展性")
        )
    }
}InfoDisplayCard 组件:
/**
 * 信息展示卡片组件
 * 展示更复杂的组合模式
 */
@Composable
fun InfoDisplayCard(
    modifier: Modifier = Modifier,
    // 头部插槽
    header: @Composable () -> Unit = {},
    // 主要内容插槽
    content: @Composable ColumnScope.() -> Unit,
    // 底部操作插槽
    footer: @Composable RowScope.() -> Unit = {}
) {
    Card(
        modifier = modifier.fillMaxWidth(),
        elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
    ) {
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            // 头部插槽
            header()
            
            // 主要内容插槽
            content()
            
            // 底部操作插槽
            Column {
                Spacer(modifier = Modifier.height(16.dp))
                HorizontalDivider()
                Spacer(modifier = Modifier.height(16.dp))
                
                Row(
                    modifier = Modifier.fillMaxWidth(),
                    horizontalArrangement = Arrangement.End
                ) {
                    footer()
                }
            }
        }
    }
}使用示例:
InfoDisplayCard(
    header = {
        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically
        ) {
            Column {
                ChineseText(
                    text = "信息展示卡片",
                    style = MaterialTheme.typography.titleMedium,
                    fontWeight = FontWeight.Bold
                )
                ChineseText(
                    text = "多插槽组合设计",
                    style = MaterialTheme.typography.bodySmall
                )
            }
            
            StatusBadge(
                text = "新功能",
                status = BadgeStatus.Info,
                size = BadgeSize.Small
            )
        }
    },
    content = {
        ChineseText(
            text = "这个组件展示了如何使用多个插槽(slot)来创建灵活的布局结构。",
            style = MaterialTheme.typography.bodyMedium
        )
    },
    footer = {
        TextButton(onClick = { /* 了解更多 */ }) {
            ChineseText(text = "了解更多")
        }
        
        Button(onClick = { /* 立即使用 */ }) {
            ChineseText(text = "立即使用")
        }
    }
)前端对比:
| 前端框架 | 优化技术 | Compose 对应 | 
|---|---|---|
| React | 
 | 
 | 
| React | 
 | 
 | 
| Vue | 
 | 
 | 
React 示例:
function SearchComponent() {
  const [searchText, setSearchText] = useState('');
  
  // useMemo 缓存计算结果
  const trimmedText = useMemo(() => {
    return searchText.trim();
  }, [searchText]);
  
  // useMemo 计算派生状态
  const searchResults = useMemo(() => {
    if (!trimmedText) return [];
    return data.filter(item => 
      item.name.includes(trimmedText)
    );
  }, [trimmedText, data]);
  
  return (
    <div>
      <input 
        value={searchText}
        onChange={e => setSearchText(e.target.value)}
      />
      <div>找到 {searchResults.length} 个结果</div>
    </div>
  );
}Compose 实现:
@Composable
fun OptimizedSearchComponent(
    modifier: Modifier = Modifier,
    onSearch: (String) -> Unit = {}
) {
    var searchText by remember { mutableStateOf("") }
    var isSearching by remember { mutableStateOf(false) }
    
    // 使用 derivedStateOf 避免不必要的重组
    val trimmedSearchText by remember {
        derivedStateOf { searchText.trim() }
    }
    
    // 模拟搜索结果(派生状态)
    val searchResults by remember {
        derivedStateOf {
            if (trimmedSearchText.isEmpty()) {
                emptyList()
            } else {
                // 模拟搜索结果
                listOf(
                    "搜索结果:$trimmedSearchText 相关项目1",
                    "搜索结果:$trimmedSearchText 相关项目2",
                    "搜索结果:$trimmedSearchText 相关项目3"
                ).filter { it.contains(trimmedSearchText, ignoreCase = true) }
            }
        }
    }
    
    Column(
        modifier = modifier.fillMaxWidth(),
        verticalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        // 搜索输入框
        OutlinedTextField(
            value = searchText,
            onValueChange = { searchText = it },
            modifier = Modifier.fillMaxWidth(),
            label = { ChineseText("搜索内容") }
        )
        
        // 搜索统计信息
        if (trimmedSearchText.isNotEmpty()) {
            ChineseText(
                text = "找到 ${searchResults.size} 个结果",
                style = MaterialTheme.typography.bodySmall
            )
        }
    }
}核心差异:
derivedStateOf 只在依赖变化时重新计算前端对比:
// JavaScript 防抖实现
function debounce(func, delay) {
  let timeoutId;
  return function(...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}
// React 使用防抖
function SearchComponent() {
  const [searchText, setSearchText] = useState('');
  
  useEffect(() => {
    const handler = setTimeout(() => {
      if (searchText.trim()) {
        performSearch(searchText);
      }
    }, 500);
    
    return () => clearTimeout(handler);
  }, [searchText]);
  
  return (
    <input 
      value={searchText}
      onChange={e => setSearchText(e.target.value)}
    />
  );
}Compose 实现:
@Composable
fun OptimizedSearchComponent(
    modifier: Modifier = Modifier,
    onSearch: (String) -> Unit = {}
) {
    var searchText by remember { mutableStateOf("") }
    var isSearching by remember { mutableStateOf(false) }
    
    val trimmedSearchText by remember {
        derivedStateOf { searchText.trim() }
    }
    
    // 防抖搜索效果
    LaunchedEffect(trimmedSearchText) {
        if (trimmedSearchText.isNotEmpty()) {
            isSearching = true
            delay(500) // 防抖延迟
            onSearch(trimmedSearchText)
            isSearching = false
        }
    }
    
    OutlinedTextField(
        value = searchText,
        onValueChange = { searchText = it },
        modifier = Modifier.fillMaxWidth(),
        label = { ChineseText("搜索内容") },
        trailingIcon = {
            if (isSearching) {
                CircularProgressIndicator(modifier = Modifier.size(20.dp))
            }
        }
    )
}优势:
LaunchedEffect 自动管理协程生命周期完整示例:
@Composable
fun OptimizedCounterComponent(
    modifier: Modifier = Modifier
) {
    var count by remember { mutableIntStateOf(0) }
    
    // 使用 derivedStateOf 计算派生状态
    val isEven by remember {
        derivedStateOf { count % 2 == 0 }
    }
    
    val countDescription by remember {
        derivedStateOf {
            when {
                count == 0 -> "初始状态"
                count < 10 -> "个位数"
                count < 100 -> "两位数"
                else -> "三位数或更多"
            }
        }
    }
    
    Column(
        modifier = modifier.fillMaxWidth(),
        verticalArrangement = Arrangement.spacedBy(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        // 计数显示卡片
        Card(
            modifier = Modifier.fillMaxWidth(),
            elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
        ) {
            Column(
                modifier = Modifier.padding(24.dp),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.spacedBy(12.dp)
            ) {
                ChineseText(
                    text = count.toString(),
                    style = MaterialTheme.typography.headlineLarge,
                    color = if (isEven) MaterialTheme.colorScheme.primary 
                           else MaterialTheme.colorScheme.secondary
                )
                
                StatusBadge(
                    text = if (isEven) "偶数" else "奇数",
                    status = if (isEven) BadgeStatus.Success else BadgeStatus.Info,
                    size = BadgeSize.Small
                )
                
                ChineseText(
                    text = countDescription,
                    style = MaterialTheme.typography.bodySmall
                )
            }
        }
        
        // 操作按钮
        Row(
            horizontalArrangement = Arrangement.spacedBy(12.dp)
        ) {
            Button(
                onClick = { count-- },
                enabled = count > 0
            ) {
                Icon(Icons.Default.Remove, contentDescription = "减少")
                Spacer(modifier = Modifier.width(4.dp))
                ChineseText("减少")
            }
            
            Button(onClick = { count = 0 }) {
                Icon(Icons.Default.Refresh, contentDescription = "重置")
                Spacer(modifier = Modifier.width(4.dp))
                ChineseText("重置")
            }
            
            Button(onClick = { count++ }) {
                Icon(Icons.Default.Add, contentDescription = "增加")
                Spacer(modifier = Modifier.width(4.dp))
                ChineseText("增加")
            }
        }
    }
}LaunchedEffect 管理副作用:
@Composable
fun RealTimeDataComponent(
    modifier: Modifier = Modifier
) {
    var data by remember { mutableStateOf(generateRandomData()) }
    var isAutoRefresh by remember { mutableStateOf(false) }
    var lastUpdateTime by remember { mutableStateOf(Clock.System.now()) }
    
    // 自动刷新效果
    LaunchedEffect(isAutoRefresh) {
        while (isAutoRefresh) {
            delay(2000) // 每2秒更新一次
            data = generateRandomData()
            lastUpdateTime = Clock.System.now()
        }
    }
    
    Column(
        modifier = modifier.fillMaxWidth(),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        // 控制面板
        Card {
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp),
                horizontalArrangement = Arrangement.SpaceBetween,
                verticalAlignment = Alignment.CenterVertically
            ) {
                Column {
                    ChineseText(
                        text = "自动刷新",
                        style = MaterialTheme.typography.bodyMedium
                    )
                    ChineseText(
                        text = if (isAutoRefresh) "每2秒更新" else "已暂停",
                        style = MaterialTheme.typography.bodySmall
                    )
                }
                
                Row(
                    horizontalArrangement = Arrangement.spacedBy(8.dp)
                ) {
                    Switch(
                        checked = isAutoRefresh,
                        onCheckedChange = { isAutoRefresh = it }
                    )
                    
                    IconButton(
                        onClick = {
                            data = generateRandomData()
                            lastUpdateTime = Clock.System.now()
                        }
                    ) {
                        Icon(
                            imageVector = Icons.Default.Refresh,
                            contentDescription = "手动刷新"
                        )
                    }
                }
            }
        }
        
        // 数据展示
        data.forEach { item ->
            DataItemCard(item = item)
        }
        
        // 更新时间显示
        ChineseText(
            text = "最后更新:${formatTimestamp(lastUpdateTime)}",
            style = MaterialTheme.typography.bodySmall
        )
    }
}每个组件应该专注于特定功能:
// ✅ 正确:单一职责
@Composable
fun StatusBadge(
    text: String,
    status: BadgeStatus = BadgeStatus.Default
) {
    // 只负责显示状态徽章
}
// ❌ 错误:职责过多
@Composable
fun ComplexComponent(
    // 太多不相关的参数
    title: String,
    subtitle: String,
    badge: String,
    buttons: List<String>,
    data: List<Item>
) {
    // 组件做了太多事情
}提供合理的默认值,简化使用:
// ✅ 正确:提供默认值
@Composable
fun StatusBadge(
    text: String,                                    // 必需参数
    status: BadgeStatus = BadgeStatus.Default,       // 有默认值
    size: BadgeSize = BadgeSize.Medium,              // 有默认值
    modifier: Modifier = Modifier,                   // 有默认值
    icon: ImageVector? = null,                       // 可选参数
    onClick: (() -> Unit)? = null                    // 可选参数
) {
    // 实现
}
// 简单使用
StatusBadge(text = "成功")
// 完整配置
StatusBadge(
    text = "成功",
    status = BadgeStatus.Success,
    size = BadgeSize.Large,
    icon = Icons.Default.CheckCircle,
    onClick = { /* 处理点击 */ }
)始终接受 Modifier 参数,让调用者控制样式:
// ✅ 正确:接受 Modifier
@Composable
fun StatusBadge(
    text: String,
    modifier: Modifier = Modifier  // 接受 Modifier
) {
    Box(
        modifier = modifier  // 应用 Modifier
            .background(...)
            .padding(...)
    ) {
        // 内容
    }
}
// 使用时可以自定义样式
StatusBadge(
    text = "成功",
    modifier = Modifier
        .fillMaxWidth()
        .padding(16.dp)
)使用组合模式而非继承:
// ✅ 正确:使用组合
@Composable
fun ExpandableCard(
    title: String,
    content: @Composable ColumnScope.() -> Unit  // 组合内容
) {
    Card {
        Column {
            Text(title)
            content()  // 调用者提供内容
        }
    }
}
// ❌ 错误:不要使用继承
// Compose 不支持组件继承组合多个自定义组件构建复杂表单:
@Composable
fun UserProfileForm() {
    var name by remember { mutableStateOf("") }
    var email by remember { mutableStateOf("") }
    var status by remember { mutableStateOf(BadgeStatus.Default) }
    
    ExpandableCard(
        title = "用户信息",
        subtitle = "编辑个人资料",
        icon = Icons.Default.Person,
        expandedInitially = true
    ) {
        Column(
            verticalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            OutlinedTextField(
                value = name,
                onValueChange = { name = it },
                label = { ChineseText("姓名") },
                modifier = Modifier.fillMaxWidth()
            )
            
            OutlinedTextField(
                value = email,
                onValueChange = { email = it },
                label = { ChineseText("邮箱") },
                modifier = Modifier.fillMaxWidth()
            )
            
            Row(
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                ChineseText("账户状态:")
                StatusBadge(
                    text = "活跃",
                    status = BadgeStatus.Success,
                    size = BadgeSize.Small
                )
            }
        }
    }
}@Composable
fun ProductCard(product: Product) {
    InfoDisplayCard(
        header = {
            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceBetween
            ) {
                ChineseText(
                    text = product.name,
                    style = MaterialTheme.typography.titleMedium,
                    fontWeight = FontWeight.Bold
                )
                StatusBadge(
                    text = product.status,
                    status = BadgeStatus.Success,
                    size = BadgeSize.Small
                )
            }
        },
        content = {
            Column(
                verticalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                ChineseText(
                    text = product.description,
                    style = MaterialTheme.typography.bodyMedium
                )
                
                TagGroup(
                    tags = product.tags
                )
            }
        },
        footer = {
            TextButton(onClick = { /* 查看详情 */ }) {
                ChineseText("查看详情")
            }
            Button(onClick = { /* 立即购买 */ }) {
                ChineseText("立即购买")
            }
        }
    )
}通过本模块的学习,我对 Compose 自定义组件开发有了深入的理解。从参数化设计到组合模式,从性能优化到设计原则,Compose 提供了一套完整而优雅的组件开发体系。
相比前端框架,Compose 的类型安全设计(枚举类型、数据类)让组件 API 更加清晰可靠。Slot API 的设计理念与 React 的 render props 和 Vue 的插槽异曲同工,但通过 @Composable 函数参数提供了更强的类型约束。性能优化方面,remember、derivedStateOf 和 LaunchedEffect 的组合使用,让我们能够轻松构建高性能的响应式组件。
对于前端开发者来说,掌握自定义组件开发是构建复杂应用的关键一步。下一步,我将继续探索应用架构与导航系统,学习如何将这些组件组织成完整的应用程序。
项目仓库:https://github.com/easonxie/learn-jetpack-compose
后续会继续更新基于这个项目的更多内容,敬请期待!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。