47. Compose自定义绘制日历-1

news2024/10/7 13:23:39

有个日历的需求, 自己实现一下简单的

  1. 生成数据
private fun initData() {
        val listOfCalendar = mutableListOf<CalendarData>()

        val calendar = Calendar.getInstance()
        val todayYear = calendar.get(Calendar.YEAR)
        val todayMonth  = calendar.get(Calendar.MONTH)
        val todayDay  = calendar.get(Calendar.DAY_OF_MONTH)


        calendar.firstDayOfWeek = Calendar.MONDAY // 设置一周的第一天为周一

        (0..100).forEach { monthIndex ->
            if (monthIndex > 0) {
                calendar.add(Calendar.MONTH, 1)
            } else {
                calendar.add(Calendar.MONTH, 0)
            }

            val year = calendar[Calendar.YEAR]
            val month = calendar.get(Calendar.MONTH)
            val calendarData = CalendarData(
                year = year,
                month = month + 1
            )

            calendar[year, month] = 1 // 设置日期为月份的第一天

            val list = mutableListOf<MonthData>()
            for (dayOfMonth in 1..calendar.getActualMaximum(Calendar.DAY_OF_MONTH)) {
                Log.d("TAG,", "dayOfMonth:::$dayOfMonth")
                calendar[year, month] = dayOfMonth

                val dayOfWeek = calendar[Calendar.DAY_OF_WEEK]
                XLogger.d("${month + 1} 月 第" + dayOfMonth + "天是星期" + (dayOfWeek - 1))

                list.add(
                    MonthData(
                        year = year,
                        month = month + 1,
                        day = dayOfMonth,
                        week = if ((dayOfWeek - 1) == 0) 7 else (dayOfWeek - 1),
                        weekOfYear = calendar.get(Calendar.WEEK_OF_YEAR),
                        isCurrentDay = year == todayYear && month == todayMonth && dayOfMonth == todayDay

                    )
                )
            }
            listOfCalendar.add(calendarData.copy(list = list))
        }
    }
  1. 界面的绘制
    入口

@OptIn(ExperimentalFoundationApi::class, ExperimentalTextApi::class)
@Composable
fun Calendar(homeViewModel: HomeViewModel = viewModel()) {
    val homeUiState = homeViewModel.homeUiState.collectAsState().value
    val weekTitleList = homeUiState.weekTitleList
    val pagerState = rememberPagerState()

    val textMeasurerAndTextSize = getTextMeasurerAndTextSize()

    Column(modifier = Modifier.fillMaxSize()) {
        XLogger.d("==================>Calendar")
        YearAndMonth(homeViewModel, pagerState)
        //星期
        WeekRow(weekTitleList)
        //日历信息
        CalendarPager(homeViewModel, pagerState, textMeasurerAndTextSize)
    }
}

星期信息


/**
 * 星期信息
 */
@Composable
fun WeekRow(weekTitleList: List<String>) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(vertical = 4.dp)
    ) {
        weekTitleList.forEachIndexed { _, s ->
            Text(
                text = s,
                modifier = Modifier.weight(1f),
                textAlign = TextAlign.Center,
                fontSize = 14.sp,
                color = Color.Black,
                fontWeight = FontWeight.Medium
            )
        }
    }
}

/**
 * 年和月
 */
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun YearAndMonth(homeViewModel: HomeViewModel, pagerState: PagerState) {
    val homeUiState = homeViewModel.homeUiState.collectAsState().value
    val calendarList = homeUiState.calendarList
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(start = 12.dp),
        verticalAlignment = Alignment.CenterVertically
    ) {
        Text(
            text = "${calendarList[pagerState.currentPage].year}年",
            fontSize = 30.sp,
            fontWeight = FontWeight.SemiBold,
            color = Color.Black
        )
        Text(
            text = "${calendarList[pagerState.currentPage].month}月",
            fontSize = 30.sp,
            fontWeight = FontWeight.SemiBold,
            color = Color.Black
        )
    }
}

获取 TextMeasurer 测量文字的高度


/**
 * 获取 TextMeasurer 测量文字的高度
 */
