背景
Android开发过程中经常使用到RecyclerView来展示列表形式的信息。我们需要定义一个Adapter来适配数据与UI展示,同时还有定义ui对应的ViewHolder。如果每个列表形式的界面都要定义这样的一套类的话,那么使用RecyclerView变得有些繁琐。为了解决这个问题,本文介绍了如何定义一个通用的Adapter和ViewHolder。
定义BaseItem
BaseItem 是抽象类,用于item数据绑定。BaseItem定义了一个bind抽象方法用于数据与ui的绑定。Adapter也有个bind方法进行数据与ui的绑定,BaseItem的bind方法相当于具体到某一条数据的绑定动作。还有一个viewType的field定义了layout id,Adapter会通过这个viewType来inflate item view。所以当我们需要一个类型的item view的时候,我们只需要继承BaseItem实现bind方法和使用Layout id覆写viewType字段就可以了。BaseItem 中还保存了一个与它绑定的viewHolder,这个viewHolder是最后一次与它绑定的viewHolder。但是viewHolder绑定的BaseItem不一定是它本身,有可能是其他的BaseItem。因为viewHolder是被recyclerView重用的。通过代码我们可以知道availableHolder代表的是双向绑定成功的viewHolder,我们可以通过availableHolder来更新item ui。
abstract class BaseItem {
internal var viewHolder: BaseViewHolder? = null
val availableHolder: BaseViewHolder?
get() {
return if (viewHolder?.baseItem == this)
viewHolder
else
null
}
abstract val viewType: Int
abstract fun bind(holder: BaseViewHolder, position: Int)
fun getStableId() = NO_ID
}
定义BaseViewHolder
BaseViewHolder从RecyclerView.ViewHolder继承过来的并实现了LayoutContainer。它的主要任务是RecyclerView.ViewHolder赋予的,即查找并持有view。通过LayoutContainer的实现我们可以避免手动定义view和查找view了。第二个任务是与BaseItem绑定,实现BaseItem与BaseViewHolder的双向绑定。通过双向绑定,我们可以方便的进行BaseItem与BaseViewHolder的互相查找操作。可以认为BaseItem是数据,通过数据查找有效的BaseViewHolder后就可以进行item UI更新。由于这种更新没有触发Adapter的notifyDataChange,所以这种更新是高效的item 局部更新。
class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), LayoutContainer {
var baseItem: BaseItem? = null
override val containerView: View?
get() = itemView
}
定义BaseDataSource
BaseDataSource用于提供RecyclerView显示的数据。因为我们要定义通用的Adapter,所以其中的集合定义也重新封装成了BaseDataSource。这样我们可以根据具体的情况选择不同的数据集合类而又不需要定义新的adapter类。它主要提供了两个方法,get方法用于返回index对应的数据,size方法返回集合的大小。
abstract class BaseDataSource<T : BaseItem> {
var attachedAdapter: BaseAdapter<T>? = null
abstract fun get(index: Int): T
abstract fun size(): Int
}
定义BaseAdapter
BaseAdapter的实现比较简单,可以认为它把上面定义的BaseItem、BaseViewHolder、BaseDataSource组合到一起工作。主要关注下onBindViewHolder方法的实现,在这个方法中对BaseViewHolder与BaseItem进行了双向绑定,并且将绑定动作传递给BaseItem的绑定方法,由BaseItem执行具体的ui更新。
class BaseAdapter<out T : BaseAdapter.BaseItem>(private val dataSource: BaseDataSource<T>) : RecyclerView.Adapter<BaseAdapter.BaseViewHolder>() {
init {
dataSource.attachedAdapter = this
}
override fun getItemViewType(position: Int) = dataSource.get(position).viewType
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = BaseViewHolder(LayoutInflater.from(parent.context).inflate(viewType, parent, false))
override fun getItemCount() = dataSource.size()
override fun getItemId(position: Int) = dataSource.get(position).getStableId()
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
val item = dataSource.get(position)
item.viewHolder = holder
holder.baseItem = item
item.bind(holder, position)
}
}
如何使用
1.定义BaseItem的子类用于保存item的数据与view现实更新
class MenuItem( val title: String, callback : (MenuItem) -> Unit) : BaseAdapter.BaseItem() {
override val viewType: Int
get() = R.layout.item_menu
private val eventListener = View.OnClickListener {
callback.invoke(this)
}
override fun bind(holder: BaseAdapter.BaseViewHolder, position: Int) {
holder.itemView.itemTitleView.text = title
holder.itemView.setOnClickListener(eventListener)
}
}
2.定义BaseDataSource子类提供数据集合的实现。
class ListDataSource<T: BaseAdapter.BaseItem>: BaseAdapter.BaseDataSource<T>(){
private val list = mutableListOf<T>()
override fun get(index: Int) = list[index]
override fun size() = list.size
fun add(item:T){
list.add(item)
}
}
3.添加BaseItem到BaseDataSource并通知BaseAdapter显示数据
class MainActivity : AppCompatActivity() {
private val dataSource = ListDataSource<BaseAdapter.BaseItem>()
private val adapter = BaseAdapter(dataSource)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initView()
initData()
}
private fun initView() {
recyclerView.adapter = adapter
}
private fun initData() {
dataSource.add(MenuItem("test group data source") {
//响应item的click事件
})
adapter.notifyDataSetChanged()
}
总结
通过定义通用的Adapter和Holder,我们在开发新的列表界面时可以只针对BaseItem的实现类做增量开发。比如A界面使用了BaseItem1类型列表项,B界面也要展示BaseItem1类型的列表项,那么我们可以直接使用A界面定义的BaseItem1.如果B界面还需要展示BaseItem2类型的列表项,那么我们只需要定义BaseItem2就可以了。Adapter和ViewHolder等都不需要再定义新的类了。DataSource的实现如果没有特殊的要求,我们也可以采用一个简单实现ListDataSource。
GIT
https://github.com/mjlong123123/CommonAdapter/releases/tag/0.0.1
我的公众号已经开通,公众号会同步发布。
欢迎关注我的公众号