48. Compose自定义绘制日历-2

news2025/1/11 14:53:57

这次的实现方式完全改了,感觉最初的想法对切换周历模式比较难实现,
现在是把月历和周历 同时生成,动态切换。

待优化的:切换的时候 闪动没那么丝滑。

还有另一种实现方案 :
只生成当前月份 和前后月份三组数据,当滑动触发到最边上的月份时生成下一个月或上一个月份的数据,这样可能对pager 的 target page 有挑战 。

1. 数据生成的代码


    /**
     * 生成48个月的数据
     */
    private fun generate48MonthData(generateDataSize: Int = 48) {
        val start = System.currentTimeMillis()
        val monthList = mutableListOf<MonthEntity>()
        val weekList = mutableListOf<WeekEntity>()

        val calendar = Calendar.getInstance()

        val todayCalendar = Calendar.getInstance()
        val todayYear = todayCalendar.get(Calendar.YEAR)
        val todayMonth = todayCalendar.get(Calendar.MONTH)

        repeat(generateDataSize / 2) {
            calendar.add(Calendar.MONTH, if (it == 0) 0 else 1)
            val generateMonthDataPair = generateMonthData(calendar, todayCalendar)

            //月数据
            monthList.add(generateMonthDataPair.first)
            //周数据
            weekList.addAll(generateMonthDataPair.second)
        }

        //回到本月
        calendar[todayYear, todayMonth] = 1

        repeat(generateDataSize / 2) {
            calendar.add(Calendar.MONTH, -1)

            val generateMonthDataPair = generateMonthData(calendar, todayCalendar)



            //月数据
            monthList.add(0, generateMonthDataPair.first)
            //周数据
            weekList.addAll(0, generateMonthDataPair.second)
        }


        _homeUIState.update {
            it.copy(monthEntityList = monthList, weekEntityList = weekList, needScrollPage = generateDataSize/2)
        }



        val end = System.currentTimeMillis()
        monthList.forEach {
            it.monthList.forEach {
                XLogger.d("monthList::: ${it.year}-${it.month+1}-${it.day}")
            }
        }

        weekList.forEach {
            it.weekList.forEachIndexed { index, dayEntity ->
                XLogger.d("weekList::: ${dayEntity.year}-${dayEntity.month+1}-${dayEntity.day}")
            }
        }

        val end2 = System.currentTimeMillis()
        println("====耗时========>${end2 - start}  ${end - start}")

    }

    /**
     * 根据年月日 生成数据
     */
    private fun generateMonthData(
        calendar: Calendar,
        todayCalendar: Calendar
    ): Pair<MonthEntity, MutableList<WeekEntity>> {
        val list = mutableListOf<DayEntity>()
        val year = calendar.get(Calendar.YEAR)
        val month = calendar.get(Calendar.MONTH)

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

        //一周第一天是否为星期天
        val isFirstSunday = calendar.firstDayOfWeek == Calendar.SUNDAY

        for (dayOfMonth in 1..calendar.getActualMaximum(Calendar.DAY_OF_MONTH)) {
            calendar[year, month] = dayOfMonth
            //获取周几
            var weekDay: Int = calendar.get(Calendar.DAY_OF_WEEK)
            //若一周第一天为星期天,则-1
            if (isFirstSunday) {
                println("周天是第一天")
                weekDay -= 1
                if (weekDay == 0) {
                    weekDay = 7
                }
            }

            list.add(
                DayEntity(
                    year = year,
                    month = month,
                    day = dayOfMonth,
                    week = weekDay,
                    isCurrentDay = (year == todayYear && month == todayMonth && dayOfMonth == todayDay),
                    isCurrentMonth = true,
                    isWeekend = weekDay == 6 || weekDay == 7,
                    weekOfYear = calendar.get(Calendar.WEEK_OF_YEAR),
                    color = if (year == todayYear && month == todayMonth && dayOfMonth == todayDay) Color.Red else Color.Black
                )
            )
        }



        if (list.first().week != 1) {
            //回到当月的第一天
            calendar.set(year, month, 1)
            repeat(list.first().week - 1) {
                println("=====>补充前面的数据")
                calendar.add(Calendar.DAY_OF_MONTH, -1)

                var weekDay: Int = calendar.get(Calendar.DAY_OF_WEEK)
                val year1: Int = calendar.get(Calendar.YEAR)
                val month1: Int = calendar.get(Calendar.MONTH)
                val day1: Int = calendar.get(Calendar.DAY_OF_MONTH)
                //若一周第一天为星期天,则-1
                if (isFirstSunday) {
//                    println("周天是第一天")
                    weekDay -= 1
                    if (weekDay == 0) {
                        weekDay = 7
                    }
                }

                list.add(
                    0, DayEntity(
                        year = year1,
                        month = month1,
                        day = day1,
                        week = weekDay,
                        isCurrentDay = false,
                        isCurrentMonth = false,
                        isWeekend = weekDay == 6 || weekDay == 7,
                        weekOfYear = calendar.get(Calendar.WEEK_OF_YEAR),
                        color = Color.LightGray
                    )
                )
            }
        }

        //
        if (list.last().week != 7) {
            //回到本月第一天
            calendar[year, month] = 1
            //回到当月最后一天
            val lastDayOfMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH)

            calendar[year, month] = lastDayOfMonth

            repeat(7 - list.last().week) {
                calendar.add(Calendar.DAY_OF_MONTH, 1)
                var weekDay: Int = calendar.get(Calendar.DAY_OF_WEEK)
                val year1: Int = calendar.get(Calendar.YEAR)
                val month1: Int = calendar.get(Calendar.MONTH)
                val day1: Int = calendar.get(Calendar.DAY_OF_MONTH)
                //若一周第一天为星期天,则-1
                if (isFirstSunday) {
                    weekDay -= 1
                    if (weekDay == 0) {
                        weekDay = 7
                    }
                }

                list.add(
                    DayEntity(
                        year = year1,
                        month = month1,
                        day = day1,
                        week = weekDay,
                        isCurrentDay = false,
                        isCurrentMonth = false,
                        isWeekend = weekDay == 6 || weekDay == 7,
                        weekOfYear = calendar.get(Calendar.WEEK_OF_YEAR),
                        color = Color.LightGray
                    )
                )
                println("添加的数据=====>${year1}-${month1 + 1}-${day1}周:${weekDay}")
            }
        }

