安卓自定义日历
android kotlin
自定义日历并不是自定义CalendarView,自定义CalendarView需要用到Canvas。这里都是使用最基本的布局元素实现的布局
效果图:
-
使用布局文件创建自定义日历的布局
-
使用GridView实现日历每天的网格
-
两个按钮切换日期
-
显示当前月份
-
-
创建日历中每天的布局
-
实现日期计算算法(计算需要空出几个格子,每天的基本信息之类的)
- Adapter适配器,用于GridView
首先是日历的布局文件内容编写
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Calendar Title -->
<RelativeLayout
android:id="@+id/relativeLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageButton
android:id="@+id/btnPrevDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:src="@drawable/arrow_left2" />
<TextView
android:id="@+id/tvCalendarTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="35dp"
android:layout_toStartOf="@+id/btnNextDate"
android:layout_toEndOf="@+id/btnPrevDate"
android:text="2020-08"
android:textAlignment="center"
android:textSize="24sp" />
<ImageButton
android:id="@+id/btnNextDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_marginTop="20dp"
android:layout_marginEnd="20dp"
android:src="@drawable/arrow_right2" />
</RelativeLayout>
<!-- Calendar Week Name -->
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/relativeLayout">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_horizontal"
android:text="MON"
android:textSize="18sp" />
<!-- 周标题…… -->
</LinearLayout>
<!-- Calendar Days-->
<GridView
android:id="@+id/customCalendarDays"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:numColumns="7"
android:gravity="center"
android:verticalSpacing="5dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout">
</GridView>
</androidx.constraintlayout.widget.ConstraintLayout>
没什么好说的,就是基本的一些布局配置
然后是日历Activity代码部分
class CustomCalendarActivity : AppCompatActivity() {
// 需要用到的变量
private lateinit var binding: CalendarLayoutBinding
private lateinit var calendarAdapter: CalendarAdapter
private var currentDate: Calendar = Calendar.getInstance()
private val sdf = SimpleDateFormat("yyyy-MM", Locale.CHINESE)
private val random: Random = Random()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = CalendarLayoutBinding.inflate(layoutInflater)
setContentView(binding.root)
title = "Custom Calendar"
supportActionBar?.apply {
setDisplayHomeAsUpEnabled(true)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
finish()
return true
}
return super.onOptionsItemSelected(item)
}
private fun updateMonth() {
// 更新日历
}
}
我们需要通过ArrayAdapter实现日历GridView展示日期,那么接下来就来重写一下ArrayAdapter
首先是实体类用来封装渲染日期需要用到的信息
class CalendarDay (val date: Calendar?, val price: Double)
然后是渲染日期单元格需要的布局文件
<!--calendar_empty_day.xml-->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="50dp"
android:layout_height="50dp">
<!--空单元格-->
</androidx.constraintlayout.widget.ConstraintLayout>
<!--calendar_day_layout.xml-->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#DDDDDD">
<!--日期单元格显示的信息-->
<TextView
android:id="@+id/tvDayName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1"
android:textSize="12sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvDayPrice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="$XX.XX"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
最后是Adapter具体的代码
class CalendarAdapter(context: Context, private var data: MutableList<CalendarDay>) :
ArrayAdapter<CalendarDay>(context, R.layout.calendar_day_layout, data) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val itemView: View
val day = getItem(position)
val inflater = LayoutInflater.from(context)
// 如果是空就使用空白布局文件实现填充空单元格
if (day?.date == null) {
itemView = inflater.inflate(R.layout.calendar_empty_day, parent, false)
return itemView
}
// 防止安卓内置的优化机制造成渲染多次同样的内容
itemView = inflater.inflate(R.layout.calendar_day_layout, parent, false)
itemView?.findViewById<TextView>(R.id.tvDayName)?.text = "${day.date.get(Calendar.DAY_OF_MONTH)}"
itemView?.findViewById<TextView>(R.id.tvDayPrice)?.text = "$${day.price}"
return itemView
}
fun updateDate(data: MutableList<CalendarDay>) {
// 更新日历
this.data.clear()
this.data.addAll(data)
notifyDataSetChanged()
}
}
这里提到了安卓优化机制 简单来说就是有一个视图复用的问题,后面也许会专门写一下这个
然后就是修改一下之前写的Activity,实现日期更新之类的功能
-
基本配置
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = CalendarLayoutBinding.inflate(layoutInflater) setContentView(binding.root) calendarAdapter = CalendarAdapter(baseContext, mutableListOf()) updateMonth() title = "Custom Calendar" supportActionBar?.apply { setDisplayHomeAsUpEnabled(true) } binding.btnPrevDate.setOnClickListener { currentDate.add(Calendar.MONTH, -1) updateMonth() } binding.btnNextDate.setOnClickListener { currentDate.add(Calendar.MONTH, 1) updateMonth() } binding.customCalendarDays.apply { adapter = calendarAdapter } }
-
切换日期逻辑
private fun updateMonth() { val tempDate: Calendar = currentDate.clone() as Calendar tempDate.set(Calendar.DAY_OF_MONTH, 1) // 更新标题 binding.tvCalendarTitle.text = sdf.format(tempDate.time) // 更新月份中的每一天 // 填充空日期 val dates = mutableListOf<CalendarDay>() var beforeEmptyCount = tempDate.get(Calendar.DAY_OF_WEEK) beforeEmptyCount -= 1 if (beforeEmptyCount == 0) beforeEmptyCount = 7 for (i in 2..beforeEmptyCount) { dates.add(CalendarDay(null, 0.0)) } do { val price = (random.nextDouble() * 100).roundToInt() / 1.00 dates.add(CalendarDay(tempDate.clone() as Calendar, price)) tempDate.add(Calendar.DAY_OF_MONTH, 1) } while (tempDate.get(Calendar.MONTH) == currentDate.get(Calendar.MONTH)) calendarAdapter.updateDate(dates) }
最后就可以实现自定义日历
分享这篇文章