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颜色,好了,你也可以自己预留接口,改变某天日期的背景色,这个需要你自己动手修改代码。

博主幸劳,转载记得标注原出处链接,支持原创。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/pingmian/84729.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

驭码CodeRider 2.0全栈开发实战指南:从零构建现代化电商平台

驭码CodeRider 2.0全栈开发实战指南:从零构建现代化电商平台 一、CodeRider 2.0:重新定义全栈智能开发 1.1 革命性升级亮点 #mermaid-svg-AKjytNB4hD95UZtF {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-AKjyt…

大模型智能体AutoGen面试题及参考答案

目录 AutoGen 的核心是什么? Agent 在 AutoGen 中承担什么角色? AutoGen 是如何定义 AssistantAgent、UserProxyAgent 等代理类型的? 什么是 GroupChat(组对话)模式? AutoGen 的 system message 在框架中扮演什么作用? 如何通过 Agent 实现自然语言处理? AutoGen…

深度学习笔记26-天气预测(Tensorflow)

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、前期准备 1.数据导入 import numpy as np import pandas as pd import warnings import seaborn as sns import matplotlib.pyplot as plt warnings.filt…

day54 python对抗生成网络

目录 一、GAN对抗生成网络思想 二、实践过程 1. 数据准备 2. 构建生成器和判别器 3. 训练过程 4. 生成结果与可视化 三、学习总结 一、GAN对抗生成网络思想 GAN的核心思想非常有趣且富有对抗性。它由两部分组成&#xff1a;生成器&#xff08;Generator&#xff09;和判…

龙虎榜——20250613

上证指数放量下跌收阴线&#xff0c;个股下跌超4000只&#xff0c;受外围消息影响情绪总体较差。 深证指数放量下跌&#xff0c;收阴线&#xff0c;6月总体外围风险较高&#xff0c;转下跌走势的概率较大&#xff0c;注意风险。 2025年6月13日龙虎榜行业方向分析 1. 石油石化&…

Linux常用命令加强版替代品

Linux常用命令加强版替代品 还在日复一日地使用 ls、grep、cd 这些“上古”命令吗&#xff1f;是时候给你的终端来一次大升级了&#xff01;本文将为你介绍一系列强大、高效且设计现代的Linux命令行工具&#xff0c;它们将彻底改变你的工作流&#xff0c;让你爱上在终端里操作…

Hadoop 003 — JAVA操作MapReduce入门案例

MapReduce入门案例-分词统计 文章目录 MapReduce入门案例-分词统计1.xml依赖2.编写MapReduce处理逻辑3.上传统计文件到HDFS3.配置MapReduce作业并测试4.执行结果 1.xml依赖 <dependency><groupId>org.apache.hadoop</groupId><artifactId>hadoop-commo…

Python打卡第53天

浙大疏锦行 作业&#xff1a; 对于心脏病数据集&#xff0c;对于病人这个不平衡的样本用GAN来学习并生成病人样本&#xff0c;观察不用GAN和用GAN的F1分数差异。 import pandas as pd import numpy as np import torch import torch.nn as nn import torch.optim as optim from…

力扣-279.完全平方数

题目描述 给你一个整数 n &#xff0c;返回 和为 n 的完全平方数的最少数量 。 完全平方数 是一个整数&#xff0c;其值等于另一个整数的平方&#xff1b;换句话说&#xff0c;其值等于一个整数自乘的积。例如&#xff0c;1、4、9 和 16 都是完全平方数&#xff0c;而 3 和 1…

前端构建工具Webapck、Vite——>前沿字节开源Rspack详解——2023D2大会

Rspack 以下是针对主流构建工具&#xff08;Webpack、Vite、Rollup、esbuild&#xff09;的核心不足分析&#xff0c;以及 Rspack 如何基于这些痛点进行针对性改进 的深度解析&#xff1a; 一、主流构建工具的不足 1. Webpack&#xff1a;性能与生态的失衡 核心问题 冷启动慢…

输入法,开头输入这U I V 三个字母会不显示 任何中文

1. 汉语拼音规则的限制 汉语拼音中不存在以“V”“U”“I”为声母的情况&#xff1a; 汉语拼音的声母是辅音&#xff0c;而“V”“U”“I”在汉语拼音中都是元音&#xff08;或韵母的一部分&#xff09;。汉语拼音的声母系统中没有“V”“U”“I”作为声母的音节。例如&#xf…

Linux文件权限详解:从入门到精通

前言 权限是什么&#xff1f; 本质&#xff1a;无非就是能做和不能做什么。 为什么要有权限呢&#xff1f; 目的&#xff1a;为了控制用户行为&#xff0c;防止发生错误。 1.权限的理解 在学习下面知识之前要先知道的一点是&#xff1a;linux下一切皆文件&#xff0c;对li…

在多云环境透析连接ngx_stream_proxy_protocol_vendor_module

1、模块定位与价值 多云接入&#xff1a;在同一 Nginx 实例前端接入来自多云平台的私有链路时&#xff0c;能区分 AWS、GCP、Azure 特有的连接 ID。安全审计&#xff1a;自动记录云平台侧的 Endpoint/VPC ID&#xff0c;有助于联调和安全事件追踪。路由分流&#xff1a;基于不…

力扣:基本计算器

基本计算器: 224. 基本计算器 - 力扣&#xff08;LeetCode&#xff09; 本体思路为&#xff0c;将中缀表达式转为后缀表达式&#xff0c;通过后缀表达式进行运算。 中缀表达式: 我们日常生活中熟知的表达式如12-30 就是一个中缀表达式。 后缀表达式: 150. 逆波兰表达式求值 - …

《AI日报 · 0613|ChatGPT支持导出、Manus免费开放、GCP全球宕机》

AI 资讯 1️⃣ OpenAI ChatGPT Canvas新增多格式导出功能 OpenAI终于为ChatGPT Canvas推出了用户期待已久的导出功能。现在,用户可以将创作内容导出为多种格式:文档类支持PDF、docx和markdown格式,代码文件则可直接保存为对应扩展名的源文件(如.py、.js、.sql等)。这一功…

C++中的零拷贝技术

一、C中零拷贝技术的核心概念 零拷贝&#xff08;Zero-copy&#xff09;是一种重要的优化技术&#xff0c;旨在减少数据在内存中的不必要复制&#xff0c;从而提高程序性能、降低内存使用并减少CPU消耗。在C中&#xff0c;零拷贝技术通过多种方式实现&#xff0c;包括引用语义…

RT_Thread内核源码分析(五)——内存管理@小堆内存管理算法

目录 1、内存堆控制 1.1 内存堆控制器 1.2 内存块节点 1.3 内存堆管理 2、内存堆初始化 2.1 初始化接口 2.2 初始化示例 2.3 源码分析 3、内存堆操作 3.1 内存块申请 3.1.1 相关接口 3.1.2 原理分析 3.1.3 示例分析 3.1.4 代码分析 3.2 内存块伸缩 3.2.1 相关…

MyBatis-Plus 混合使用 XML 和注解

mybatisplus代码生成器&#xff1a; 版本匹配是个比较麻烦的问题&#xff0c;这是我的配置&#xff1a; <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</version>…

基于ssm的教学质量评估系统

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…

【STM32】G030单片机开启超过8个ADC通道的方法

如图所示通道数量已经超过8个&#xff0c;按照之前博客的办法已经行不通了 CubeMX配置STM32F103C8T6多路ADC配合DMA采集_stm32f103c8t6的adc采样率-CSDN博客 这里笔者开了10个channel&#xff0c;注意切换为不完全配置&#xff0c;否则的话最多只有8个rank 开DMA&#xff0c;…