@OptIn(ExperimentalTextApi::class)
@Composable
fun getTextMeasurerAndTextSize(): Pair<TextMeasurer, IntSize> {
    val textMeasurer = rememberTextMeasurer()
    val textLayoutResult: TextLayoutResult =
        textMeasurer.measure(
            text = "9",
            style = TextStyle(
                textAlign = TextAlign.Center,
                color = Color.Black,
                fontSize = 14.sp,
                fontWeight = FontWeight.Medium,
            )
        )
    val textSize = textLayoutResult.size

    return Pair(textMeasurer, textSize)
}

日历的滑动页面


/**
 * 日历的滑动页面
 */
@OptIn(ExperimentalFoundationApi::class, ExperimentalTextApi::class)
@Composable
fun CalendarPager(
    homeViewModel: HomeViewModel,
    pagerState: PagerState,
    textMeasurerAndTextSize: Pair<TextMeasurer, IntSize>
) {
    val homeUiState = homeViewModel.homeUiState.collectAsState().value
    val calendarList = homeUiState.calendarList

    //size 最大的列 找出最长的那个决定高度
    var maxColumn by remember {
        mutableStateOf(1)
    }

    val screenWidthDp = LocalConfiguration.current.screenWidthDp

    val paddingPx = 2

    val textMeasurer = textMeasurerAndTextSize.first
    val textSize = textMeasurerAndTextSize.second


    LaunchedEffect(key1 = pagerState.currentPage, block = {
        XLogger.d("=======>${pagerState.currentPage}")
        //TODO:监听 滑动到<=2 或 size-2 的时候追加 日期数据
    })
    XLogger.d("==================>CalendarPager")

    HorizontalPager(
        pageCount = calendarList.size,
        state = pagerState,
        modifier = Modifier
            .fillMaxWidth()
            .wrapContentHeight(),

        ) {
        val calendarData = calendarList[it]

        //绘制整个月的数据
        Canvas(modifier = Modifier
            .fillMaxWidth()
            .height((maxColumn * screenWidthDp / 7f).dp)
//            .pointerInput(Unit) {
//                detectTapGestures(onTap = {
//                   //TODO:点击 定位哪一天
//                })
//                detectDragGestures { change, dragAmount ->
//                    //TODO:竖直方向滑动 进入周历模式
//                }
//            }
//            .background(color = Color.Blue)
            , onDraw = {
                val perWidthWithPadding = this.size.width / 7f

                //第一条数据是周几
                val firstWeek = calendarData.list[0].week

                //计算最大的列 跨度 一年的几周就是最大的列
                maxColumn = calendarData.list.groupBy { monthData ->
                    monthData.weekOfYear
                }.size

                //根据星期 进行分组
                val groupedData = calendarData.list.groupBy { monthData ->
                    monthData.week
                }

                //竖着按照列进行 绘制
                groupedData.forEach { (week, monthDataList) ->
                    monthDataList.forEachIndexed { index, monthData ->
                        //按照列写的数据
//                    drawRoundRect(
//                        color = Color.Magenta,
//                        topLeft = Offset(
//                            (week - 1) * perWidthWithPadding + paddingPx,
//                            (index + if (week >= firstWeek) 0 else 1) * perWidthWithPadding - paddingPx
//                        ),
//                        size = Size(
//                            perWidthWithPadding - 2 * paddingPx,
//                            perWidthWithPadding - 2 * paddingPx
//                        )
//                    )
                        drawText(
                            textMeasurer = textMeasurer,
                            text = "${monthData.day}",
                            size = Size(
                                perWidthWithPadding - 2 * paddingPx,
                                perWidthWithPadding - 2 * paddingPx
                            ),
                            topLeft = Offset(
                                (week - 1) * perWidthWithPadding,

                                (index + if (week >= firstWeek) 0 else 1) * perWidthWithPadding
                                        //定位到中间位置
                                        + perWidthWithPadding * 0.5f
                                        //减去文字的高度
                                        - textSize.height / 2f
                            ),
                            style = TextStyle(
                                textAlign = TextAlign.Center,
                                color = if (monthData.isCurrentDay) Color.Red else Color.Black,
                                fontSize = 14.sp,
                                fontWeight = FontWeight.Medium,
                            )
                        )


                        //当天画圆背景
                        if (monthData.isCurrentDay) {
                            drawCircle(
                                color = Color.LightGray.copy(0.5f),
                                radius = (perWidthWithPadding - 2 * paddingPx) / 2f,
                                center = Offset(
                                    (week - 1) * perWidthWithPadding + paddingPx + perWidthWithPadding / 2f,
                                    (index + if (week >= firstWeek) 0 else 1) * perWidthWithPadding - paddingPx + perWidthWithPadding / 2f
                                ),
                            )
                        }
                    }
                }
            })
    }
}

