目录
前言
由于只想单纯的实现一个日历,因此整体比较简单,没有过多的代码,更方便捋清楚实现日历的逻辑
效果展示
实现思路
首先日历控件可以左右滑动因此我们选择ViewPager来作为最外层容器,然后在顶上加上周日到周六的文字标题
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_weight="1"
android:textSize="15sp"
android:textColor="@color/black"
android:padding="5dp"
android:gravity="center"
android:text="日"
android:layout_width="0dp"
android:layout_height="wrap_content"/>
<TextView
android:layout_weight="1"
android:textSize="15sp"
android:gravity="center"
android:textColor="@color/black"
android:padding="5dp"
android:text="一"
android:layout_width="0dp"
android:layout_height="wrap_content"/>
<TextView
android:layout_weight="1"
android:textSize="15sp"
android:gravity="center"
android:textColor="@color/black"
android:padding="5dp"
android:text="二"
android:layout_width="0dp"
android:layout_height="wrap_content"/>
<TextView
android:layout_weight="1"
android:textSize="15sp"
android:gravity="center"
android:textColor="@color/black"
android:padding="5dp"
android:text="三"
android:layout_width="0dp"
android:layout_height="wrap_content"/>
<TextView
android:layout_weight="1"
android:textSize="15sp"
android:gravity="center"
android:textColor="@color/black"
android:padding="5dp"
android:text="四"
android:layout_width="0dp"
android:layout_height="wrap_content"/>
<TextView
android:layout_weight="1"
android:textSize="15sp"
android:gravity="center"
android:textColor="@color/black"
android:padding="5dp"
android:text="五"
android:layout_width="0dp"
android:layout_height="wrap_content"/>
<TextView
android:layout_weight="1"
android:textSize="15sp"
android:gravity="center"
android:textColor="@color/black"
android:padding="5dp"
android:text="六"
android:layout_width="0dp"
android:layout_height="wrap_content"/>
</LinearLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/vpContainer"
android:layout_width="match_parent"
android:layout_height="330dp"/>
</LinearLayout>
然后展示具体日期的可以看做是一个七列多行的网格
因此我们可以使用GridView来展示日期,这里我用的是自定义的自适应高度的GridView
class WrapGridView : GridView {
constructor(context: Context?) : this(context, null)
constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val heightSpec = if (layoutParams.height == LayoutParams.WRAP_CONTENT) {
MeasureSpec.makeMeasureSpec(Int.MAX_VALUE shr 2, MeasureSpec.AT_MOST)
} else {
heightMeasureSpec
}
// 这几行代码比较重要
super.onMeasure(widthMeasureSpec, heightSpec)
}
}
然后就是展示日期部分了,展示日期的时候我们只需要知道展示的月份有多少天和第一天是星期几即可,每月前几天的空白可以用空数据顶替
/**
* 获取第一天为星期几
*/
private fun getMonthOneDayWeek(): Int {
val a: Calendar = Calendar.getInstance()
a.set(Calendar.YEAR, year)
a.set(Calendar.MONTH, month)
a.set(Calendar.DATE, 1) //把日期设置为当月第一天
return a.get(Calendar.DAY_OF_WEEK)
}
/**
* 获取当月有几天
*/
private fun getMonthMaxDay(): Int {
val a: Calendar = Calendar.getInstance()
a.set(Calendar.YEAR, year)
a.set(Calendar.MONTH, month)
return a.getActualMaximum(Calendar.DAY_OF_MONTH)
}
val data = ArrayList<DateBean>()
//获取第一天是星期几然后计算出需要填充的空白数据,这里使用0作为空白数据,展示的时候判断并显示空
repeat(getMonthOneDayWeek() - 1){
//填充空白的
data.add(DateBean(0,0,0))
}
//填充日期数据
repeat(getMonthMaxDay()){
data.add(DateBean(year,month,it + 1))
}
这是日期实体类
data class DateBean(var year:Int,var month:Int,var day:Int)
最后的话就是关于添加多少页展示数据的处理了,这里我设置的当前月之前会固定有200页(也就是200个月),当前月之后默认有5页(5个月),当往后滑动页面的时候会动态的添加新的页面
private fun initListener(){
vpContainer.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
currentPos = position
fragments[currentPos].arguments?.let {
val currentPageYear = it.getInt(CalendarFragment.YEAR) //当前页的年
val currentPageMonth = it.getInt(CalendarFragment.MONTH) //当前页的月
//展示的月需要加1(因为系统中的月是从0开始的)
onPageChangedCallBack?.onPageChanged(currentPageYear,currentPageMonth + 1)
}
//刷新下当前的数据
fragments[currentPos].refreshData()
if(currentPos + 3 > fragments.size){
addNextFragment()
}
}
})
}
/**
* 动态添加后面的日历
*/
private fun addNextFragment() {
fragments[fragments.size-1].arguments?.let {
//获取展示的最小的年和月
val minYear = it.getInt(CalendarFragment.YEAR)
val minMonth = it.getInt(CalendarFragment.MONTH)
//动态加两个
for(i in minMonth + 1 until minMonth + 2){
var month = i
var year = minYear
if(i > 11){
month = i - 12
year = minYear + 1
}
val fragment = CalendarFragment(object :OnDateSelectCallBack{
override fun onDateSelect(year: Int, month: Int, day: Int) {
onDateSelectCallBack?.onDateSelect(year,month,day)
}
})
val arguments = Bundle()
arguments.putInt(CalendarFragment.YEAR,year)
arguments.putInt(CalendarFragment.MONTH,month)
fragment.arguments = arguments
fragments.add(fragment)
}
fragmentAdapter?.notifyDataSetChanged()
}
}
更加详细的代码请参考源码