开源地址:todolist卓版本reminder(提醒事项):https://gitee.com/kjiskyz/reminder

请开发一个Android的计划管理APP。要求对计划进行全面的管理,包括列表、查询、订制、编辑和删除。一个计划包括开始时间、结束时间、文字描述、图片、附件、重要程度、完成情况等。APP会提示用户即将开始的计划,超过计划结束时间后要求用户输入完成情况。计划可以提前完成,也可以延期。数据保存在本地的sqlite数据库中,图片和附件以文件的形式保存于手机文件系统中。请合理使用组件进行开发,并要求使用Androidx的组件。开发语言要求使用Kotlin。

目录

计划管理APP

功能

列表功能和数据库实现

数据库实现

列表功能

查询功能

订制功能/新建提醒事项与编辑功能

提醒功能

左滑删除

展示/隐藏已完成

计划管理APP

功能

列表

上拉查询(数据保存在本地的sqlite数据库)

订制

编辑(开始时间、结束时间、文字描述、图片、附件、重要程度、完成情况)

左滑删除

提醒

显示/隐藏已完成

列表功能和数据库实现

数据库实现

定义数据类

使用了@Parcelize注解,这意味着它可以被自动地转换为Parcelable,从而可以在Intent中传递

imageurl和fileurl用于保存图片和附件在手机文件系统中的地址

@Parcelize

data class detail(

var title: String, //标题

val description: String? =null, //描述

var isChecked: Boolean = false, //是否完成

var begindate: Long? = null,

var enddate: Long?=null,

var begintime:Long?=null,

var endtime:Long?=null,

var priority:Int?=3, //默认无优先级

var id:Int?=null,

var imageUrl:String?=null,

var fileUrl:String?=null,

): Parcelable

创建数据库

数据库要实现查询,更新,添加,删除等功能

首先,它们都需要一个Context对象来创建DatabaseHelper,然后通过DatabaseHelper获取SQLiteDatabase对象。然后,它们都会根据需要对数据库进行查询、插入、更新或删除操作。

通过游标cursor实现查询功能,通过添加/覆盖contentvalues的值实现添加和更新功能

列表功能

使用recyclerview进行列表展示,首先定义两种type,第一种为正常的列表展示listitem,第二种为底部footview用于添加新事项,现在适配器的getitemviewtype中定义类型,数据的底部定义为footview。

如果视图类型是TYPE_ITEM,那么就从listitem.xml布局文件中创建一个新的视图;如果视图类型是TYPE_FOOTER,那么就从footview.xml布局文件中创建一个新的视图,并将这个视图传递给FooterViewHolder的构造函数,创建一个FooterViewHolder实例。

override fun getItemViewType(position: Int): Int {

return if (position == DetailList.size) {

TYPE_FOOTER

} else {

TYPE_ITEM

}

}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {

return if (viewType == TYPE_ITEM) {

val view = LayoutInflater.from(parent.context).inflate(R.layout.listitem, parent, false)

ViewHolder(view)

} else {

val view = LayoutInflater.from(parent.context).inflate(R.layout.footview, parent, false)

FooterViewHolder(view)

}

}

在onbindviewholder实现具体逻辑在mainactivity绑定recyclerview

首先获取数据库的所有数据,定义列表为线性布局注册一个用于处理从Activity返回的结果的回调。如果返回的结果码是RESULT_OK,则调用updateRecyclerView()函数更新RecyclerView。创建一个新的Recycleadapter,将查询到的数据、结果处理器和一个函数引用(用于设置FloatingActionButton的可见性)传递给它。

private fun setupRecyclerView() {

val detailList = queryDataFromDatabase(this)

val layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this)

recyclerView.layoutManager = layoutManager

// registerForActivityResult()方法的第一个参数是一个ActivityResultContract对象,这个对象负责创建Intent,

// 当Activity结束时,会将Intent传递给onActivityResult()方法。