源码地址github

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

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

相关文章

单机和分布式有什么区别?分布式系统相比单机系统的优势在哪里?

写在前面 本文隶属于专栏《大数据理论体系》&#xff0c;该专栏为笔者原创&#xff0c;引用请注明来源&#xff0c;不足和错误之处请在评论区帮忙指出&#xff0c;谢谢&#xff01; 本专栏目录结构和文献引用请见《大数据理论体系》 思维导图 1. 资源共享 单机系统是指只有一…

springboot项目通过nginx访问ftp服务器文件

前文 本来准备记录一下。项目中遇到的springboot项目访问ftp服务器图片、视频问题的&#xff0c;想在我自己服务器上重新部署一遍&#xff0c;然后发现&#xff0c;执行docker的时候报错了。具体报错原因如下&#xff1a; 原因是我重启了一下服务器 Cannot connect to the Do…

ChatGPT实战:生成演讲稿

当众发言&#xff08;演讲&#xff09;是一种传达信息、观点和情感的重要方式。通过演讲&#xff0c;人们可以在公共场合表达自己的观点&#xff0c;向观众传递自己的知识和经验&#xff0c;激发听众的思考和行动。无论是商务演讲、学术讲座还是政治演说&#xff0c;演讲稿的写…

linux查找文件内容命令之grep -r ‘关键字‘

目录 grep命令介绍参数选项 grep命令的使用1. 在指定的文件中查找包含的关键字2. 在指定目录下多个文件内容中查找包含的关键字3.在追加的文件内容中查找关键字4. 统计文件中关键字出现的次数5. vi或vim打开的文件查找关键字(补充) 总结 grep命令介绍 Linux操作系统中 grep 命…

EventBus源码分析

差不多两年没写博客了&#xff0c;最近想着要找工作了&#xff0c;打算复习下一些常用的开源库&#xff0c;也是这篇博客的由来&#xff5e; EventBus使用非常简单 参考&#xff1a;github 再贴一张官网的图 一、示例代码 示例代码是为了便于理解后面注解处理器生成代码的处…

1. MyBatis 整体架构

作为正式内容的第一篇&#xff0c;本次不会介绍具体的技术&#xff0c;而是先从全局视角上对 MyBatis 做一个俯瞰&#xff0c;了解 MyBatis 项目工程的组织结构&#xff0c;以及内部的核心功能模块。 工程结构 打开 MyBatis 的 Github 地址&#xff0c;就可以看到其代码工程结…

C语言:打印用 * 组成的带空格直角三角形图案

题目&#xff1a; 多组输入一个整数&#xff08;2~20&#xff09;&#xff0c;表示直角三角形直角边的长度&#xff0c;即 * 的数量&#xff0c;也表示输出行数。 思路&#xff1a; 总体思路&#xff1a; 找到规律&#xff1a; 行数 列数 < 三角形长度 - 1 打印 两个空格…

一步一步学OAK之十四: 获取OAK设备信息

这一节我们通过调用DepthAI API 来获取OAK设备信息 目录 DeviceBootloader简介获取OAK设备信息的方法Setup 1: 创建文件Setup 2: 安装依赖Setup 3: 导入需要的包Setup 4: 获取可用设备Setup 5: 判断infos的长度Setup 6: 遍历infosSetup 7: 打印提示消息Setup 8: 连接设备Setup…

html_4——知识总结

html_4——知识总结 一、计算机基础知识二、html4总结2.1 html基本结构2.2 全局属性-id,class,style,dir,title,lang2.3 格式排版标签-div,p,h1-h6,br,hr,pre2.4 文本标签-span,en,strong,del,ins,sub,sup2.5 图片标签-img:src,alt,width,height,boder2.6 超链接-a:herf,target…

内部函数和外部函数

文章目录 怎么来的&#xff1f;内部函数外部函数明确一下内外的概念&#xff1a;外部函数的实例fgets()函数 怎么来的&#xff1f; 函数本质上是全局的&#xff0c;因为定义一个函数的目的就是这个函数与其他函数之间相互调用&#xff0c;如果不声明的话&#xff0c;一个函数既…

YouTube正测试屏蔽“广告拦截器”,以确保其广告收入

YouTube目前正在进行一项全球范围内的小规模测试&#xff0c;警告用户关掉他们的广告屏蔽器&#xff0c;否则将被限制观看视频的次数。 周三&#xff08;6月28日&#xff09;&#xff0c;Reddit的一位用户发现&#xff0c;在使用YouTube时弹出了一个窗口&#xff0c;提示该用户…

Cali3F: Calibrated Fast Fair Federated Recommendation System

Decentralized Collaborative Learning Framework for Next POI Recommendation 标定的&#xff08;校准的&#xff09;快速公平联邦推荐系统 1. What does literature study? 提出一个经过校准的快速而公平的联邦推荐框架Cali3F&#xff0c;通过集群内参数共享解决了收敛问…

创新引领未来:RFID技术在汽车装配中的智能革命

射频识别&#xff08;RFID&#xff09;技术作为一种自动识别技术&#xff0c;已经在许多领域得到广泛应用。在汽车装配领域&#xff0c;RFID技术的应用可以提高装配过程的效率、降低人工错误率&#xff0c;并帮助实现自动化和智能化生产。本文将介绍RFID技术在汽车装配中的应用…

动态二维码生成器PHP Dynamic QRcode

什么是 PHP Dynamic QRcode &#xff1f; PHP Dynamic QRcode 是一个允许生成和保存动态和静态二维码&#xff08;QR码&#xff09;的应用。它具有简洁、响应灵敏且用户友好的设计。其中包含您网站中可能需要的一般功能&#xff0c;如&#xff1a;记录管理&#xff08;CRUD&…

【2023,学点儿新Java-27】是的——C语言中的const关键字 | 附:按照类型 快速了解与划分:C语言中的关键字 | goto关键字解释

前情回顾&#xff1a; 【2023&#xff0c;学点儿新Java-26】关键字介绍示例代码&#xff1a;assert 断言&#xff08;如何启用断言&#xff09;&#xff0c;以 验证一个数组的长度是否不为零 为例说明【2023&#xff0c;学点儿新Java-25】如何解决浮点计算存在误差&#xff1a…

Selenium Grid入门详解

目录 前言&#xff1a; 一、简介 二、使用场景 三、使用前提 四、使用方式 五、实现在另一台电脑运行脚本 前言&#xff1a; Selenium Grid是一个用于分布式测试的工具&#xff0c;它允许同时在多个机器上执行Selenium测试。通过使用Selenium Grid&#xff0c;你可以在不…

Linux系统Centos7 安装MySQL8.0详细步骤

MySql安装 1.下载wget命令 yum -y install wget 2. 在线下载mysql安装包 wget https://repo.mysql.com//mysql80-community-release-el7-3.noarch.rpm 3.MySQL的GPG升级了&#xff0c;需要更新&#xff0c;如果是新安装的MySQL&#xff0c;执行以下脚本即可&#xff1…

Vite + Vue3 + Electron实现进程通信

Vite Vue3 Electron实现进程通信 实现 渲染进程 / 主进程 通信&#xff08;IPC&#xff09; Electron 是一个基于 Chromium 和 Node.js 的桌面应用程序开发框架&#xff0c;而 Vue3 则是一种流行的前端框架。将两者结合使用可以快速地打造出跨平台的桌面应用程序。在这种组…

Sui x KuCoin Labs夏季黑客松第三批入围项目公布

自Sui x KuCoin Labs夏季黑客松开放注册以来&#xff0c;已收获了众多开发者的报名参与。赛程过半&#xff0c;截至目前为止&#xff0c;第一批和第二批入围项目已在前两周公布&#xff0c;第三批入围名单项目新鲜出炉&#xff0c;进入最终的Demo Day。 第三批入围名单 SuiVi…

在 Linux 中查找 IP 地址的 3 种简单方法

在 Linux 系统中&#xff0c;经常需要查找 IP 地址以进行网络配置、故障排除或安全管理。无论是查找本地主机的 IP 地址还是查找其他设备的 IP 地址&#xff0c;本文将介绍三种简单的方法&#xff0c;帮助你在 Linux 中轻松找到所需的 IP 地址。 总结 通过上述三种简单的方法&…