Android Studio jetpack compose折叠日历日期选择器【折叠日历】
今天写一个日期选择器,大家根据自己需求改代码,记得点赞支持,谢谢~
这是进入的默认状态
折叠状态选中本周其他日期状态
切换上下周状态
展开日历状态
切换上下月状态
选中状态
代码如下:
import android.content.Context
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.ArrowForward
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.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.fj.test.utils.SetSystemUiColors
import com.fj.test.viewmodel.CollapsibleCalendarViewModel
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.YearMonth
import java.time.format.DateTimeFormatter
import java.time.format.TextStyle
import java.util.*@Composable
fun CollapsibleCalendarScreen(viewModel: CollapsibleCalendarViewModel) {val currentMonth by viewModel.currentMonth.collectAsState()val isExpanded by viewModel.isExpanded.collectAsState()val selectedDate by viewModel.selectedDate.collectAsState()val context = LocalContext.current// 设置系统 UI 颜色SetSystemUiColors(statusBarColor = Color.Transparent,navigationBarColor = Color.White,useDarkIcons = true)// 主界面Box(modifier = Modifier.fillMaxSize().background(Color(0xFFF5F5F9))) {Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {// 顶部标题栏CalendarTopBar(context, viewModel)Spacer(modifier = Modifier.height(16.dp))// 日历卡片CalendarCard(currentMonth = currentMonth,isExpanded = isExpanded,selectedDate = selectedDate,onPrevMonth = { viewModel.prevMonth() },onNextMonth = { viewModel.nextMonth() },onToggleExpand = { viewModel.toggleExpand() },onSelectDate = { viewModel.selectDate(it) })Spacer(modifier = Modifier.height(16.dp))// 这里可以添加选中日期的详细信息Text(text = "选中日期: ${selectedDate.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"))}",style = MaterialTheme.typography.bodyLarge,modifier = Modifier.padding(8.dp))}}
}@Composable
fun CalendarTopBar(context: Context, viewModel: CollapsibleCalendarViewModel) {Box(modifier = Modifier.fillMaxWidth().height(60.dp),) {IconButton(onClick = { /* 返回上一页 */ },modifier = Modifier.align(Alignment.CenterStart)) {Icon(imageVector = Icons.Default.ArrowBack,contentDescription = "返回",tint = Color(0xFF111111))}Text(modifier = Modifier.align(Alignment.Center),text = "折叠日历",fontSize = 16.sp,color = Color(0xFF111111),maxLines = 1,)}
}@Composable
fun CalendarCard(currentMonth: LocalDate,isExpanded: Boolean,selectedDate: LocalDate,onPrevMonth: () -> Unit,onNextMonth: () -> Unit,onToggleExpand: () -> Unit,onSelectDate: (LocalDate) -> Unit
) {Card(modifier = Modifier.fillMaxWidth(),shape = RoundedCornerShape(16.dp),colors = CardDefaults.cardColors(containerColor = Color.White)) {Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {// 月份选择器Row(modifier = Modifier.fillMaxWidth(),verticalAlignment = Alignment.CenterVertically) {IconButton(onClick = onPrevMonth) {Icon(imageVector = Icons.Default.ArrowBack,contentDescription = if (isExpanded) "上个月" else "上周")}Text(text = if (isExpanded) currentMonth.format(DateTimeFormatter.ofPattern("yyyy年MM月"))else "${selectedDate.format(DateTimeFormatter.ofPattern("MM月dd日"))} - ${selectedDate.plusDays(6).format(DateTimeFormatter.ofPattern("MM月dd日"))}",modifier = Modifier.weight(1f),textAlign = TextAlign.Center,fontWeight = FontWeight.Bold,fontSize = 18.sp)IconButton(onClick = onNextMonth) {Icon(imageVector = Icons.Default.ArrowForward,contentDescription = if (isExpanded) "下个月" else "下周")}IconButton(onClick = onToggleExpand) {Icon(imageVector = Icons.Default.ArrowDropDown,contentDescription = "展开/收起")}}Spacer(modifier = Modifier.height(8.dp))// 星期标题 - 从周日开始WeekdayHeader()// 日历内容AnimatedVisibility(visible = isExpanded,enter = expandVertically(),exit = shrinkVertically()) {// 月视图MonthCalendar(currentMonth = currentMonth,selectedDate = selectedDate,onSelectDate = onSelectDate)}if (!isExpanded) {// 周视图WeekCalendar(selectedDate = selectedDate,onSelectDate = onSelectDate)}}}
}@Composable
fun WeekdayHeader() {// 重新排序星期,使周日在第一位val daysOfWeek = listOf(DayOfWeek.SUNDAY,DayOfWeek.MONDAY,DayOfWeek.TUESDAY,DayOfWeek.WEDNESDAY,DayOfWeek.THURSDAY,DayOfWeek.FRIDAY,DayOfWeek.SATURDAY)Row(modifier = Modifier.fillMaxWidth()) {for (dayOfWeek in daysOfWeek) {Box(modifier = Modifier.weight(1f).padding(vertical = 8.dp),contentAlignment = Alignment.Center) {Text(text = dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.CHINA),fontSize = 14.sp,color = if (dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY)Color(0xFFE57373) else Color(0xFF666666))}}}
}@Composable
fun MonthCalendar(currentMonth: LocalDate,selectedDate: LocalDate,onSelectDate: (LocalDate) -> Unit
) {val yearMonth = YearMonth.of(currentMonth.year, currentMonth.month)val firstDay = yearMonth.atDay(1)val lastDay = yearMonth.atEndOfMonth()// 获取当月第一天是周几(0=周日,1=周一,...,6=周六)val firstDayOfWeek = firstDay.dayOfWeek.value % 7val daysInMonth = yearMonth.lengthOfMonth()val days = remember(currentMonth) {val list = mutableListOf<LocalDate>()val previousMonth = yearMonth.minusMonths(1)val daysInPreviousMonth = previousMonth.lengthOfMonth()// 添加上个月的日期填充第一行for (i in 0 until firstDayOfWeek) {val day = daysInPreviousMonth - firstDayOfWeek + i + 1list.add(LocalDate.of(previousMonth.year, previousMonth.month, day))}// 添加当月的日期for (i in 1..daysInMonth) {list.add(LocalDate.of(currentMonth.year, currentMonth.month, i))}// 计算需要填充的下个月天数val remainingCells = 42 - list.size // 6行7列 = 42个格子// 添加下个月的日期填充剩余行val nextMonth = yearMonth.plusMonths(1)for (i in 1..remainingCells) {list.add(LocalDate.of(nextMonth.year, nextMonth.month, i))}list}LazyVerticalGrid(columns = GridCells.Fixed(7),modifier = Modifier.height(240.dp)) {items(days) { date ->DayItem(date = date,selectedDate = selectedDate,isCurrentMonth = date.month == currentMonth.month,onSelectDate = onSelectDate)}}
}@Composable
fun WeekCalendar(selectedDate: LocalDate,onSelectDate: (LocalDate) -> Unit
) {// 获取当前选中日期所在的周(从周日开始)val startOfWeek = remember(selectedDate) {// 计算周日selectedDate.minusDays((selectedDate.dayOfWeek.value % 7).toLong())}val weekDays = remember(startOfWeek) {(0..6).map { startOfWeek.plusDays(it.toLong()) }}Row(modifier = Modifier.fillMaxWidth()) {weekDays.forEach { date ->Box(modifier = Modifier.weight(1f).padding(4.dp),contentAlignment = Alignment.Center) {DayItem(date = date,selectedDate = selectedDate,isCurrentMonth = true, // 周视图不需要区分当前月onSelectDate = onSelectDate)}}}
}@Composable
fun DayItem(date: LocalDate,selectedDate: LocalDate,isCurrentMonth: Boolean,onSelectDate: (LocalDate) -> Unit
) {val isSelected = date.equals(selectedDate)val isToday = date.equals(LocalDate.now())val isWeekend = date.dayOfWeek == DayOfWeek.SATURDAY || date.dayOfWeek == DayOfWeek.SUNDAYval isPastDate = date.isBefore(LocalDate.now())Box(modifier = Modifier.aspectRatio(1f).padding(4.dp).clip(CircleShape).background(when {isSelected -> Color(0xFF42A5F5)isToday -> Color(0xFFBBDEFB)else -> Color.Transparent}).border(width = if (isToday && !isSelected) 1.dp else 0.dp,color = if (isToday && !isSelected) Color(0xFF42A5F5) else Color.Transparent,shape = CircleShape).clickable { onSelectDate(date) },contentAlignment = Alignment.Center) {Text(text = date.dayOfMonth.toString(),color = when {isSelected -> Color.White!isCurrentMonth -> Color(0xFFBDBDBD) // 非当前月的日期显示为浅灰色isPastDate -> Color(0xFF9E9E9E) // 过去的日期显示为灰色isWeekend -> Color(0xFFE57373) // 周末显示为红色else -> Color(0xFF333333) // 未来的日期显示为黑色},fontSize = 14.sp,fontWeight = if (isToday) FontWeight.Bold else FontWeight.Normal)}
}
import androidx.lifecycle.LifecycleOwner
import com.fj.test.base.BaseViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import java.time.LocalDateclass CollapsibleCalendarViewModel : BaseViewModel() {// 当前显示的年月private val _currentMonth = MutableStateFlow(LocalDate.now().withDayOfMonth(1))val currentMonth: StateFlow<LocalDate> = _currentMonth// 是否展开日历private val _isExpanded = MutableStateFlow(false)val isExpanded: StateFlow<Boolean> = _isExpanded// 选中的日期private val _selectedDate = MutableStateFlow(LocalDate.now())val selectedDate: StateFlow<LocalDate> = _selectedDate// 切换上个月或上周(根据展开状态)fun prevMonth() {if (_isExpanded.value) {// 展开状态:切换到上个月_currentMonth.value = _currentMonth.value.minusMonths(1)} else {// 折叠状态:切换到上周_selectedDate.value = _selectedDate.value.minusWeeks(1)}}// 切换下个月或下周(根据展开状态)fun nextMonth() {if (_isExpanded.value) {// 展开状态:切换到下个月_currentMonth.value = _currentMonth.value.plusMonths(1)} else {// 折叠状态:切换到下周_selectedDate.value = _selectedDate.value.plusWeeks(1)}}// 展开/收起日历fun toggleExpand() {_isExpanded.value = !_isExpanded.value// 展开时确保当前月份与选中日期一致if (_isExpanded.value) {_currentMonth.value = LocalDate.of(_selectedDate.value.year,_selectedDate.value.month,1)}}// 选择日期fun selectDate(date: LocalDate) {_selectedDate.value = date}
}
核心代码我都给出来,这项目采用MVVM的架构,代码这里可以更改状态
Text(text = date.dayOfMonth.toString(),color = when {isSelected -> Color.White!isCurrentMonth -> Color(0xFFBDBDBD) // 非当前月的日期显示为浅灰色isPastDate -> Color(0xFF9E9E9E) // 过去的日期显示为灰色isWeekend -> Color(0xFFE57373) // 周末显示为红色else -> Color(0xFF333333) // 未来的日期显示为黑色},fontSize = 14.sp,fontWeight = if (isToday) FontWeight.Bold else FontWeight.Normal)
你可以根据数据更改不同颜色,比如,我希望过去是灰色,但是没到的日期都是黑色,你就改这里!isCurrentMonth -> Color(0xFFBDBDBD) // 非当前月的日期显示为浅灰色代码,【这里我改为0xFF333333】变成以下这样的
那么未到的日期都是是0xFF333333颜色,过去日期都是0xFF9E9E9E颜色,好了,你也可以自己预留接口,改变某天日期的背景色,这个需要你自己动手修改代码。
博主幸劳,转载记得标注原出处链接,支持原创。