//        list.forEach {
//            println("=====>${it.year}-${it.month + 1}-${it.day}周:${it.week}")
//        }

        calendar[year, month] = 1

        val week: MutableList<WeekEntity> = mutableListOf()
        list.chunked(7).forEach {
            week.add(
                WeekEntity(
                    year = year,
                    month = month,
                    weekList = it
                )
            )
        }

        return Pair(MonthEntity(year = year, month = month, monthList = list), week)
    }

    private fun getWeek(calendar:Calendar): Int {
        val isFirstSunday = calendar.firstDayOfWeek == Calendar.SUNDAY
        var weekDay: Int = calendar.get(Calendar.DAY_OF_WEEK)
        //若一周第一天为星期天,则-1
        if (isFirstSunday) {
            println("周天是第一天")
            weekDay -= 1
            if (weekDay == 0) {
                weekDay = 7
            }
        }

        return weekDay
    }

2.绘制

加入了滑动的判断 ,竖直方向滑动 切换日历模式,特殊颜色标注 和 今天的标识。

@OptIn(ExperimentalTextApi::class)
@Composable
fun CalendarPagerContent(
    homeViewModel: HomeViewModel,
    textMeasurerAndTextSize: Pair<TextMeasurer, IntSize>,
    monthEntity: MonthEntity?,
    weekEntity: WeekEntity?,

    ) {
    XLogger.d("CalendarContent======>")
    val homeUiState = homeViewModel.homeUiState.collectAsState().value
    val weekModel = homeUiState.weekModel

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

    val screenWidthDp = LocalConfiguration.current.screenWidthDp
    val clickDay = homeUiState.clickDay

    val height: Dp? = if (weekModel) {
        (screenWidthDp / 7f).dp
    } else {
        monthEntity?.let {
            (monthEntity.monthList.size / 7 * (screenWidthDp / 7f)).dp
        }
    }

    XLogger.d("height=====monthList:${height?.value}")

    Canvas(modifier = Modifier
        .fillMaxWidth()
        .height(height ?: 1.dp)
//        .background(color = Color.Magenta)
        .animateContentSize()
        .pointerInput(Unit) {
            detectTapGestures(onTap = { offset ->
                if (weekModel) {
                    weekEntity?.let {
                        XLogger.d("onTap x y =========>${offset.x} ${offset.y}")
                        val perWidthWithDp = screenWidthDp / 7f
                        val widthIndex = ceil(offset.x / perWidthWithDp.dp.toPx()).toInt()
                        val weekData = weekEntity.weekList[widthIndex - 1]
                        XLogger.d("click========>${weekData.year}-${weekData.month + 1}-${weekData.day}")
                        homeViewModel.dispatch(HomeAction.ItemClick(weekData))
                    }
                } else {
                    monthEntity?.let {
                        XLogger.d("onTap x y =========>${offset.x} ${offset.y}")
                        val perWidthWithDp = screenWidthDp / 7f
                        val widthIndex = ceil(offset.x / perWidthWithDp.dp.toPx()).toInt()
                        val heightIndex = ceil(offset.y / perWidthWithDp.dp.toPx()).toInt()
                        val monthData =
                            monthEntity.monthList[(heightIndex - 1) * 7 + widthIndex - 1]
                        XLogger.d("click========>${monthData.year}-${monthData.month + 1}-${monthData.day}")
                        homeViewModel.dispatch(HomeAction.ItemClick(monthData))
                    }
                }
            })
        }
        .pointerInput(Unit) {
            detectVerticalDragGestures { change, dragAmount ->
                XLogger.d("detectDragGestures=======>change:${change.position.y}  dragAmount:${dragAmount}")
                if (dragAmount >= 20) {
                    homeViewModel.dispatch(HomeAction.SetCalendarModel(false))
                }
                if (dragAmount <= -20) {
                    homeViewModel.dispatch(HomeAction.SetCalendarModel(true))
                }
            }
        }, onDraw = {

        val perWidthWithPadding = this.size.width / 7f

        if (!weekModel) {
            monthEntity?.let {
                monthEntity.monthList.forEachIndexed { index, monthData ->
                    XLogger.d("月历模式")
                    val week = index % 7
                    val rowIndex = index / 7
                    //XLogger.d("每日的数据 ${monthData.year}-${monthData.month+1}-${monthData.day}-${monthData.color}")
                    val textColor =
                        if (monthData.year == clickDay.year && monthData.month == clickDay.month && monthData.day == clickDay.day) {
                            Color.White
                        } else if (monthData.isWeekend && monthData.month == monthEntity.month) {
                            Color.Red
                        } else {
                            monthData.color
                        }

                    val backgroundColor: Color = if (monthData.year == clickDay.year && monthData.month == clickDay.month && monthData.day == clickDay.day) {
                        //点击的画圆背景
                        Color.Blue.copy(0.5f)
                    } else if (monthData.isCurrentDay) {
                        //当天画圆背景
                        Color.LightGray.copy(0.5f)
                    }else {
                        Color.Transparent
                    }



                    drawCircle(
                        color = backgroundColor,
                        radius = (perWidthWithPadding - 2 * paddingPx) / 2f,
                        center = Offset(
                            week * perWidthWithPadding + paddingPx + perWidthWithPadding / 2f,
                            rowIndex * perWidthWithPadding - paddingPx + perWidthWithPadding / 2f
                        ),
                    )

                    drawText(
                        textMeasurer = textMeasurer,
                        text = "${monthData.day}",
                        size = Size(
                            perWidthWithPadding - 2 * paddingPx,
                            perWidthWithPadding - 2 * paddingPx
                        ),
                        topLeft = Offset(
                            week * perWidthWithPadding,
                            rowIndex * perWidthWithPadding
                                    //定位到中间位置
                                    + perWidthWithPadding * 0.5f
                                    //减去文字的高度
                                    - textSize.height / 2f
                        ),
                        style = TextStyle(
                            textAlign = TextAlign.Center,
                            color = textColor,
                            fontSize = 14.sp,
                            fontWeight = FontWeight.Medium,
                        )
                    )

                    if(monthData.isCurrentDay){
                        //今天的背景
                        val todayRadius = (perWidthWithPadding - 2 * paddingPx)/8f
                        drawCircle(
                            color = Color.White,
                            radius =todayRadius,
                            center = Offset(
                                week * perWidthWithPadding + perWidthWithPadding * 0.75f + todayRadius,
                                rowIndex * perWidthWithPadding +todayRadius
                            ),
                        )
                        //今天的文字 大小是0.75倍的宽度
                        drawText(
                            textMeasurer = textMeasurer,
                            text = "今",
                            size = Size(
                                (perWidthWithPadding - 2 * paddingPx)/4f,
                                (perWidthWithPadding - 2 * paddingPx)/4f
                            ),
                            topLeft = Offset(
                                week * perWidthWithPadding + perWidthWithPadding*0.75f,
                                rowIndex * perWidthWithPadding
                            ),
                            style = TextStyle(
                                textAlign = TextAlign.Center,
                                color = Color.Magenta,
                                fontSize = 12.sp,
                                fontWeight = FontWeight.SemiBold,
                            )
                        )
                    }
                }
            }

        } else {
            XLogger.d("周历模式")
            weekEntity?.let {
                weekEntity.weekList.forEachIndexed { index, dayEntity ->
                    XLogger.d("周历模式weekList")
                    //一行 当前的日期
                    //XLogger.d("每日的数据 ${monthData.year}-${monthData.month+1}-${monthData.day}-${monthData.color}")
                    val textColor = if (dayEntity.year == clickDay.year && dayEntity.month == clickDay.month && dayEntity.day == clickDay.day) {
                        Color.White
                    } else if (dayEntity.isWeekend && dayEntity.month == weekEntity.month) {
                        Color.Red
                    } else {
                        dayEntity.color
                    }

                    val backgroundColor: Color = if (dayEntity.year == clickDay.year && dayEntity.month == clickDay.month && dayEntity.day == clickDay.day) {
                        //点击的画圆背景
                        Color.Blue.copy(0.5f)
                    } else if (dayEntity.isCurrentDay) {
                        //当天画圆背景
                        Color.LightGray.copy(0.5f)
                    }else {
                        Color.Transparent
                    }

                    drawCircle(
                        color =backgroundColor,
                        radius = (perWidthWithPadding - 2 * paddingPx) / 2f,
                        center = Offset(
                            index * perWidthWithPadding + paddingPx + perWidthWithPadding / 2f,
                            perWidthWithPadding / 2f
                        ),
                    )

                    drawText(
                        textMeasurer = textMeasurer,
                        text = "${dayEntity.day}",
                        size = Size(
                            perWidthWithPadding - 2 * paddingPx,
                            perWidthWithPadding - 2 * paddingPx
                        ),
                        topLeft = Offset(
                            index * perWidthWithPadding,
                            perWidthWithPadding * 0.5f
                                    //减去文字的高度
                                    - textSize.height / 2f
                        ),
                        style = TextStyle(
                            textAlign = TextAlign.Center,
                            color = textColor,
                            fontSize = 14.sp,
                            fontWeight = FontWeight.Medium,
                        )
                    )

                    if(dayEntity.isCurrentDay){
                        //今天的背景
                        val todayRadius = (perWidthWithPadding - 2 * paddingPx)/8f
                        drawCircle(
                            color = Color.White,
                            radius =todayRadius,
                            center = Offset(
                                index * perWidthWithPadding + perWidthWithPadding * 0.75f + todayRadius,
                                todayRadius
                            ),
                        )
                        //今天的文字 大小是0.75倍的宽度
                        drawText(
                            textMeasurer = textMeasurer,
                            text = "今",
                            size = Size(
                                (perWidthWithPadding - 2 * paddingPx)/4f,
                                (perWidthWithPadding - 2 * paddingPx)/4f
                            ),
                            topLeft = Offset(
                                index * perWidthWithPadding + perWidthWithPadding*0.75f,
                                0f
                            ),
                            style = TextStyle(
                                textAlign = TextAlign.Center,
                                color = Color.Magenta,
                                fontSize = 12.sp,
                                fontWeight = FontWeight.SemiBold,
                            )
                        )
                    }
                }
            }
        }
    })
}

