compose-multiplatform日历应用:日程管理UI

compose-multiplatform日历应用:日程管理UI

【免费下载链接】compose-multiplatform JetBrains/compose-multiplatform: 是 JetBrains 开发的一个跨平台的 UI 工具库,基于 Kotlin 编写,可以用于开发跨平台的 Android,iOS 和 macOS 应用程序。 【免费下载链接】compose-multiplatform 项目地址: https://gitcode.com/GitHub_Trending/co/compose-multiplatform

还在为多平台日程管理应用开发而烦恼?一文掌握Compose Multiplatform构建跨平台日历应用的完整解决方案!本文将带你从零开始,使用JetBrains的Compose Multiplatform框架构建一个功能完整的跨平台日历日程管理应用。

🎯 读完本文你将获得

  • Compose Multiplatform日历组件的完整实现
  • 跨平台日期时间处理的最佳实践
  • 响应式日程管理UI的设计模式
  • 多平台状态管理的统一方案
  • 完整的代码示例和架构设计

📅 技术架构设计

mermaid

🏗️ 核心组件实现

日历视图组件

@Composable
fun CalendarView(
    modifier: Modifier = Modifier,
    selectedDate: LocalDate = LocalDate.now(),
    onDateSelected: (LocalDate) -> Unit = {},
    events: List<CalendarEvent> = emptyList()
) {
    val calendarState = rememberCalendarState(initialDate = selectedDate)
    
    Column(modifier = modifier) {
        CalendarHeader(
            state = calendarState,
            onPreviousMonth = { calendarState.previousMonth() },
            onNextMonth = { calendarState.nextMonth() }
        )
        
        CalendarGrid(
            state = calendarState,
            selectedDate = selectedDate,
            onDateSelected = onDateSelected,
            events = events
        )
    }
}

@Composable
fun rememberCalendarState(initialDate: LocalDate): CalendarState {
    return remember { CalendarState(initialDate) }
}

class CalendarState(initialDate: LocalDate) {
    var currentMonth by mutableStateOf(initialDate)
    
    fun previousMonth() {
        currentMonth = currentMonth.minusMonths(1)
    }
    
    fun nextMonth() {
        currentMonth = currentMonth.plusMonths(1)
    }
}

日期网格实现

@Composable
fun CalendarGrid(
    state: CalendarState,
    selectedDate: LocalDate,
    onDateSelected: (LocalDate) -> Unit,
    events: List<CalendarEvent>
) {
    val daysInMonth = remember(state.currentMonth) {
        calculateDaysInMonth(state.currentMonth)
    }
    
    LazyVerticalGrid(
        columns = GridCells.Fixed(7),
        horizontalArrangement = Arrangement.spacedBy(4.dp),
        verticalArrangement = Arrangement.spacedBy(4.dp)
    ) {
        items(daysInMonth) { day ->
            CalendarDay(
                date = day,
                isSelected = day == selectedDate,
                hasEvents = events.any { it.date == day },
                onClick = { onDateSelected(day) }
            )
        }
    }
}

@Composable
fun CalendarDay(
    date: LocalDate,
    isSelected: Boolean,
    hasEvents: Boolean,
    onClick: () -> Unit
) {
    Box(
        modifier = Modifier
            .size(40.dp)
            .clip(CircleShape)
            .background(
                if (isSelected) MaterialTheme.colorScheme.primary
                else MaterialTheme.colorScheme.surface
            )
            .clickable(onClick = onClick),
        contentAlignment = Center
    ) {
        Column(horizontalAlignment = CenterHorizontally) {
            Text(
                text = date.dayOfMonth.toString(),
                color = if (isSelected) MaterialTheme.colorScheme.onPrimary
                       else MaterialTheme.colorScheme.onSurface,
                fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal
            )
            
            if (hasEvents) {
                Spacer(modifier = Modifier.height(2.dp))
                Box(
                    modifier = Modifier
                        .size(4.dp)
                        .clip(CircleShape)
                        .background(MaterialTheme.colorScheme.primary)
                )
            }
        }
    }
}

📊 日程事件数据模型

data class CalendarEvent(
    val id: String = UUID.randomUUID().toString(),
    val title: String,
    val description: String = "",
    val date: LocalDate,
    val startTime: LocalTime? = null,
    val endTime: LocalTime? = null,
    val color: Color = Color.Unspecified,
    val isAllDay: Boolean = false,
    val reminder: Reminder? = null
)