resultLauncher =

registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {

if (it.resultCode == RESULT_OK) {

updateRecyclerView()

}

}

// Recycleadapter是自己写的一个类,用于将数据和视图绑定,这里的adapter就是Recycleadapter的一个实例

val adapter =

Recycleadapter(detailList, resultLauncher, ::setFloatingActionButtonVisibility)

recyclerView.adapter = adapter

// ItemTouchHelper是一个辅助类,用于实现RecyclerView的拖拽和滑动删除功能

val itemTouchHelperCallback =

object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {

// onMove()方法用于实现拖拽功能,这里不需要,所以返回false

override fun onMove(

recyclerView: RecyclerView,

viewHolder: RecyclerView.ViewHolder,

target: RecyclerView.ViewHolder

): Boolean {

return false

}

// onSwiped()方法用于实现滑动删除功能

override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {

val position = viewHolder.adapterPosition

detailList.removeAt(position)

recyclerView.adapter!!.notifyItemRemoved(position)

// 删除数据库中的数据

deleteDataFromDatabase(this@MainActivity, position)

}

}

// 将ItemTouchHelper和RecyclerView绑定

val itemTouchHelper = ItemTouchHelper(itemTouchHelperCallback)

itemTouchHelper.attachToRecyclerView(recyclerView)

}

查询功能

使用NestedScrollView将主界面的layout包裹起来,实现上滑搜索设置NestedScrollView的滚动监听。当滚动位置(scrollY)小于等于0时,searchView可见,checkmore按钮不可见。当滚动位置大于100时,searchView不可见,checkmore按钮可见。这样可以在滚动时改变这两个控件的可见性。设置searchView的查询文本监听。当查询文本改变时,调用updateRecyclerView(newText)函数来更新RecyclerView。这样可以实现搜索功能,即根据用户在searchView中输入的文本来过滤RecyclerView中的数据。updateRecyclerView调用setData(filteredList)方法将过滤后的数据设置到适配器中,设置showFooter属性的值为filter是否为空或空字符串,然后调用notifyDataSetChanged()方法通知RecyclerView数据已更改

private fun setupSearchView() {

val nestedScrollView = findViewById(R.id.NestedScrollView)

nestedScrollView.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, _ ->

if (scrollY <= 0) {

searchView.visibility = View.VISIBLE

checkmore.visibility = View.GONE

} else if (scrollY > 100) {

searchView.visibility = View.GONE

checkmore.visibility = View.VISIBLE

}

})

searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {

override fun onQueryTextSubmit(query: String?): Boolean {

return false

}

override fun onQueryTextChange(newText: String?): Boolean {

updateRecyclerView(newText)

return false

}

})

}

private fun updateRecyclerView(filter: String? = null) {

val detailList = queryDataFromDatabase(this)

// 如果filter不为空,就将detailList中的数据过滤,只保留包含filter的数据

// 用于查询功能,不为空就只保存输入框里的内容进行查询

val filteredList = if (filter != null) {

detailList.filter { it.title.contains(filter) }

} else {

detailList

}

(recyclerView.adapter as Recycleadapter).apply {

setData(filteredList)

showFooter = filter.isNullOrEmpty()

notifyDataSetChanged()

}

}

订制功能/新建提醒事项与编辑功能

方法一:在列表最下方有footview,输入标题后点击最右边的图标,会进入到编辑页面;方法二:通过主页面的底部的悬浮按钮添加新事项当点击addbutton时,执行以下操作: 获取addtext中的文本,作为标题。如果标题为空,显示一个Toast消息"标题不能为空"。如果DetailList中存在一个未完成的任务,其标题与输入的标题相同,显示一个Toast消息"标题已存在",并清空addtext。否则,创建一个新的detail对象,将其添加到数据库和DetailList中,通知RecyclerView插入了一个新的item,然后清空addtext。点击事项右侧的图标后(或通过悬浮按钮),跳转到detail activity中,在detail activity中获取到detail实例后进行初始化