github地址

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

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

相关文章

HTML5网页设计小案例:逸仙园茶馆招聘启事网页的设计

前言&#xff1a; 今天分享的逸仙园茶馆招聘启事网页的设计是本专栏的第一篇博客&#xff0c;也是我学习了几个小时知识点后写的实战小案例。我有个想法&#xff0c;想以逸仙园茶馆为灵感不断优化改进代码与想法设计一套与茶叶有关的的精美网页 逸仙园茶馆招聘启事网页的设计案…

【动态规划上分复盘】这是你熟悉的地下城游戏吗?

欢迎 前言一、动态规划五步曲二、地下城游戏题目分析思路&#xff1a;动态规划具体代码如下 总结 前言 本文讲解关于动态规划思路的两道题目。 一、动态规划五步曲 1.确定状态表示&#xff08;确定dp数组的含义&#xff09;2.确定状态转移方程&#xff08;确定dp的递推公式&a…

NumPy实现逻辑回归

说明&#xff1a;数据集 ex2data1.txt是吴恩达机器学习的作业的数据集。 # -*-coding:utf-8-*- import matplotlib.pyplot as plt import numpy as np import pandas as pdclass Logitstic_Regression:def __init__(self, learning_rate0.01, num_iterations75000, threshold0.…

# 技术架构演进之路

