无名之辈的Android之路

Android 手机便签(三)

2021-08-18  本文已影响0人  搬码人

创建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.png

notifyDataSetChanged():强制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.png

DetailFragment类的全部代码

这里对代码中的一些方法作简单的介绍:
initPriorityEvent():点击选择事件的优先级,其中只包含点击事件的触发,而使按钮颜色的改变的具体实现在setPriority(priority: Priority)中
initDateEvent():点击时间选择按钮,弹出日历,并且显示到对应的VIew上。这里需要使用系统的DatePickerDialog()
optionBtnHideAnim():菜单选项按钮的动画设置,旋转消失。
goBack():返回上一页,通常是完成save或delete或update操作后执行。封装着requireActivity.onBackPressed()
initMenuEvent():封装着点击菜单按钮弹出save等以及save、delete操作按钮的具体实现。其中涉及BounceInterpolator()的使用,使菜单弹出时抖动一下。
initData():从主页存在的标签点击进入详情页面,将数据显示到详情页面。

注意:顶部菜单的visbility设置为gone,使菜单真正的隐藏而不仅仅是视觉效果。 顶部菜单visbility的设置
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)
    }

}

未完待续……

完整代码

上一篇下一篇

猜你喜欢

热点阅读