Android 手机便签(三)
创建RecyclerView
image.png在设置标签(Tag)的背景色时遇到的问题:我不能直接设置标签的background,因为这样会使我设置的圆角图案“无效”,所以采用调用backgroundTintList的方式,backgroundTintList中ColorStateList.valueOf()来设置颜色,又因为我们数据表中Tag背景色类型为String而需要的是Int类型,所以需要使用Color.parseColor(todo.tag.bgColor)解析。
*@Description
*@Author PC
*@QQ 1578684787
*/
class TodoAdapter:RecyclerView.Adapter<TodoAdapter.MyViewHolder>() {
private var dataList: List<Todo> = emptyList()
class MyViewHolder(view:View) : RecyclerView.ViewHolder(view) {
private var dateTextView:TextView = itemView.findViewById(R.id.date)
private var priorityImageView: ImageView = itemView.findViewById(R.id.priority)
private var titleTextView: TextView = itemView.findViewById(R.id.todo_title)
private var timeTextView: TextView = itemView.findViewById(R.id.time)
private var tagTextView:TextView = itemView.findViewById(R.id.tag)
//使用静态方法封装创建MyViewHolder的方法
companion object {
fun from(parent:ViewGroup): MyViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater.inflate(R.layout.todo_item_layout,
parent,
false)
return MyViewHolder(view)
}
}
//绑定数据
@SuppressLint("SetTextI18n")
fun bindData(todo:Todo){
//标题
titleTextView.text = todo.title
//标签
tagTextView.text = todo.tag.text
tagTextView.backgroundTintList = ColorStateList
.valueOf(Color.parseColor(todo.tag.bgColor))
//优先级
when(todo.priority){
Priority.HIGH ->{priorityImageView.setImageResource(R.drawable.red_plate)}
Priority.MIDDLE ->{priorityImageView.setImageResource(R.drawable.yellow_plate)}
Priority.LOW ->{priorityImageView.setImageResource(R.drawable.green_plate)}
}
//时间
dateTextView.text = "${todo.date.month}.${todo.date.day}"
timeTextView.text = "${todo.date.year}-${todo.date.month}-${todo.date.day}"
itemView.setOnClickListener {
val action = MainFragmentDirections.actionMainFragmentToDetailFragment(todo)
it.findNavController().navigate(action)
//在Activity以及Fragment中可以直接通过findNavController找到NavController,
// 其他地方则可以通过视图调用findNavController方法
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder.from(parent)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bindData(dataList[position])
}
override fun getItemCount(): Int {
return dataList.size
}
fun setDatas(newData:List<Todo>){
val diffResult = DiffUtil.calculateDiff(TodoDiffUtil(newData,dataList))
dataList = newData
diffResult.dispatchUpdatesTo(this)
}
}
class TodoDiffUtil(
private val newData:List<Todo>,
private val oldData:List<Todo>
) : DiffUtil.Callback() {
override fun getOldListSize(): Int {
return oldData.size
}
override fun getNewListSize(): Int {
return newData.size
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldData[oldItemPosition]
val newItem = newData[newItemPosition]
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldData[oldItemPosition]
val newItem = newData[newItemPosition]
return oldItem == newItem
}
}
setDatas方法为刷新数据方法除了上面提到的需创建DiffUtil的方式,还有一种比较简单的方式,但不建议使用
image.pngnotifyDataSetChanged():强制recyclerView中的所有Item刷新,效率不高。所以这里采用DiffUtil的方式进行刷新,只刷新有变化的内容。
在MainFragment中创建并使用Adaper,用于将recyclerView中的数据传递显示到主页中
private val mAdapter:TodoAdapter by lazy { TodoAdapter() }
//初始化recyclerview
private fun initRecyclerView(){
binding.recyclerView.layoutManager = LinearLayoutManager(
requireContext(),
RecyclerView.VERTICAL,
false)
binding.recyclerView.adapter = mAdapter
//滑动删除
swipeToDelete()
}
添加任务的点击布局
DetailFragment类
点击首页的某个板块进入详情页面,使首页的数据也能传递到详情页中,方便查看和修改。就必须在navigation -> argument ->添加接受数据Custom Parcelable类型。
image.png但是要支持这种类型数据的传递,就必须完善数据表的搭建
image.png image.png image.pngDetailFragment类的全部代码
注意:顶部菜单的visbility设置为gone,使菜单真正的隐藏而不仅仅是视觉效果。 顶部菜单visbility的设置这里对代码中的一些方法作简单的介绍:
initPriorityEvent():点击选择事件的优先级,其中只包含点击事件的触发,而使按钮颜色的改变的具体实现在setPriority(priority: Priority)中
initDateEvent():点击时间选择按钮,弹出日历,并且显示到对应的VIew上。这里需要使用系统的DatePickerDialog()
optionBtnHideAnim():菜单选项按钮的动画设置,旋转消失。
goBack():返回上一页,通常是完成save或delete或update操作后执行。封装着requireActivity.onBackPressed()
initMenuEvent():封装着点击菜单按钮弹出save等以及save、delete操作按钮的具体实现。其中涉及BounceInterpolator()的使用,使菜单弹出时抖动一下。
initData():从主页存在的标签点击进入详情页面,将数据显示到详情页面。
class DetailFragment : Fragment(){
private lateinit var binding:FragmentDetailBinding
//接受传递过来的参数
private val mCurrentTodoArgs:DetailFragmentArgs by navArgs()
//将Args解析出来
private var mCurrentTodo:Todo ?= null
private val mainViewModel: MainViewModel by viewModels()
private val detailViewModel:DetailViewModel by viewModels()
private var mTagData:TagData ? = null
private var lastSelectedTagView:View ? =null
private var mPriority = Priority.LOW
private var mDate = Date(
Calendar.getInstance().get(Calendar.YEAR),
Calendar.getInstance().get(Calendar.MONTH),
Calendar.getInstance().get(Calendar.DAY_OF_MONTH))
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentDetailBinding.inflate(inflater)
mCurrentTodo = mCurrentTodoArgs.currentTodo
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//优先级按钮的点击事件
initPriorityEvent()
//日期的点击事件
initDateEvent()
//顶部菜单点击事件
initMenuEvent()
binding.backBtn.setOnClickListener { goBack() }
//初始化显示的数据
initData()
//save还是update
if (mCurrentTodo != null){
binding.saveBtn.text = "Update"
}else{
binding.deleteBtn.visibility = View.INVISIBLE
}
initAddTagEvent()
//观察tag标签属性
detailViewModel.tagList.observe(viewLifecycleOwner){
//清除容器之前的子视图
binding.tagContainer.removeAllViews()
//改变tag的显示
it.forEach {tagData ->
//创建一个textView显示Tag
TextView(requireContext()).apply {
text = tagData.title
setBackgroundColor(Color.parseColor(tagData.bgColor))
setTextColor(Color.WHITE)
setPadding(dp2x(requireContext(),15),
dp2x(requireContext(),10),
dp2x(requireContext(),15),
dp2x(requireContext(),10))
setOnClickListener {
if (lastSelectedTagView!=null){
lastSelectedTagView?.setBackgroundColor(Color.parseColor(mTagData?.bgColor))
}
mTagData = tagData
lastSelectedTagView = this
this.setBackgroundColor(requireActivity().getColor(R.color.main_purple))
}
binding.tagContainer.addView(this)
}
}
}
}
private fun initAddTagEvent(){
binding.addTagBtn.setOnClickListener {
findNavController().navigate(R.id.action_detailFragment_to_addTagDialogFragment)
}
}
@SuppressLint("SetTextI18n")
private fun initData(){
mCurrentTodo?.let {
//将数据显示到界面上
binding.titleEditText.setText(it.title)
setPriority(it.priority)
binding.descriptionView.setText(it.description)
binding.dateBtn.text = "${it.date.year}.${it.date.month+1}.${it.date.day}"
}
}
private fun setPriority(priority: Priority){
mPriority = priority
binding.priorityhighBtn.setTextColor(requireContext().getColor(R.color.white))
binding.prioritylowBtn.setTextColor(requireContext().getColor(R.color.white))
binding.prioritymiddelBtn.setTextColor(requireContext().getColor(R.color.white))
when(priority){
Priority.LOW -> binding.prioritylowBtn.setTextColor(requireContext().getColor(R.color.main_purple))
Priority.MIDDLE -> binding.prioritymiddelBtn.setTextColor(requireContext().getColor(R.color.main_purple))
Priority.HIGH -> binding.priorityhighBtn.setTextColor(requireContext().getColor(R.color.main_purple))
}
}
private fun goBack(){
//navigation中已经设置从mainfragment -> detailfragment的动画,onBackPressed直接模拟返回
//缺点:无法传递数据
requireActivity().onBackPressed()
}
private fun initMenuEvent(){
binding.menuBtn.setOnClickListener {
binding.optionBtnContainer.visibility = View.VISIBLE
ObjectAnimator.ofFloat(
binding.optionBtnContainer,"rotationX",-90f,0f).apply {
duration = 300
interpolator = BounceInterpolator()
start()
}
}
binding.saveBtn.setOnClickListener {
if (checkInputValid()) {
if (mCurrentTodo==null) {
//创建Todo对象
val TodoData = Todo(
0,
binding.titleEditText.text.toString(),
binding.descriptionView.text.toString(),
mPriority,
mDate,
Tag(mTagData!!.title, mTagData!!.bgColor)
)
//插入数据
mainViewModel.insertTodoData(TodoData)
} else {
mCurrentTodo?.title = binding.titleEditText.text.toString()
mCurrentTodo?.description = binding.descriptionView.text.toString()
mCurrentTodo?.priority = mPriority
mCurrentTodo?.date = mDate
if (mTagData!=null){
mCurrentTodo?.tag = Tag(mTagData!!.title, mTagData!!.bgColor)
}
//更新数据
mainViewModel.updateTodoData(mCurrentTodo!!)
}
optionBtnHideAnim()
goBack()
}else{
Toast.makeText(requireContext(), "有必填项未填写", Toast.LENGTH_LONG).show()
}
}
binding.deleteBtn.setOnClickListener {
mainViewModel.deleteTodoData(mCurrentTodo!!)
optionBtnHideAnim()
goBack()
}
}
private fun checkInputValid():Boolean{
return binding.titleEditText.text!!.isNotEmpty() &&
binding.descriptionView.text.isNotEmpty() &&
mTagData!=null
}
private fun optionBtnHideAnim(){
binding.optionBtnContainer
.animate()
.rotationX(-90f)
.setDuration(300)
.setListener(object :Animator.AnimatorListener{
override fun onAnimationStart(animation: Animator?) {
}
override fun onAnimationEnd(animation: Animator?) {
binding.optionBtnContainer.visibility = View.INVISIBLE
}
override fun onAnimationCancel(animation: Animator?) {
}
override fun onAnimationRepeat(animation: Animator?) {
}
})
}
private fun initPriorityEvent(){
binding.priorityhighBtn.setOnClickListener {
setPriority(Priority.HIGH)
}
binding.prioritymiddelBtn.setOnClickListener {
setPriority(Priority.MIDDLE)
}
binding.prioritylowBtn.setOnClickListener {
setPriority(Priority.LOW)
}
}
private fun initDateEvent() {
binding.dateBtn.setOnClickListener {
//DatePickerDialog弹出日期选项的控件
DatePickerDialog(
requireContext(), object : DatePickerDialog.OnDateSetListener {
@SuppressLint("SetTextI18n")
override fun onDateSet(
p0: DatePicker?,
year: Int,
month: Int,
dayofMonth: Int
) {
mDate.year = year
mDate.month = month
mDate.day = dayofMonth
//将日期显示到dateBtn上
binding.dateBtn.text = "$year-${month+1}-$dayofMonth"
}
},
mDate.year, mDate.month, mDate.day
).show()
}
}
}
MainFragment类
对类中的几个方法做讲解展示:
checkEmptyStatus(dataList:List<Todo>):判断数据库中是否有数据
nitTopTodo(todo: Todo):将数据库中的第一条数据显示到主页头部板块
initRecyclerView():对recyclerView进行配置
swipeToDelete():在主页中向左滑动删除一条信息
mViewModel.todoDataList.observe(viewLifecycleOwner, ...)实时监听数据库的数据变化。相信不少小伙伴会疑惑为什么这里的监听者不再是this(也就是Activity本身,https://juejin.cn/post/6915222252506054663这篇文章有非常准确的讲解,主要与声明周期有关)
class MainFragment : Fragment() {
private lateinit var binding:FragmentMainBinding
private val mViewModel: MainViewModel by viewModels()
private val mAdapter:TodoAdapter by lazy { TodoAdapter() }
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View{
binding = FragmentMainBinding.inflate(inflater)
return binding.root
}
@SuppressLint("ShowToast")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initRecyclerView()
mViewModel.todoDataList.observe(viewLifecycleOwner, Observer {
//检查是否有数据
checkEmptyStatus(it)
//第一条数据显示在头部
if (it.isNotEmpty()){
initTopTodo(it[0])
}
if (it.size > 1){
//把除第一条的数据添加到后面
//包含fromIndex 不包含ToIndex
mAdapter.setDatas(it.subList(0,it.size))
}else{
mAdapter.setDatas(emptyList())
}
//更新Adapter的数据
mAdapter.setDatas(it)
})
binding.addBtn.setOnClickListener{
val action = MainFragmentDirections.actionMainFragmentToDetailFragment(null)
findNavController().navigate(action)
}
binding.TopTaskContainer.setOnClickListener {
//获取第一个数据
val data = mViewModel.todoDataList.value!![0]
val action = MainFragmentDirections.actionMainFragmentToDetailFragment(data)
findNavController().navigate(action)
}
binding.deleteAllBtn.setOnClickListener {
//删除所有todo
mViewModel.deleteAllTodoDatas()
Toast.makeText(requireContext(),"DeleteAll Finished!",Toast.LENGTH_LONG)
}
}
//初始化头部信息
@SuppressLint("SetTextI18n")
private fun initTopTodo(todo: Todo){
binding.topTitle.text = todo.title
binding.topTime.text = "${todo.date.year}.${todo.date.month+1}.${todo.date.day}"
binding.topDescription.text = todo.description
binding.topTag.text = todo.tag.text
when(todo.priority){
Priority.HIGH ->binding.topPriority.setImageResource(R.drawable.red_ball)
Priority.MIDDLE ->binding.topPriority.setImageResource(R.drawable.yellow_ball)
Priority.LOW ->binding.topPriority.setImageResource(R.drawable.green)
}
binding.topTag.background.setTint(Color.parseColor(todo.tag.bgColor))
}
//初始化recyclerview
private fun initRecyclerView(){
binding.recyclerView.layoutManager = LinearLayoutManager(
requireContext(),
RecyclerView.VERTICAL,
false)
binding.recyclerView.adapter = mAdapter
//滑动删除
swipeToDelete()
}
//检查是否显示无数据状态
private fun checkEmptyStatus(dataList:List<Todo>){
if (dataList.isEmpty()){
binding.ivEmptyFace.visibility = View.VISIBLE
binding.ivEmpty.visibility = View.VISIBLE
//隐藏头部视图
binding.TopTaskContainer.visibility = View.INVISIBLE
}else{
binding.ivEmptyFace.visibility = View.INVISIBLE
binding.ivEmpty.visibility = View.INVISIBLE
//显示头部视图
binding.TopTaskContainer.visibility = View.VISIBLE
}
}
/**
* 滑动删除
*/
private fun swipeToDelete(){
val touchHelper = ItemTouchHelper(object :ItemTouchHelper.SimpleCallback(0,ItemTouchHelper.LEFT){
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
//获取滑动的位置
val index = viewHolder.adapterPosition
//将数据从数据源中删除
val data = mViewModel.todoDataList.value!![index]
mViewModel.deleteTodoData(data)
//弹出删除提示信息
Toast.makeText(requireContext(),"Delete Finished!",Toast.LENGTH_LONG)
}
})
//将滑动删除事件绑定到recyclerview
touchHelper.attachToRecyclerView(binding.recyclerView)
}
}
未完待续……