如果有标题、开始日期、结束日期、开始时间、结束时间、图片、附件的,则初始化显示通过DatePickerDialog设置开始日期和结束日期的日期选择功能通过TimePickerDialog设置开始时间和结束时间的选择功能通过Spinner设置优先级的选择,首先创建一个array,存放要展示的列表,创建一个ArrayAdapter,用于将数组和spinner绑定,获取用户选择的优先级后更新数据库

// 设置优先级

val priority = findViewById(R.id.priority_spinner)

// 创建一个ArrayAdapter,用于将数组和spinner绑定

val adapter_spinner = ArrayAdapter.createFromResource(

this,

R.array.planets_array,

android.R.layout.simple_spinner_item

)

// 设置下拉列表的风格

adapter_spinner.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)

priority.adapter = adapter_spinner

// 设置优先级的默认值

priority.setSelection(detail?.priority ?: 3)

priority.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {

override fun onItemSelected(

parent: AdapterView<*>,

view: View?, position: Int, id: Long

) {

parent as Spinner

detail?.priority = position // 保存优先级

}

override fun onNothingSelected(parent: AdapterView<*>?) {

}

}

添加图像与图片,添加附件。

用户选择拍照/选择图片,如果用户选择"拍照",会检查应用是否有相机权限。如果没有,会请求相机权限。如果有,会创建一个新的文件用于存储拍照后的图片,然后启动相机应用。拍照后的图片会保存到刚刚创建的文件中。 如果用户选择"选择图片",代码会启动一个用于选择图片的应用。用户可以从这个应用中选择一张图片,然后这个图片的URI会作为结果返回。 当这两个应用结束后,会调用当前Activity的onActivityResult方法,可以在这个方法中获取拍照后的图片或用户选择的图片。当用户点击添加附件按钮时,会创建一个新的Intent,并设置其动作为Intent.ACTION_GET_CONTENT。这意味着这个Intent将用于获取用户选择的文件。然后,设置Intent的类型为"/",这意味着这个Intent可以选择任何类型的文件。最后,使用startActivityForResult方法启动这个Intent,并等待用户选择文件。当用户选择了文件后,会调用当前Activity的onActivityResult方法,可以在这个方法中获取用户选择的文件。在用户选择完照片/拍照/上传完附件后,打开一个输入流,用于读取用户拍摄的照片。 创建一个新的文件,用于保存用户拍摄的照片。打开一个输出流,用于写入用户拍摄的照片。 将输入流的内容复制到输出流,即将用户拍摄的照片写入到文件中。关闭输入流和输出流。

if (requestCode == 1 && resultCode == RESULT_OK) {

val userimage = findViewById(R.id.userimage)

// 从文件中读取图片

userimage.setImageURI(imageUri)

userimage.visibility = View.VISIBLE

// 随机生成文件名

val filename = "userimage${System.currentTimeMillis()}.jpg"

val inputStream = contentResolver.openInputStream(imageUri) //获取图片的输入流

val file = File(filesDir, filename) //创建文件

val outputStream = FileOutputStream(file) //创建输出流

inputStream?.copyTo(outputStream) //将输入流复制到输出流

inputStream?.close() //关闭输入流

outputStream.close() //关闭输出流

// 将文件名保存

val detail = intent.getParcelableExtra("detail")

if (detail != null) {

detail.imageUrl = filename

// 更新数据库

val id = queryIdFromDatabase(this, detail)

updateDatabase(this, detail, id)

}

}

提醒功能

使用worker进行后台处理,提醒事项的即将开始,和结束。首先从数据库中查询所有的计划,然后过滤出即将开始和已经结束的计划。对于每个即将开始的计划,发送一个"你的计划即将开始"的通知;对于每个已经结束的计划,发送一个"你的计划已经结束,请输入完成情况"的通知。在mainactivity启动后台任务