技术架构演进之路 文章目录 技术架构演进之路单机架构应用数据分离架构应用服务集群架构读写分离架构冷热分离架构垂直分库架构微服务架构容器编排技术互联网架构 单机架构 简介应用和服务公用一台服务器出现原因出现在互联网早期,访问量比较小,单机足以满足需求.架构工作原理…

本地新项目推送至gitlab仓库

1. gitlab上新建一个空白项目 gitlab上点击new project按钮&#xff0c;新建一个项目 新建空白项目 项目名称与本地新建项目名称相同&#xff0c;其余根据具体需要选择 2. 初始化本地仓库并commit项目 进入本地项目根目录下&#xff0c;右击 git bash here打开命令窗口 初始化…

java代码审查过关的一次总结

**1.for循环中的逻辑都抽出一个方法并且把重要逻辑抽出一个方法 2.参数比较多合并成一个对象 3.避免没必要的if else 例如if判断能直接return就return,避免没必要的else 4.检查代码中是否包含适当的注释&#xff0c;解释代码的目的、实现细节和注意事项。 5.代码格式和命名规…

百炼智能发布垂直模型“爱迪生”,B2B行业的AIGC大潮来了

&#xff08;图片来源&#xff1a;Pixels&#xff09; AIGC终于来到B2B行业&#xff0c;企业服务AGI时代已拉开帷幕。 数科星球原创 作者丨苑晶 编辑丨大兔 百炼智能是一家专注B2B行业的智能营销企业。在过去&#xff0c;该行业经历了大数据、人工智能时代的洗礼。随着行业对数…

