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

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

  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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值