单层RecycleView实现列表的展开与折叠操作
- 目标需求
- Activity的创建
- Model的创建
- Holder的创建
- Adapter的创建
- 效果展示
- 总结
目标需求
在Android开发中使用单层的RecycleView实现:一、单击主目录展开其子目录列表,再次单击关闭主目录列表;二、单击子目录,显示子目录的内容或者多子目录进行下载等其他操作。
Activity的创建
我们只需要对创建一个MainActivity,对功能进行一个简单的展示即可。MainActivity主要包含:
1、recycLayoutManager 定义一个布局管理器,用来对recycleview的布局进行约束;
2、mListData 定义一个List用来承载数据;
3、myAdapter 定义一个MyRecycAdapter,自定义的Adapter是关键,后面将详细介绍
4、最后还要定义一个RecycleView
当点击事件发生时,我们要根据点击目录的类型进行判断,可以为两大类:
1、当点击对象为主目录时,需要判断当下的主目录对象是否展开。如果展开,点击下,则需要关闭该目录,则将其对应的子目录从mListData中删除;反之,点击下,怎需要展开该目录,则将其对应的子目录在mListData的指定位置添加,然后更新数据;
2、当点击对象为子目录时,需要判断当下的子目录是否被选中,如果选中,点击下,则需要取消选中;反之,点击下,则需要确定选中。
在这里插入MainActivity的主要代码片
class MainActivity : AppCompatActivity() {
val TAG = "MainActivity"
var recycLayoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
var mListData = ArrayList<Any>()
var myAdapter : MyRecycAdapter ?= null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView_type.layoutManager = recycLayoutManager
initData()
myAdapter = MyRecycAdapter(mListData)
recyclerView_type.adapter = myAdapter
myAdapter!!.onItemClickListener = object : MyRecycAdapter.OnItemClickListener {
override fun onItemClick(obj: Any, position: Int) {
if (obj is MainCategory){
val mainCategory = obj as MainCategory
if (mainCategory.isShown!!){
mainCategory.isShown = false
val children = mainCategory.children
Log.e(TAG, "onItemClick: ${children.toString()}" )
mListData.removeAll(children!!)
myAdapter!!.notifyDataSetChanged()
}else{
mainCategory.isShown = true
val children = mainCategory.children
mListData.addAll(position+1, children!!)
myAdapter!!.notifyDataSetChanged()
}
}else if (obj is ChildCategory){
val childCategory = obj as ChildCategory
childCategory.isSelected = !childCategory.isSelected!!
if (childCategory.isSelected!!){
Toast.makeText(baseContext, childCategory.toString(), Toast.LENGTH_SHORT).show()
}
}
}
};
}
}
Model的创建
我们需要创建主目录和子目录对应的model,这些model有助于建立起来主目录与子目录的联系,主目录方面主要包含以下四个参数:
1、 id,主目录的编号,用来标记子目录中的ParentId;
2、name,主目录的名称,用来展示主目录的内容;
3、isShown,主目录是否展开,用来判断主目录是否展开
4、children,主目录对应的子目录,用来点击主目录的时候,将其对应的子目录展开
class MainCategory {
var id : String ?= null
var name : String ?= null
var isShown : Boolean ?= null
var children : ArrayList<ChildCategory> ?= null
constructor() {}
constructor(id : String, name : String, isShown : Boolean, children : ArrayList<ChildCategory>){
this.id = id
this.name = name
this.isShown = isShown
this.children = children
}
}
子目录对应的参数如下:
1、id,子目录编号
2、name,子目录的名字
3、content,子目录的内容,用来简单描述子目录
4、parentId,子目录对应主目录的ID,用来寻找到其父目录
5;isSelected,子目录是否被选中,选中的情况下,将选中的icon展示出来。
class ChildCategory {
var id : String ?= null
var name : String ?= null
var content : String ?= null
var parentId : String ?= null
var isSelected : Boolean ?= null
constructor(){}
constructor(id: String?, name: String?, content: String?, parentId: String?, isSelected : Boolean?) {
this.id = id
this.name = name
this.content = content
this.parentId = parentId
this.isSelected = isSelected
}
override fun toString(): String {
return "ChildCategory(id=$id, name=$name, content=$content, parentId=$parentId, isSelected=$isSelected)"
}
}
Holder的创建
因为主目录与子目录的布局不同,Holder的创建显得格外的重要,主目录和子目录分别对应着不同的Holder。但是两者又不同完全割裂开来,因为在Adapter中要添加一个泛型类型。为了解决此问题,我们首先创建一个BaseHolder让它简单继承ViewHolder,代码如下:
open class BaseViewHolder(itemView: View) : ViewHolder(itemView) {}
让后让MainCategoryHolder和ChildCategoryHolder分别继承BaseHolder,这样我们就可以在Adapter的泛型类型中只写BaseHolder即可,代码如下:
class MainCategoryHolder(itemView: View) : BaseViewHolder(itemView) {
init {
var tv_maincategory: TextView = itemView.findViewById(R.id.tv_mainCategory)
}
}
class ChildCategoryHolder(itemView: View) : BaseViewHolder(itemView) {
init {
var tv_childcategory: TextView = itemView.findViewById(R.id.tv_childCategory)
var img_selected: ImageView = itemView.findViewById(R.id.img_selected)
}
}
在MainCategoryHolder和ChildCategoryHolder中可以将main_item.xml和child_item.xml文件分别联系起来,进行实例化;格外注意,这三个类是在Adapter中以静态类的形式存在的。
Adapter的创建
接下来到了本文中最为重要的一个环节了,Adapter类的创建。我们需要自定义一个Adapter用来适配。因为存在不同类型的Holder,因此我们首先定义两个常数,用来区别两种类型的Holder:
1、val TYPE_MAINCATEGORY: Int = 0;
2、val TYPE_CHILDCATEGORY: Int = 1;
然后,我们需要复写getItemViewType()方法,让它根据不同View类型,返回不同的参数,当它为MainCategory则返回TYPE_MAINCATEGORY;反之,则返回TYPE_CHILDCATEGORY,代码如下:
override fun getItemViewType(position: Int): Int {
super.getItemViewType(position)
val any = mDataList[position]
if (any is MainCategory) {
return TYPE_MAINCATEGORY
} else if (any is ChildCategory) {
return TYPE_CHILDCATEGORY
} else {
Log.e(TAG, "getItemViewType is error!")
return -1
}
}
然后就是根据不同类型的ItemView创建不同类型的Holder,这里就要复写onCreateViewHolder(),根据不同类型的viewType,分别创建不同的View,不同得View对应着不同类型的XML文件,然后将View传进Holder里面,返回。
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
when (viewType) {
TYPE_MAINCATEGORY -> {
var view = LayoutInflater.from(parent.context)
.inflate(R.layout.maincategory_item, parent, false)
return MainCategoryHolder(view)
}
TYPE_CHILDCATEGORY -> {
var view = LayoutInflater.from(parent.context)
.inflate(R.layout.childcategory_item, parent, false)
return ChildCategoryHolder(view)
}
else -> {
return null!!
}
}
}
接下来就是绑定ViewHolder,将Model中的数据与View中的对象进行一对一的绑定,如果是MainCategoryHolder,则其XML文件中对应的的控件只有一个TextView,将TextView中的内容更新为Model中的name内容(也可为Model中的其他内容);若为ChildCategoryHolder的时候,其对应的XML文件中存在TextView与ImageView两个控件,我们不仅需要将TextView中的内容与Model中的数据对应起来,还需要根据Model的isSelected数据来判断此时ImageView是否应该展露出来。最为关键的是为了将自定义Adapter中的数据传递出去,我们还需要在自定义的Adapter中定义一个接口,和其对应的set方法。
fun SetOnItemClickListener(onItemClick: OnItemClickListener) {
this.onItemClickListener = onItemClickListener
}
public interface OnItemClickListener {
fun onItemClick(obj: Any, position: Int);
}
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
if (holder is MainCategoryHolder) {
val mainCategoryHolder = holder as MainCategoryHolder
val mainCategory = mDataList[position] as MainCategory
mainCategoryHolder.itemView.tv_mainCategory.text = mainCategory.name
mainCategoryHolder.itemView.setOnClickListener (View.OnClickListener {
if (onItemClickListener != null){
onItemClickListener?.onItemClick(mainCategory, position)
}
})
} else if (holder is ChildCategoryHolder) {
val childCategoryHolder = holder as ChildCategoryHolder
val childCategory = mDataList[position] as ChildCategory
childCategoryHolder.itemView.tv_childCategory.text = childCategory.name
if (childCategory.isSelected!!){
childCategoryHolder.itemView.img_selected.visibility = View.VISIBLE
}else{
childCategoryHolder.itemView.img_selected.visibility = View.INVISIBLE
}
childCategoryHolder.itemView.setOnClickListener(View.OnClickListener {
if (onItemClickListener != null) {
onItemClickListener?.onItemClick(childCategory, position)
}
if (childCategory.isSelected!!){
childCategoryHolder.itemView.img_selected.visibility = View.VISIBLE
}else{
childCategoryHolder.itemView.img_selected.visibility = View.INVISIBLE
}
})
} else {
}
}
效果展示
图片:
总结
RecycleView是一个Android开发中非常重要的一个插件,这里简单介绍一下其在多级目录中的使用。如果您觉得有用欢迎点赞、收藏、赞赏,您的鼓励是我前进的动力!需要完整Demo,评论区留言,留下邮箱,并支付五元辛苦费。未经同意,不得转载!