jupyter中如何管理内核

1、jupyter notebook如何和已有的虚拟环境关联起来&#xff1a; 如果在电脑中某个conda的虚拟环境中已经安装了jupyter&#xff0c;其他虚拟环境想要作为内核在jupyter中使用&#xff0c;分为两个步骤&#xff1a; 第一步&#xff1a;在没有jupyter的环境中下载ipykernel&…

E: 无法修正错误,因为您要求某些软件包保持现状,就是它们破坏了软件包间的依赖关系。

T&#xff1a;what Y&#xff1a;why W&#xff1a;how Y $ sudo apt-get install ros-noetic-gazebo-ros 正在读取软件包列表... 完成 正在分析软件包的依赖关系树 正在读取状态信息... 完成 有一些软件包无法被安装。如果您用的是 unstable 发行版&#xff0…

可信数据库大会,不见不散!

由中国信息通信研究院、中国通信标准化协会指导&#xff0c;中国通信标准化协会大数据技术标准推进委员会&#xff08;CCSA TC601&#xff09;、InfoQ 极客传媒联合主办的 2023 可信数据库发展大会将于今日在北京国际会议中心隆重召开。 本届大会以“自主 创新 引领”为主题…

【线段树】P6492 [COCI2010-2011#6] STEP

P6492 [COCI2010-2011#6] STEP - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题意&#xff1a; 思路&#xff1a; 要维护区间最长子串&#xff0c;就需要维护左起最长子串和右起最长子串 要维护这两者&#xff0c;就得维护区间两端的种类 Code&#xff1a; #include <…

Spark数据倾斜优化-AQE Skewed Join

AQE处理SkewedJoin的原理 Spark Adaptive Query Execution &#xff0c; 简称 Spark AQE&#xff0c;总体思想是动态优化和修改 stage 的物理执行计划。利用执行结束的上游 stage 的统计信息&#xff08;主要是数据量和记录数&#xff09;&#xff0c;来优化下游 stage 的物理…

记一次自建靶场域渗透过程

为方便您的阅读&#xff0c;可点击下方蓝色字体&#xff0c;进行跳转↓↓↓ 01 环境搭建02 外网突破03 权限提升并维持&#xff08;1&#xff09;获取 meterpreter 会话&#xff08;2&#xff09;尝试开启远程桌面&#xff08;3&#xff09;Msf 派生 Cobalt Strike shell&#…

TinyStories: How Small Can Language Models Be and Still Speak Coherent English?

本文是LLM系列的文章之一&#xff0c;针对《TinyStories: How Small Can Language Models Be and Still Speak Coherent English?》的翻译。 TinyStories&#xff1a;语言模型能有多小&#xff0c;还能说连贯的英语&#xff1f; 摘要1 引言2 TinyStories数据集的描述2.1 Tiny…

<td>标签内的文字内容换行显示

修改前&#xff1a; 修改后&#xff1a; 代码&#xff1a; table tr td{ word-WRAP: break-word }

CI-持续集成 — 软件工业“流水线”技术实现

1 概述 持续集成&#xff08;Continuous Integration&#xff09;是一种软件开发实践。在本系列文章的前一章节已经对其背景及理论体系进行了介绍。本小节则承接前面提出的理论构想进行具体的技术实现。 《Google软件测试之道》: "每天&#xff0c;Google都要测试和发…

(模拟) 463. 岛屿的周长 ——【Leetcode每日一题】

❓ 463. 岛屿的周长 难度&#xff1a;简单 给定一个 row x col 的二维网格地图 grid &#xff0c;其中&#xff1a;grid[i][j] 1 表示陆地&#xff0c;grid[i][j] 0 表示水域。 网格中的格子 水平和垂直 方向相连&#xff08;对角线方向不相连&#xff09;。整个网格被水完…

前端Vue自定义可自由滚动精美tabs选项卡标签栏标题栏 可设置背景颜色,

前端Vue自定义可自由滚动精美tabs选项卡标签栏标题栏 可设置背景颜色&#xff0c; 下载完整代码请访问uni-app插件市场地址&#xff1a;https://ext.dcloud.net.cn/plugin?id13313 效果图如下&#xff1a; # cc-scrollTag #### 使用方法 使用方法 <!-- tabChange: tab选…

Vue指令--v-bind、v-model、v-on

常见指令--v-bind/model/on 指令&#xff1a;HTML标签中带有v-前缀的特殊属性&#xff0c;不同指令具有不同含义。例如&#xff1a;v-if&#xff0c;v-for..常见指令 指令 作用 v-bind为HTML标签绑定属性值&#xff0c;如设置href、css样式等v-model在表单元素上创建双向数据…

【网络原理之二】网络层的IP协议、数据链路层的以太协议和MTU

网络层协议IP协议IP协议格式IP地址IPV4IPV6 IP地址规则-地址管理路由选择 数据链路层协议以太网协议协议格式 MTU(最大传输单元)MTU对IP协议的影响MTU对UDP协议的影响MTU对TCP协议的影响 开放性问题 网络层协议 IP协议 IP协议格式 4位版本号(version)&#xff1a;指定IP协议的…