简单思路:
1.头部的左右切换和日期显示是卸载布局中的
2.日历的头部
3.每一个方格的白色背景
4.显示日历
5.显示标记点
自定义view `
class MyCalendarView : View {
private val mContext: Context
private var mWidth = 0
//当前年月
private var nowYear = 0
private var nowMonth = 0
//是否是当前月份
private var isNowMonth = true
//当前年份
var showYear = 0
//当前月份
var showMonth = 0
//当前天
private var showDay = 0
//当前选择的天
private var selectDay = 0
//上一年份
private var lastYear = 0
//上月
private var lastMonth = 0
//下一年
private var nextYear = 0
//下月
private var nextMonth = 0
//日期的宽高
private var rowHeight = 0
private var columnWidth = 0
//当前月份第一天是星期几
private var firstDayInMonth = 0
//行数
var row = 0
//列数
var column = 0
//文字画笔
private var textPaint: Paint
//背景画笔
private var bgPaint: Paint
//标记点画笔
private var pointPaint: Paint
//头部高度
private var titleHeight = 0
//白色背景方块画笔
private var whiteBgPaint: Paint
//间隙
private var gap = 0
//背景块行数
private var gapRow = 0
//背景块列数
private var gapColunm = 0
//头部背景画笔
private var titleBgPaint: Paint
private val days = mutableListOf<DayEnity>()
private val weeks = mutableListOf<String>("日", "一", "二", "三", "四", "五", "六")
//控制标记点的数据
private var pointData = mutableListOf<MonthlyDataListBean>()
//点击的x y坐标
private var downX = 0f
private var downY = 0f
//抬起的x y 坐标
private var touchIndexX = 0
private var touchIndexY = 0
//回调监听
private var mCallback: ((String) -> Unit)? = null
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, style: Int) : super(context, attrs, style) {
this.mContext = context
titleHeight = DisplayUtils.dp2px(context, 40f)
gap = DisplayUtils.dp2px(context, 1f)
textPaint = Paint()
textPaint.isAntiAlias = true
textPaint.style = Paint.Style.FILL
textPaint.textSize = DisplayUtils.sp2px(mContext, 16f).toFloat()
bgPaint = Paint()
bgPaint.isAntiAlias = true
bgPaint.color = ContextCompat.getColor(context, R.color.blue_9ac)
pointPaint = Paint()
pointPaint.isAntiAlias = true
pointPaint.style = Paint.Style.FILL
pointPaint.color = ContextCompat.getColor(context, R.color.red_151)
titleBgPaint = Paint()
titleBgPaint.isAntiAlias = true
titleBgPaint.style = Paint.Style.FILL
titleBgPaint.color = ContextCompat.getColor(context, R.color.gray_f5)
whiteBgPaint = Paint()
whiteBgPaint = Paint()
whiteBgPaint.isAntiAlias = true
whiteBgPaint.style = Paint.Style.FILL
whiteBgPaint.color = ContextCompat.getColor(context, R.color.white)
initDays()
}
//初始化日期
private fun initDays() {
val calendar = Calendar.getInstance()
calendar.timeZone = TimeZone.getTimeZone("GMT+8:00")
showYear = calendar.get(Calendar.YEAR)
showMonth = calendar.get(Calendar.MONTH)
nowYear = calendar.get(Calendar.YEAR)
nowMonth = calendar.get(Calendar.MONTH)
selectDay = calendar.get(Calendar.DAY_OF_MONTH)
showDay = calendar.get(Calendar.DAY_OF_MONTH)
// Log.e("TAG", "showDay----------->$showDay")
lastYear = showYear
lastMonth = showMonth - 1
nextYear = showYear
nextMonth = showMonth + 1
firstDayInMonth = CalendarUtil.getFirstDayOfMonth(showYear, showMonth)
// Log.e("TAG", "showYear-----$showYear------showMonth------$showMonth------$firstDayInMonth")
days.addAll(CalendarUtil.getDays(showYear, showMonth))
days.forEachIndexed { index, dayEnity ->
if (showDay == dayEnity.day) {
// Log.e("TAG","index----------->$index")
touchIndexX = (index + firstDayInMonth) / 7
touchIndexY = (index + firstDayInMonth - 2) % 7
}
}
for (i in 0 until days.size) {
if (showDay == days[i].day) {
// Log.e("TAG","index----------->$index")
touchIndexX = ((i + firstDayInMonth - 1) % 7)
touchIndexY = (i + firstDayInMonth - 1) / 7 + 1
}
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
//行高
// rowHeight = (resources.displayMetrics.density * 40).toInt()
rowHeight = MeasureSpec.getSize(widthMeasureSpec - gap * 8) / 7
//列宽
columnWidth = (MeasureSpec.getSize(widthMeasureSpec) - gap * 8) / 7
mWidth = MeasureSpec.getSize(widthMeasureSpec)
//根据天数和每月第一天是周几计算一共需要几列展示
val h = (days.size + firstDayInMonth - 1) / 7 + 1
setMeasuredDimension(
MeasureSpec.getSize(widthMeasureSpec),
h * columnWidth + (h + 1) * gap+titleHeight
)
Log.e("TAG", "设置高度")
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//绘制灰色背景
canvas.drawRect(0f, 0f, mWidth.toFloat(), mWidth.toFloat(), titleBgPaint)
//绘制头部
drawTitle(canvas)
//绘制背景块
drawBgRect(canvas)
//绘制日期
drwaDays(canvas)
}
private fun drawTitle(canvas: Canvas) {
for (i in 0 until 7) {
val weekText = weeks[i]
if(weekText=="日"||weekText=="六"){
textPaint.color = ContextCompat.getColor(mContext,R.color.color_EC7DB7)
}else{
textPaint.color = Color.BLACK
}
val rect = Rect()
textPaint.getTextBounds(weekText, 0, weekText.length, rect)
val textY = (titleHeight / 2 - (textPaint.ascent() + textPaint.descent()) / 2)
val textX = columnWidth * i + (columnWidth - textPaint.measureText(weekText)) / 2
canvas.drawText(weekText, textX, textY, textPaint)
}
}
private fun drawBgRect(canvas: Canvas) {
gapRow = (days.size + firstDayInMonth - 1) / 7 + 1
gapColunm = 7
Log.e("TAG", "行数------------->$gapRow")
val rect = RectF()
for (i in 0 until gapRow) {
for (j in 0 until gapColunm) {
rect.left = (gap * (j + 1) + columnWidth * j).toFloat()
rect.top = (gap * (i + 1) + titleHeight + columnWidth * i).toFloat()
rect.right = (gap * (j + 1) + columnWidth * (j + 1)).toFloat()
rect.bottom = (gap * (i + 1) + columnWidth * (i + 1) + titleHeight).toFloat()
canvas.drawRoundRect(rect, 10f, 10f, whiteBgPaint)
}
}
}
private fun drwaDays(canvas: Canvas) {
for (i in 0 until days.size) {
val day = days[i].day
val dayText = day.toString()
column = (i + firstDayInMonth - 1) / 7
row = ((i + firstDayInMonth - 1) % 7)
val textWidth = textPaint.measureText(dayText)
val rect = Rect()
textPaint.getTextBounds(dayText, 0, dayText.length, rect)
val textHeight = rect.bottom - rect.top
val textX = (columnWidth * row) + (columnWidth - textWidth) / 2 + gap * (row + 1)
val textY =
(rowHeight * column + titleHeight) + (rowHeight + textHeight) / 2 + gap * (column + 1)
if (row == touchIndexX && (column + 1) == touchIndexY) {
//绘制背景
val bgRect = RectF()
bgRect.left = ((columnWidth * row) + gap * (row + 1)).toFloat()
bgRect.top = ((rowHeight * column + titleHeight) + gap * (column + 1)).toFloat()
bgRect.right = ((columnWidth * row) + gap * (row + 1) + columnWidth).toFloat()
bgRect.bottom =
((rowHeight * column + titleHeight) + gap * (column + 1) + rowHeight).toFloat()
canvas.drawRoundRect(bgRect, 10f, 10f, bgPaint)
}
if (days[i].day == showDay && isNowMonth) {//如果是当天
textPaint.color = ContextCompat.getColor(mContext, R.color.blue_007)
} else if (days[i].month == showMonth) {//如果是当月
textPaint.color = Color.BLACK
} else {
textPaint.color = Color.GRAY
}
canvas.drawText(dayText, textX, textY.toFloat(), textPaint)
if (pointData.size > 0 && i < pointData.size) {
val pointX = columnWidth * row + columnWidth / 2 + gap * (row + 1)
val pointY =
(rowHeight * column + titleHeight) + (rowHeight + textHeight) / 2 + DisplayUtils.dp2px(
mContext,
10f
) + gap * (column + 1)
if (pointData[i].state == 0) {
//绘制点
pointPaint.color = ContextCompat.getColor(mContext, R.color.blue_007)
canvas.drawCircle(pointX.toFloat(), pointY.toFloat(), 5f, pointPaint)
} else if (pointData[i].state == 1) {
//绘制点
pointPaint.color = ContextCompat.getColor(mContext, R.color.red_151)
canvas.drawCircle(pointX.toFloat(), pointY.toFloat(), 5f, pointPaint)
}
} else {
val pointX = columnWidth * row + columnWidth / 2
val pointY =
(rowHeight * column + titleHeight) + (rowHeight + textHeight) / 2 + DisplayUtils.dp2px(
mContext,
10f
)
pointPaint.color = Color.TRANSPARENT
canvas.drawCircle(pointX.toFloat(), pointY.toFloat(), 5f, pointPaint)
}
}
}
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
downX = event.x
downY = event.y
}
MotionEvent.ACTION_MOVE -> {
}
MotionEvent.ACTION_UP -> {
val upX = event.x
val upY = event.y
touchIndexX = (upX / columnWidth).toInt()
touchIndexY = if (upY > 0 && upY < titleHeight) {
0
} else {
((upY - titleHeight) / rowHeight + 1).toInt()
}
// Log.e("TAG", "$touchIndexX----------$touchIndexY")
val index = (touchIndexY - 1) * 7 + touchIndexX
Log.e("TAG", "index---------------$index")
if (index >= 0 && index < days.size) {
selectDay = days[index].day
}
if (touchIndexX >= 0 && touchIndexX <= 7 && touchIndexY >= 1 && touchIndexY <= 6) {
val count = (touchIndexY - 1) * 7 + touchIndexX - firstDayInMonth + 1
if (count >= 0 && count < days.size) {
selectDay = days.get(count).day
if (mCallback != null) {
mCallback?.let { it("$showYear-${showMonth + 1}-$selectDay") }
}
invalidate()
}
}
}
}
return true
}
//上一个月
fun toLastMonth() {
if (showMonth == 0) {//当年1月,上一月则为12月,年份减一
showMonth = 11
showYear--
} else {
showMonth--
}
days.clear()
days.addAll(CalendarUtil.getDays(showYear, showMonth))
isNowMonth = nowYear == showYear && nowMonth == showMonth
firstDayInMonth = CalendarUtil.getFirstDayOfMonth(showYear, showMonth)
touchIndexX = -1
touchIndexY = -1
invalidate()
}
//下一个月
fun toNextMonth() {
if (showMonth == 11) {//当年12月,下一月则为1月,年份减加一
showMonth = 0
showYear++
} else {
showMonth++
}
days.clear()
days.addAll(CalendarUtil.getDays(showYear, showMonth))
isNowMonth = nowYear == showYear && nowMonth == showMonth
firstDayInMonth = CalendarUtil.getFirstDayOfMonth(showYear, showMonth)
touchIndexX = -1
touchIndexY = -1
// invalidate()
requestLayout()
}
//设置标记点
fun setPointData(datas: MutableList<MonthlyDataListBean>) {
pointData.clear()
pointData.addAll(datas)
requestLayout()
// invalidate()
}
//点击日历回调监听
fun setCallback(callbak: (String) -> Unit) {
this.mCallback = callbak
}
}
工具类
object CalendarUtil {
//获取当月第一天是星期几
fun getFirstDayOfMonth(showYear: Int, showMonth: Int): Int {
val calendar = Calendar.getInstance()
calendar.set(showYear, showMonth, 1)
Calendar.JANUARY
// calendar.set(Calendar.DAY_OF_MONTH,1)
// Log.e("TAG","----------->${calendar.get(Calendar.DAY_OF_MONTH)}")
// Log.e("TAG","----------->${calendar.get(Calendar.DAY_OF_YEAR)}")
return calendar.get(Calendar.DAY_OF_WEEK)
}
//获取当前月份有多少天
fun checkDays(showYear: Int, showMonth: Int): Int {
var day = 0
day = if (showYear % 4 == 0 && showYear % 100 != 0 || showYear % 400 == 0) {
29
} else {
28
}
when (showMonth) {
1, 3, 5, 7, 8, 12 -> {
return 31
}
4, 6, 9, 11 -> {
return 30
}
2 -> {
return day
}
}
return 0
}
//获取当前年月天数集合
fun getDays(showYear: Int, showMonth: Int): MutableList<DayEnity> {
val days = mutableListOf<DayEnity>()
val count = checkDays(showYear, showMonth+1)
for (i in 0 until count) {
val entity = DayEnity(showYear, showMonth, i + 1)
days.add(entity)
}
return days
}
//判断是否是当天日期
fun isDateTody(dateString:String):Boolean{
val sdf = SimpleDateFormat("yyyy-MM-dd")
try {
val inputDate = sdf.parse(dateString)
val inputCalendar = Calendar.getInstance()
inputCalendar.setTime(inputDate)
val todayCalendar = Calendar.getInstance()
return (inputCalendar.get(Calendar.YEAR) == todayCalendar.get(Calendar.YEAR))
&& (inputCalendar.get(Calendar.MONTH) == todayCalendar.get(Calendar.MONTH))
&& (inputCalendar.get(Calendar.DAY_OF_MONTH) == todayCalendar.get(Calendar.DAY_OF_MONTH))
}catch (e:Exception){
return false
}
}
}