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

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

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

还有另一种实现方案 :
只生成当前月份 和前后月份三组数据,当滑动触发到最边上的月份时生成下一个月或上一个月份的数据,这样可能对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地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值