enum class ReminderType {
    NONE, MINUTES_5, MINUTES_15, HOUR_1, DAY_1
}

data class Reminder(
    val type: ReminderType = ReminderType.NONE,
    val customTime: LocalDateTime? = null
)

@Stable
class EventState {
    val events = mutableStateListOf<CalendarEvent>()
    val selectedEvent = mutableStateOf<CalendarEvent?>(null)
    
    fun addEvent(event: CalendarEvent) {
        events.add(event)
    }
    
    fun updateEvent(updatedEvent: CalendarEvent) {
        val index = events.indexOfFirst { it.id == updatedEvent.id }
        if (index != -1) {
            events[index] = updatedEvent
        }
    }
    
    fun deleteEvent(eventId: String) {
        events.removeAll { it.id == eventId }
    }
    
    fun getEventsForDate(date: LocalDate): List<CalendarEvent> {
        return events.filter { it.date == date }
    }
}

@Composable
fun rememberEventState(): EventState {
    return remember { EventState() }
}

🎨 事件编辑界面

@Composable
fun EventEditor(
    event: CalendarEvent? = null,
    onSave: (CalendarEvent) -> Unit,
    onCancel: () -> Unit
) {
    var title by remember { mutableStateOf(event?.title ?: "") }
    var description by remember { mutableStateOf(event?.description ?: "") }
    var selectedDate by remember { mutableStateOf(event?.date ?: LocalDate.now()) }
    var startTime by remember { mutableStateOf(event?.startTime ?: LocalTime.NOON) }
    var endTime by remember { mutableStateOf(event?.endTime ?: LocalTime.NOON.plusHours(1)) }
    var isAllDay by remember { mutableStateOf(event?.isAllDay ?: false) }
    
    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text(if (event == null) "新建事件" else "编辑事件") },
                navigationIcon = {
                    IconButton(onClick = onCancel) {
                        Icon(Icons.Default.ArrowBack, "返回")
                    }
                },
                actions = {
                    IconButton(
                        onClick = {
                            val newEvent = CalendarEvent(
                                title = title,
                                description = description,
                                date = selectedDate,
                                startTime = if (isAllDay) null else startTime,
                                endTime = if (isAllDay) null else endTime,
                                isAllDay = isAllDay
                            )
                            onSave(newEvent)
                        },
                        enabled = title.isNotBlank()
                    ) {
                        Icon(Icons.Default.Check, "保存")
                    }
                }
            )
        }
    ) { padding ->
        Column(
            modifier = Modifier
                .padding(padding)
                .verticalScroll(rememberScrollState())
                .padding(16.dp)
        ) {
            OutlinedTextField(
                value = title,
                onValueChange = { title = it },
                label = { Text("事件标题") },
                modifier = Modifier.fillMaxWidth()
            )
            
            Spacer(modifier = Modifier.height(16.dp))
            
            OutlinedTextField(
                value = description,
                onValueChange = { description = it },
                label = { Text("事件描述") },
                modifier = Modifier.fillMaxWidth(),
                maxLines = 3
            )
            
            Spacer(modifier = Modifier.height(16.dp))
            
            DatePickerField(
                selectedDate = selectedDate,
                onDateSelected = { selectedDate = it }
            )
            
            Spacer(modifier = Modifier.height(16.dp))
            
            Switch(
                checked = isAllDay,
                onCheckedChange = { isAllDay = it },
                text = { Text("全天事件") }
            )
            
            if (!isAllDay) {
                Spacer(modifier = Modifier.height(16.dp))
                TimeRangePicker(
                    startTime = startTime,
                    endTime = endTime,
                    onStartTimeChange = { startTime = it },
                    onEndTimeChange = { endTime = it }
                )
            }
        }
    }
}

🔧 多平台适配方案

日期时间处理

// commonMain
expect class PlatformDateTimeFormatter {
    fun formatDate(date: LocalDate): String
    fun formatTime(time: LocalTime): String
    fun formatDateTime(dateTime: LocalDateTime): String
}

// androidMain
actual class PlatformDateTimeFormatter {
    actual fun formatDate(date: LocalDate): String {
        return DateTimeFormatter.ofPattern("yyyy-MM-dd").format(date)
    }
    
    actual fun formatTime(time: LocalTime): String {
        return DateTimeFormatter.ofPattern("HH:mm").format(time)
    }
    
    actual fun formatDateTime(dateTime: LocalDateTime): String {
        return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").format(dateTime)
    }
}

