简介
最近稍微学了一些RecyclerView
的进阶使用,就做了一个简单的ToDoList
来进行应用。
github地址
https://github.com/generalio/AndroidStudy/tree/main/TaskListDemo
简要功能介绍

实现
activity_main.xml
里面导入布局:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| <?xml version="1.0" encoding="utf-8"?> <LinearLayout 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:id="@+id/main" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView android:id="@+id/rv" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="7"/>
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1">
<Button android:id="@+id/btn_main_addParent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="添加任务" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
|
再布置好一级菜单和二级菜单的两个布局:
item_parent.xml
一级菜单布局:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| <?xml version="1.0" encoding="utf-8"?> <com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_marginTop="20dp" android:layout_marginEnd="20dp" android:layout_marginStart="20dp" app:cardCornerRadius="8dp" app:cardElevation="4dp" app:cardBackgroundColor="#00ffff" app:cardPreventCornerOverlap="true">
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent">
<TextView android:id="@+id/parent_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:text="一级列表title" android:textSize="18dp" android:paddingBottom="10dp" android:paddingTop="10dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
<ImageView android:id="@+id/parent_isExpand" android:layout_width="24dp" android:layout_height="24dp" android:src="@drawable/ic_folder" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toTopOf="parent" android:layout_marginEnd="8dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
|
item_child.xml
二级菜单布局:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <?xml version="1.0" encoding="utf-8"?> <com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_marginTop="20dp" android:layout_marginEnd="20dp" android:layout_marginStart="54dp" app:cardCornerRadius="8dp" app:cardElevation="4dp" app:cardBackgroundColor="#00ffcc" app:cardPreventCornerOverlap="true">
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent">
<TextView android:paddingBottom="10dp" android:paddingTop="10dp" android:id="@+id/child_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:text="二级列表title" android:textSize="18dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
|
然后设置两个数据类分别存放一级菜单信息和二级菜单信息:
TaskInfo.kt
:
1 2 3 4 5 6 7
|
data class TaskInfo(val text: String, val TYPE: Int, val childList: MutableList<ChildInfo>, var isExpand: Boolean)
|
ChildInfo.kt
:
1
| data class ChildInfo(val content: String, val TYPE: Int)
|
然后开始编写RecyclerView
的Adapter
。新建一个TaskRecyclerViewAdapter.kt
,我们这里使用listAdapter
的差分刷新来实现,方便后面的一系列更改数据操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class TaskRecyclerViewAdapter() : ListAdapter<TaskInfo, RecyclerView.ViewHolder>(object : DiffUtil.ItemCallback<TaskInfo>() { override fun areContentsTheSame(oldItem: TaskInfo, newItem: TaskInfo): Boolean { return oldItem == newItem }
override fun areItemsTheSame(oldItem: TaskInfo, newItem: TaskInfo): Boolean { return oldItem == newItem }
}) {
}
|
这里的areContentsTheSame
和areItemsTheSame
两个方法正确写法应该是第一个比较他们的唯一id
是否一样,第二个再比较各个内容是否一样,这里偷个懒。
然后我们的思路是根据传入不同的TYPE
来加载不同的布局,即重写getItemViewType()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| class TaskRecyclerViewAdapter() : ListAdapter<TaskInfo, RecyclerView.ViewHolder>(object : DiffUtil.ItemCallback<TaskInfo>() { override fun areContentsTheSame(oldItem: TaskInfo, newItem: TaskInfo): Boolean { return oldItem == newItem }
override fun areItemsTheSame(oldItem: TaskInfo, newItem: TaskInfo): Boolean { return oldItem == newItem }
}) {
private val TYPE_PARENT = 1 private val TYPE_CHILD = 2
inner class parentViewHolder(view: View): RecyclerView.ViewHolder(view) { val parentTitle: TextView = view.findViewById(R.id.parent_title) val parentIsExpand: ImageView = view.findViewById(R.id.parent_isExpand) }
inner class childViewHolder(view: View) : RecyclerView.ViewHolder(view) { val childTitle: TextView = view.findViewById(R.id.child_title) }
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val task = getItem(position) when(holder) { is parentViewHolder -> { holder.parentTitle.text = task.text if(task.isExpand) { holder.parentIsExpand.setImageResource(R.drawable.ic_expand) } else { holder.parentIsExpand.setImageResource(R.drawable.ic_folder) } }
is childViewHolder -> { holder.childTitle.text = task.text } } }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { if(viewType == TYPE_PARENT) { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_parent, parent, false) return parentViewHolder(view) } else { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_child, parent, false) return childViewHolder(view) } }
override fun getItemViewType(position: Int): Int { return getItem(position).TYPE }
}
|
然后我们自定义一个点击接口,并对点击事件进行反馈,回调给外部进行后续处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| class TaskRecyclerViewAdapter(private val itemClickListener: OnItemClickListener) : ListAdapter<TaskInfo, RecyclerView.ViewHolder>(object : DiffUtil.ItemCallback<TaskInfo>() { override fun areContentsTheSame(oldItem: TaskInfo, newItem: TaskInfo): Boolean { return oldItem == newItem }
override fun areItemsTheSame(oldItem: TaskInfo, newItem: TaskInfo): Boolean { return oldItem == newItem }
}) {
interface OnItemClickListener { fun onExpandClick(position: Int) fun onItemClick(position: Int) }
private val TYPE_PARENT = 1 private val TYPE_CHILD = 2
inner class parentViewHolder(view: View): RecyclerView.ViewHolder(view) { val parentTitle: TextView = view.findViewById(R.id.parent_title) val parentIsExpand: ImageView = view.findViewById(R.id.parent_isExpand) init { parentIsExpand.setOnClickListener { if(getItem(adapterPosition).isExpand) { parentIsExpand.setImageResource(R.drawable.ic_folder) } else { parentIsExpand.setImageResource(R.drawable.ic_expand) } itemClickListener.onExpandClick(adapterPosition) } view.setOnClickListener { itemClickListener.onItemClick(adapterPosition) } } }
inner class childViewHolder(view: View) : RecyclerView.ViewHolder(view) { val childTitle: TextView = view.findViewById(R.id.child_title) }
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val task = getItem(position) when(holder) { is parentViewHolder -> { holder.parentTitle.text = task.text if(task.isExpand) { holder.parentIsExpand.setImageResource(R.drawable.ic_expand) } else { holder.parentIsExpand.setImageResource(R.drawable.ic_folder) } }
is childViewHolder -> { holder.childTitle.text = task.text } } }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { if(viewType == TYPE_PARENT) { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_parent, parent, false) return parentViewHolder(view) } else { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_child, parent, false) return childViewHolder(view) } }
override fun getItemViewType(position: Int): Int { return getItem(position).TYPE }
}
|
然后,回到我们的MainActivity.kt
中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
| class MainActivity : AppCompatActivity(), TaskRecyclerViewAdapter.OnItemClickListener {
lateinit var recyclerview: RecyclerView lateinit var recyclerViewAdapter: TaskRecyclerViewAdapter lateinit var mBtnAddParent: Button private val taskInfoList: MutableList<TaskInfo> = mutableListOf()
@SuppressLint("InflateParams", "MissingInflatedId") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main)
recyclerview = findViewById(R.id.rv) mBtnAddParent = findViewById(R.id.btn_main_addParent)
taskInfoList.add(TaskInfo("1.点下方按钮添加任务", 1, mutableListOf(), false)) taskInfoList.add(TaskInfo("2.单击任务可添加子任务", 1, mutableListOf(), false)) taskInfoList.add(TaskInfo("3.侧滑删除任务", 1, mutableListOf(), false))
recyclerview.layoutManager = LinearLayoutManager(this) recyclerViewAdapter = TaskRecyclerViewAdapter(this) recyclerview.adapter = recyclerViewAdapter recyclerViewAdapter.submitList(taskInfoList.toList())
addHelper()
mBtnAddParent.setOnClickListener { showAddParentDialog() } }
fun addHelper() { val helper = ItemTouchHelper(object : ItemTouchHelper.Callback() { override fun getMovementFlags( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder ): Int { val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN val swipeFlags = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT return makeMovementFlags(dragFlags, swipeFlags) }
override fun onMove( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder ): Boolean { val from = viewHolder.adapterPosition val to = target.adapterPosition if(!taskInfoList[from].isExpand && !taskInfoList[to].isExpand) { Collections.swap(taskInfoList, from, to) recyclerViewAdapter.submitList(taskInfoList.toList()) } return true }
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { val position = viewHolder.adapterPosition folder(position) taskInfoList.removeAt(position) recyclerViewAdapter.submitList(taskInfoList.toList()) }
}) helper.attachToRecyclerView(recyclerview) }
fun showAddParentDialog() { val dialogView = layoutInflater.inflate(R.layout.dialog_change, null) val dialogText: EditText = dialogView.findViewById(R.id.et_change_content) val dialogBuilder = AlertDialog.Builder(this) .setTitle("添加任务") .setView(dialogView) .setPositiveButton("确认") {dialog, which -> addParentTask(dialogText.text.toString()) } .setNegativeButton("取消") {dialog, which -> } dialogBuilder.show() }
fun addParentTask(title: String) { taskInfoList.add(TaskInfo(title, 1, mutableListOf(), false)) recyclerViewAdapter.submitList(taskInfoList.toList()) }
fun addChildTask(position: Int, content: String) { if(taskInfoList[position].isExpand) { taskInfoList[position].childList.add(ChildInfo(content, 2)) val newPosition = position + taskInfoList[position].childList.size taskInfoList.add(newPosition, TaskInfo(content, 2, mutableListOf(), false)) recyclerViewAdapter.submitList(taskInfoList.toList()) } else { taskInfoList[position].childList.add(ChildInfo(content, 2)) recyclerViewAdapter.submitList(taskInfoList.toList()) } }
override fun onExpandClick(position: Int) { if(taskInfoList[position].isExpand) { taskInfoList[position].isExpand = false folder(position) } else { taskInfoList[position].isExpand = true expand(position) } }
override fun onItemClick(position: Int) { showAddChildDialog(position) }
fun showAddChildDialog(position: Int) { val dialogView = layoutInflater.inflate(R.layout.dialog_change, null) val dialogText: EditText = dialogView.findViewById(R.id.et_change_content) val dialogBuilder = AlertDialog.Builder(this) .setTitle("添加子任务") .setView(dialogView) .setPositiveButton("确认") {dialog, which -> addChildTask(position, dialogText.text.toString()) } .setNegativeButton("取消") {dialog, which -> } dialogBuilder.show() }
fun expand(position: Int) { var nowPosition = position for(childInfo in taskInfoList[position].childList) { taskInfoList.add(nowPosition + 1, TaskInfo(childInfo.content, childInfo.TYPE, mutableListOf(), false)) nowPosition++ } recyclerViewAdapter.submitList(taskInfoList.toList()) }
fun folder(position: Int) { for(childInfo in taskInfoList[position].childList) { if(position + 1 < taskInfoList.size) { taskInfoList.removeAt(position + 1) }
} recyclerViewAdapter.submitList(taskInfoList.toList()) } }
|
这样,我们就可以完成一个基本的清单列表了。
RecyclerView的进阶用法------完成一个简易的任务列表