这次的实现方式完全改了,感觉最初的想法对切换周历模式比较难实现,
现在是把月历和周历 同时生成,动态切换。
![](https://i-blog.csdnimg.cn/blog_migrate/303c5e078ae41ebf50b60d91cb6d63f0.gif)
待优化的:切换的时候 闪动没那么丝滑。
还有另一种实现方案 :
只生成当前月份 和前后月份三组数据,当滑动触发到最边上的月份时生成下一个月或上一个月份的数据,这样可能对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,
)
)
}
}
}
}
})
}