override fun doWork(): Result {

val allPlans = queryDataFromDatabase(applicationContext)

val currenttime = System.currentTimeMillis()

val upcomingPlans = allPlans.filter { plan ->

plan.begindate?.let { it != 0.toLong() && it - currenttime < 86400000 && it > currenttime && !plan.isChecked }

?: false

}

val overPlans = allPlans.filter { plan ->

plan.enddate?.let { it != 0.toLong() && it < currenttime && !plan.isChecked } ?: false

}

upcomingPlans.forEach {

showNotification(

applicationContext,

it,

"你的${it.title}计划即将开始"

)

}

overPlans.forEach {

showNotification(

applicationContext,

it,

"你的${it.title}计划已经结束,请输入完成情况"

)

}

return Result.success()

}

左滑删除

创建一个ItemTouchHelper.SimpleCallback对象,这个对象定义了当用户在RecyclerView的item上执行拖拽或滑动操作时应该执行的操作。当一个item被滑动时,会获取这个item的位置,然后从detailList中移除这个item,通知适配器这个item已经被移除,并从数据库中删除这个item

private fun setupRecyclerView() {

val detailList = queryDataFromDatabase(this)

val layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this)

recyclerView.layoutManager = layoutManager

// ItemTouchHelper是一个辅助类,用于实现RecyclerView的拖拽和滑动删除功能

val itemTouchHelperCallback =

object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {

// onMove()方法用于实现拖拽功能,这里不需要,所以返回false

override fun onMove(

recyclerView: RecyclerView,

viewHolder: RecyclerView.ViewHolder,

target: RecyclerView.ViewHolder

): Boolean {

return false

}

// onSwiped()方法用于实现滑动删除功能

override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {

val position = viewHolder.adapterPosition

detailList.removeAt(position)

recyclerView.adapter!!.notifyItemRemoved(position)

// 删除数据库中的数据

deleteDataFromDatabase(this@MainActivity, position)

}

}

// 将ItemTouchHelper和RecyclerView绑定

val itemTouchHelper = ItemTouchHelper(itemTouchHelperCallback)

itemTouchHelper.attachToRecyclerView(recyclerView)

}

展示/隐藏已完成

当用户点击"showfinished"菜单项时,会切换clickshowfinished的值,然后获取RecyclerView的适配器,并将适配器的setShowFinished属性设置为clickshowfinished的值。然后,根据clickshowfinished的值来更新"showfinished"菜单项的标题。最后,通知适配器数据已更改

// mainactivity

private fun setupCheckMoreButton() {

var clickshowfinished = false

var showFinishedTitle = "显示已完成的任务"

checkmore.setOnClickListener {

val popupMenu = PopupMenu(this, it)

popupMenu.menuInflater.inflate(R.menu.popup_menu, popupMenu.menu)

popupMenu.menu.findItem(R.id.showfinished).title = showFinishedTitle

popupMenu.setOnMenuItemClickListener { menuItem ->

when (menuItem.itemId) {

R.id.showfinished -> {

clickshowfinished = !clickshowfinished

val adapter = recyclerView.adapter as Recycleadapter

adapter.setShowFinished = clickshowfinished

showFinishedTitle =

if (clickshowfinished) "隐藏已完成的任务" else "显示已完成的任务"

adapter.notifyDataSetChanged()

true

}

else -> false

}

}

popupMenu.show()

}

}

//adapter

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {

if (holder is ViewHolder) {

val detail = DetailList[position]

// 默认隐藏已经完成的item,只有点击查看已完成的任务才会显示

if (setShowFinished) {

holder.itemView.visibility = View.VISIBLE

holder.itemView.layoutParams = RecyclerView.LayoutParams(

ViewGroup.LayoutParams.MATCH_PARENT,

ViewGroup.LayoutParams.WRAP_CONTENT

) //设置布局参数,宽度为match_parent,高度为wrap_content

}

文章来源

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: