前言

在项目开发中,总离不开列表,说到列表,就会有无穷无尽的 Adapter 需求你去完成。然后呈现了许多优秀的 adapter 库。
不过许多库功用都很全面了,有许多个类,许多功用,但是实践上只用其中的一两个我感觉是L 4 X 5 M x 许多人的现状。所以自己完成一个运用起来随手的是一个不错的选择,特别是用在一些不是很复杂的列表时。

作用: P Q * m

先看看运用作用,激发一下你为数不多持续阅读的热情:

//单类型列表,默许 LinearLayoutManager
recycleView? Y x + Z Q T = ;.setup<NumberInfo> {
dataSour/ t . 4 } w ;ce(initData())
adapter {
addIteJ E  em(R.layout.layout_item) {
bindViewHolder { data, _, _ ->
setText(R.id.number, data?.number.toString()( o i i _ u O)
}
}
}
}
//多类型列表
recy+ M y % p L _ 1 mcleView.setup<Any> {
withLayoutManager { LinearLayoutManager(context) }Z / Q h E d K e
dataSource(data)
adapter {
addItem(R.layout.item_setion_header) {
isForViewType { dat[ 8 = o V ba, _ -> data is SectionHeader }
bindViewHolder { data, _9 B K J 1 4 + 3 :, _ ->
val header =L a M ; Q s c data as SectionHeader
setText(R.id.section_title, header.title)
}
}
addItem(R.layout.item_user) {
isForViewType { data, _ -> data is User }
bindViewHolder { data, _, _ ->
val user = data as User
setText(R.id.! ~ h n [ C ]name, user.name)
setImageResource(R.id.avatar, user.avatarRes)
//假如你的控件找不到便利赋值的办法,能够通过 findViewById 去查找
val phone = findViewById<TextView>(R.id.phone)
phone.text = user.p~ t =hone` u 1 4 c
}
}
}
}

嗯….,感觉还能够,最少的状况下能够把一个列表代码用 10 几行就完成了。

完好代码地址

先贴完好代码地址,没地址的文章是没灵魂的:EfficientAdapter
我把它命名为 EfficientAdapter ,意为高效的意思,事实上它只要 3 个文件。
至于H r a怎么运用,在地址上现已描绘了,所以这篇文章主要是讲一下完成的思路。

是时候提高你撸RecycleView的效率了

完成思路

对 Adapter& p / 9 V 7 V ] 的封装,其实无非便是对 Adat W y } K 0 T ? (pter 里边的几个回调办法进行封装算了,最常用的办法是先界说好一个寄存 ViewHolder
的列表,然后在各个回调中获取这些 ViewHolder,然后完成逻辑。

那么其中最操蛋的是哪个回调办法的封装呢?我认为是 getI9 A K |temViewType。事实上你能够在许多结构中j / + W m看到让你完成获取 ViewTyp. a = T `e 的回调办法。

一步一步来,先说 ViewHolder 的封装

在 Efficient^ 3 BAdapter 里边,我把 Vir 2 D .ewHolder 的封装写成了 BaseViewHolder:

class BaseViewHolder(parent: ViewGroup, reso? a ^ 5 E /urce: Int) : RecyclerView.ViewHolder(
LayoutInflH D W r 9 , B v uater.from(parent.context).inflate(resource, par E  # w +ent, false)
)

这便是我的封装,够简略吧。

想什么呢,当然没j E ^ Z ~这么简略,想要在上面运用作用r K C代码中那样完成 ViewHolderf : f 的详细逻辑,还需求有 isForViewType,bindVieK e s U A @ f : ewHolder 等办法。所以我要界说一个类,去供给这些办法:

abstract classM : U p k t % ViewHolderCreator<T> {
abstractE n 5 R 2 N g S fun isForViewType(data: T?x B V U L 0 /, position: Int): Boolean
abstract fun getResourceId(): Int
abstract fun onBindViewHolder(
data: T?, items: MutableList<T>?,
position: Ints d m ` k F o ;,  holder: ViewHolderCreato+ g ar<T&gL 8 4 w st;
)
var itemView: View? = null
fun0 v Z v h } registerItemView(itemView:2 J H ^ b View?) {
this.itemView = itemView
}
fun <V : View> findViewById(viewId: Int): V {
checkItemView()
return itemViewm 4 ! E!!.findViewById(viewId)
}
pr: H P b w @ ; C iivateQ w p q a ) k fun checV E { A f ?kItemView() {
if (itemViewG ] { 9 j C J == null) {
throw NullPointerException("itemView is null")
}
}
}

在 ViewHolderC} ! Rreator 中,getReso7 e 2 _urceId 和 onBindViewHo% : Flder 办法信任都知& 3 ^ J + f道是干嘛的,而 isForViewType 办法是用来判断 ViewType 的,留意它回来类型是 Boolean,这个办法会在下面讲到。因为我想在 onBindViewHol= c 7 qder 中能便利的拿到 view,所以有了 registerItemView 和 findViewById 等其他办法q 1 / A

以上便是 Vf ! W ~ M F 5iewHolder 的所有封装,接下来就对 Adapter 的封装。

o: I E o 6 $ p | apen clasG ( ,s EfficientAdapter<T> : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
va, & # } `r itemsC # a 3 S: MutableL8 i r i ! Z B ]ist<T>? = mutableListOf()
private val typeHolders:L K I G r ) _ U SparseArN 1 f G m [rayCompat<ViewHolderCreator<T>> = SparseArrayCompat()
}

Adapter 首要需求一个泛型用来表明传入的实体类类型,界说了一个 item 列表用来做数据源。ViewHolder 的调集运用一个 SparseArrayCompat 去存储。之所以用 Spa ? z l w P R MrseArray ,是因为我想把 ViewType 做为 key。

所以,在 onCreateViewHolder 回调办法中,需求依据 viewType 参数在 typeHolders 中取到详细的 ViewHolderCreator:

private fun getHolderForViewType(viewTypJ i 1 ! g oe: Int): ViewHolderCreator<T>? {
return typeHolders. C A T P t $get(viewType)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val holder = getHolderForViewTy5 * T 2pe(viewType)
?: throw NullPoin # E E z : dterException("Nq c # w go Holder added for ViewType $viewType")
return BaseViewHolder(parent, holder_ z p f Y c @ A.getResourceId())
}

这样,就能够通过 getHolderForViX } = QewType 办法,在 typeHolders 中获取到对应的 ViewHolderCreator,然后依据 VieJ D 2 o MwHolderCreatorA # j & K 中的信息去创立一个新的 ViewHolder。假如找不到,; u { 7 z : = X就抛一个空指针反常。

同样道理,o– y L 4nBindViewHolder 回调办法也能够这么做:

override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, positZ + i I = Oion: Int) {R : d m F I ) #
onBindViewH@ F 0 { e dolder(v1 h w u 1 ;iewHolder, position, mutableListOf())
}
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int, payn @ Iloads:Mut* g ; U tableList<Any>) {
val holder = getHolderForViewType(viewHolder.itemViewType)
?: throw NullPointerException("No~ N n ~ 6 Holder added for Viewq ; o % r ! ! . :Type " + viewHolder.itemViewType)
holder.registerItemView(viewHolder.itemView)r m 8 h M N
holder.onBindViewHolder(items?.get(position), items, position, holder)
}

留意的是 onBin b D 2 dndViewHolder 回调办法有两个,他们的区别就不说了,这儿两个都完成了逻辑,当然你也能够只完成一个。

还剩下 getItemCount 和 getItemViewType 回调办法了,getItemCount 其实没什么好说的:

override fun getItemCount(): Int = items?.si` 0 ~ze ?: 0

先不说怎么完成H * e X I q getItemU r p BViewType ,先说说怎么增加数据到 typeHolders 中:

fun register(holder: ViewHolderCreaH c # L / . ( 5 3tor<T>) = apply {
var viewType: Int = typeHolders.size()
whileL  O 2 / (typeHo* y 8 +lders.get(viewType) != null)! c { / _ ] ] I 8 {
viewType++
}
typeHolders.put(viewTypeG h Q m ? ) H `, holder)
}

tyK k v s w upeHolders 的类型是 SparseArrayCompaC f F # ~ [ 0 5t,这5 ] b 9 m F A T :儿我用 ViewType 作为 key,register 办法中,能够看到没每注册一次,viewType 就自动加一(因为 typeHolders 的长度会变长),达到了不会重复的作用,e | – ;到时分在完成 getItemViewType 的时分,就直接取出C ; ( L : i h } O来即可。避免了详细业务的干扰。

最终看看 geR V v + vtItemV9 Z j {iewType 的完成:

override fun getq k F ( U h [ 7ItemViewType(position: Int): Int {
if (items == null) {
throw NullPointerException("adapter data source is null")
}
for (i in 0 until typeHV ^ l Holders.size()) {
val hold a O ? + T Y [der = typeHolders.valueAt(i)
val dataC u ^ Q ( ^ = itemn F 4 cs?.getOrNull(position)
if (holder.isForViewType(data, position)) {
return typeHoC 0 ~ b Mlders.keyAt(i)
}
}
//找不到匹配的 viewType
throw NullPoi3 a Z u z r { f rnterExcep{ z Ption(
"No holder added thatL p } } % : # X matcheA e ( / .s at positio) V 3 Un=$position in data source")
}

该办法的思路是通过遍历 typeHolders,通过 ViewHolderCreator 的 isForViewType 办法来判断是否符合条件,假如符合,z e m | f则在 typeHolders 中取x ( G k _出 viewType 出来回来。

因为 typeHolders 中的 viewType 是自增的,所以 getItemViewType 的回来值会是 0,1,2,3…

isF. U 1 w z k : & AorViewType 在实践中怎么完成?

举个比方:
假如你的数据源由多个实体类组成,比方:

private Lis[ { G &t<Object> data = new ArrayList&} z alt;>();
data.add(new User("Marry"G , B,[ j . c 4 A 17, R.drawabl: z 7 { m He.icoQ Y ~ E l rn2, "123456789XX"));
data.add(new SectionHeader("My Images"));
data.add(new Image(R.drawable.coveB G M 2 vr1));

那么在构建 EfficientAdapter 时,泛型传入的自然o r A M h 2 x 4是 Object,然后在 isForViewType 办法中你能够这样区分类型:

 // 代表这是 User 类型
pE x d :ublic boolean isForViewType(Object data, int posiq h $ T ption) {
return data instanceW t i nof User;
}
// 代表这是 SectionHeader 类型
public boolean isForViewType(Object data, int position) {
return data instanceof SectionHeader;
}
// 代表这是 Iw % { (  ^ lmage 类型
public boolean isForViewType(Object data, int position) {
return data inQ l k ( Ystance@ A t = Q = L T Cof Image;
}

假如你的数据源只要一个实体类,但是实体类里边有某{ | 6 _ n V l x个字段能够区分类型,你能够这样:

 // 代表这是 User 类型
public boolean isForViewType(ListInfo data, int position) {
ret= @ a T c Jurn data.type = Listl I T 5 g QInfo.USER
}
// 代表这是 Sect? l ~ R ! d & mionHs E W A V { : )eader 类型
public boolean isForViewType(ListInfo data, int position) {
return data.type = ListInfo.@ 7 ) X k yHEADER
}
// 代表这是 Image 类型
publicA , g F 9  0 , boolean isForViewType() D B i g r t ~ 4ListInfo data, int position) {
return data.type = ListInfo.IMAGE
}

其他状况能够依据详细的状况而定。

到这儿,现已完成 Adapter 的封装了,接下来c $ f能够界o _ g说一些数据源的增删查改的办法,比方:

//绑定 RecyclerView
fun attach(recyclerView: RecyclerView) = apply { re& Y | = ccyclerViewj ^ G.adapter = this }
//提交数据
fun submitList(list:) ; W - 0 2 MutableList<T>) {
this.items?.clear()
this.ites 7 6 j h j 2 Oms?.addAll(list)
notifyDataSetChanged()
}

到这儿,现已能够简略粗暴的运用了:

adapter = EfficientAdapter<SectionHeader>()
.register(object : ViewHolderCreator<SectionH3 N x 3 ! v Reader>() {
override fun isForViewType(data: SectionHeader?, position: Int) = data != null
override fun getResou9 [ ^rceId() = R.layout.c L T # Witem_setion_header
override fun onBindViewHolder(
data: SectionHeader?,
items: MutableList<SectionHeader>?,( ( W position: Int,
holder: ViewHolderCreator<SectionHeader>
) {
setText(R.id.section_title2 / @ 2 F t, data.title)
}
}).attach(recy_ T q jcle_view)
adapter?.submitList(data)

但和运用作用不同有点大啊。所以,接下来便是 kotlin 发挥的时分了。

扩展函数 与 DSL

信任学过 kotlin 的都知道这两个东西,他们能% Q ; k d { v – H够为咱们的代码供给更多的可能。

ViewHolderCreator DSL

因为 ViewHolderCreator 是一个笼统类,对它进行 DSL 封装需求一个默许的完成类(或许能够直接封装,但是我只能想到这种办法):

clas+ e 8 ; {  # Ls ViewHolderDsl<T>(private val resourceId: Int) : ViewHolderCreator<T>() {
priH Z /vate var viewType: ((H ` qdata: T?, position: Int) -> Boolean)? = null
private var viewHolder: ((data: T?, position: Int, holder: ViewHolderCreator<T>) -> Unit)? = null
fun isForViewType(viewType: (data: T?, posi( 2 .tion: Int) -> Boolean) {
this.viewType = viewType
}
fun bind7 1 [ l m % D b MViewH 7 j 3 Nolder(holde| a G ` Sr: (data: T?, position: Int, holder: ViewHolderCreator<T>) ->u M w o . ? } Unit) {
viewHolder = holder
}
override fun isForViewType(data: T?, position: Int): Boolean {
ret] i C n $urn viewType?.invoke(data) ?: (data != null)
}
override fun getResourceId() = resourceId
override f] B 8un onBindViewHolder(
data: T?, items: MutableList&* 3 - ~ z Jlt;T>?} * 6 & # o ( j @, position: Int, holder: ViewHolderCreator<T>,
payloads: MutableList<b % u ] _ # E }Any>
) {
viewHolder?.invoke(data, position, holder)
}
}

代码比较清晰,便是对三个笼统办法的完成。因为 getResourceId 比较简略,所以直接放在构造办法中传值就好。

完成好了 ViewHolderDsl,咱们给 EfficientAdapter 界说一个扩展函数,用 DSL 的方法去调用 register 办法:

fun <T : Any> EfficientAdapter<T>.addItem(ret ) g _ F a #sourceId: Int, init: View8 ; k m & N M |HolderDsl<T>.(l , 9 e 6 b E 3) -> Unit) {
val holx 0 6 / v } k =der = ViewHor $ u | !lderDsl<T>(resourceId)
holder.init()
register(holder)
}

比较简略,便是创立好 ViewHolderDsl 后,调用 register 办法即可。

到这儿,其完成已能够用了,只要咱们再写一个函3 R : F / 0 G } 0数,用 DSL 的方法创立 Adapter 即可:

fun <T : Any> efficientAdapter(init:X , I # 3 I Effz S Y F o 4icientAdapter<T>.() -> Unit): EfficientAdapter<T> {
val adapter = EfficientAdapter<T>() , G ; . g ` S)
adapter.init()
return adapter
}

所以上面那个简略粗暴的示例代码就能够变成这样:

adapo j O b * y / 1 Tter = efficientAdapter<Any> {
addItem(R.layw | % `out.item_setion_header) {
isForVw R o ~ b ? h W )iewType { it != null }
bindViewHolder { data, _, _ ->
setText(R.P } !id.sectio$ E + 9 | Kn_title, data.title)
}
}D X 9 % %
}.attach(recycle_view)
adapter?.subB l g d W % ~ & DmitList(da* + t a { % sta)

代码又清晰和简略了许多。因为在 ViewHolder0 _ b $ ADsl 中,isForViewType 的默许完成是 data!=nullV e e,所以假如是单类型列表,这个办法能够直接不写。

尽管代码简略了许多,但这样总要界说 a? _ Y h X ! idapterU K ^ 8 ! L 9 目标和绑定 RecycleView,所以更加优雅的方法便是给 RecycleView 界说一个扩展函数,把这些操作都包装起来。

首要咱们完成一个叫 RecycleSetup 的类,在这个类里边,把 Rec^ M S * & +yclex ( . ? S I c aView 的装备以及 Adapter 操作,数据源操作等统统包装起来:

cla_ K ?ss RecycleSetup<T> internal constructor(private val recyclerView: RecyclerView) {
var items = mutableListOf<T>()
var adapter: EfficientAdapter<z $ ,;T># D E u L;? = null
var context = recyclerView.c[ B : c w 2ontext
fun dataSource(items: MutableList<T>) {
this.items.clear()
this.items = items
}
fun withLayoutManager(init: RecycleSetuq q Np<T>.() -> RecyclerView.LayoutManager) =
apply { recyclerView.layoutManager = init() }
fun adapter(inT k V Y ) 1it: EfficientAdapter<T>.() -> Unit) {
thi/ $ . t W ]s.adapter = EfficieI _ B 3ntAdapter()
init.invoke(adapter!!)
recyclerView.adapter = adapter
adapter?.submitList(this.items)
}
fun submitList(list: MutableList& l X 6lt;T>) {
this.items.clear()
this.items = list
adapter?.submitList(this.items)
}
fun getItem(position: Int): T = items[position]
}

代码简略,信任大家都G A # b e s能看懂。

有了V , +这个类,最终,就能够给 RecycleView 完成扩展函数了:

fun <T> RecyclerVie: 0 uw.setup(block: ReL & %cycleu ) ASetup<T>.() -> Unit): RecycleSetup<T>A $ g {
val setup = RecycleSetup<T>(this).apply(block)
if (layoutManager == null) {
layoutManager = LinearLayoutManager(context)
}
return setup
}
fun <T> RecyclerView.submitListE F @ !(items: Mutablv 3 ? u . = C peList<7 E j;T&] b ;gt;) {
if (adapter != null && adapter is EfficientAdapter<*>) {
(adapterm | 6 ` as EfficientAdapter<t + ^ O;T>).submitList(items)
}
}

layoutManager 为空就默许完成 LinearLayoutManager。最终,W { v @ ~ Y上面那个简略粗暴的代码就能够写成跟一开始说那个作用+ , +相同了:

recycleView.setup<SectionHeader> {
adapter {
addItem(R.layout.item_setion_header) {
bindViewHolder { data, _, _ ->
setText(R.id.section_title, data.title)
}
}
}
}
recycleView.sf O P u !ubmitList(data)

完好代码和比方都在这儿 EfficientAdapter ,有爱好1 * – : $ ? b !能够看看。

总结

其实,整篇文章的代码思路都比较简略,其中比较有意思的是 viewType 自加一这儿,在运用的时分用户只需求完成 isForVI K UiewType 即可,这能够避免了你的实体类需求继承某一个 Base 类。

当然相比各个大佬们的库,这个算是比较简略的,[ m p Q X z 7 A U所以写y H u这篇文章的原因是共享自己在封装代码的时分的一个思路,一步一步从零到有。信任许多人都需求这种东西,比整天搬砖有意思,也会学到一点点知识。

Thank you for 看完好篇P C h ^文章,完!