// iosMain
actual class PlatformDateTimeFormatter {
    actual fun formatDate(date: LocalDate): String {
        // 使用iOS的DateFormatter
        return NSDateFormatter().apply {
            dateFormat = "yyyy-MM-dd"
        }.stringFromDate(date.toNSDate())
    }
    
    // 其他实现...
}

平台特定功能

expect class PlatformNotification {
    fun scheduleEventReminder(event: CalendarEvent)
    fun cancelEventReminder(eventId: String)
}

// androidMain
actual class PlatformNotification {
    actual fun scheduleEventReminder(event: CalendarEvent) {
        // 使用Android的AlarmManager或WorkManager
        val intent = Intent(context, ReminderReceiver::class.java).apply {
            putExtra("event_id", event.id)
            putExtra("event_title", event.title)
        }
        
        val pendingIntent = PendingIntent.getBroadcast(
            context,
            event.id.hashCode(),
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
        
        val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
        val triggerTime = calculateTriggerTime(event)
        alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent)
    }
    
    actual fun cancelEventReminder(eventId: String) {
        val intent = Intent(context, ReminderReceiver::class.java)
        val pendingIntent = PendingIntent.getBroadcast(
            context,
            eventId.hashCode(),
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
        
        val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
        alarmManager.cancel(pendingIntent)
    }
}

📱 完整的应用架构

mermaid

🎯 性能优化建议

1. 懒加载优化

@Composable
fun CalendarViewOptimized(
    selectedDate: LocalDate,
    onDateSelected: (LocalDate) -> Unit,
    events: List<CalendarEvent>
) {
    LazyColumn {
        item {
            CalendarHeader(/* ... */)
        }
        
        items(calculateVisibleMonths(selectedDate)) { month ->
            key(month) {
                MonthView(
                    month = month,
                    selectedDate = selectedDate,
                    onDateSelected = onDateSelected,
                    events = events.filter { it.date.month == month.month }
                )
            }
        }
    }
}

2. 状态管理优化

@Composable
fun CalendarApp() {
    val eventState = rememberEventState()
    val calendarState = rememberCalendarState()
    
    // 使用derivedStateOf避免不必要的重组
    val eventsForSelectedDate = remember(calendarState.selectedDate, eventState.events) {
        derivedStateOf {
            eventState.events.filter { it.date == calendarState.selectedDate }
        }
    }
    
    // 使用LaunchedEffect处理副作用
    LaunchedEffect(calendarState.currentMonth) {
        // 预加载下个月的事件数据
        preloadEventsForMonth(calendarState.currentMonth.plusMonths(1))
    }
}

📋 功能对比表

功能特性AndroidiOSDesktopWeb
日历视图
事件编辑
本地存储
通知提醒⚠️
手势支持
主题切换

🚀 部署和发布

Gradle配置

// build.gradle.kts
kotlin {
    androidTarget()
    jvm("desktop")
    iosX64()
    iosArm64()
    iosSimulatorArm64()
    js(IR) {
        browser()
    }
    
    sourceSets {
        commonMain {
            dependencies {
                implementation(compose.runtime)
                implementation(compose.foundation)
                implementation(compose.material3)
                implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
            }
        }
    }
}

💡 最佳实践总结

  1. 状态管理: 使用单向数据流和响应式状态管理
  2. 性能优化: 合理使用remember、derivedStateOf和key
  3. 平台适配: 通过expect/actual机制处理平台差异
  4. 测试策略: 编写跨平台的单元测试和UI测试
  5. 用户体验: 保持各平台原生体验的一致性

通过Compose Multiplatform,你可以用一套代码构建出在Android、iOS、Desktop和Web上都能完美运行的日历应用。这种开发方式不仅提高了开发效率,还保证了各平台用户体验的一致性。

现在就开始你的跨平台日历应用开发之旅吧!🚀

【免费下载链接】compose-multiplatform JetBrains/compose-multiplatform: 是 JetBrains 开发的一个跨平台的 UI 工具库,基于 Kotlin 编写,可以用于开发跨平台的 Android,iOS 和 macOS 应用程序。 【免费下载链接】compose-multiplatform 项目地址: https://gitcode.com/GitHub_Trending/co/compose-multiplatform

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值