自定义RecyclerView的adapter实现二级列表
图片大于5MB,CSDN不让上传,使用github链接,如果看不到请使用科学上网
https://github.com/nanjolnoSat/PersonalProject/blob/recyclerexpandableadapter/Recyclerexpanableadapter/pic/pic1.gif
源码
- 必要方法
- getItemViewType的实现
- getItemCount的实现
- onCreateViewHolder的实现
- onBindViewHolder的实现
- demo
- 优化
- 二级列表的悬浮功能
必要方法
抽一个base出来,因为不可能每次需要这个功能就把相同代码编写一遍。先提供必要的方法,再思考怎么完善方法的细节。
typealias OnParentClickListener = (parentPosition: Int) -> Unit
typealias OnChildClickListener = (parentPosition: Int, childPosition: Int) -> Unitabstract class BaseRecyclerExpandableAdapter<PARENT_VH : BaseRecyclerExpandableAdapter.BaseViewHolder, CHILD_VH : BaseRecyclerExpandableAdapter.BaseViewHolder> :RecyclerView.Adapter<BaseRecyclerExpandableAdapter.BaseViewHolder>() {companion object {const val DEFAULT_VIEW_TYPE = 0}private var onParentClickListener: OnParentClickListener? = nullprivate var onChildClickListener: OnChildClickListener? = null// 记录不需要显示child list的列表private val hideChildListParentPositionList = ArrayList<Int>()final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {}// 创建parent的ViewHolderprotected abstract fun onCreateParentViewHolder(parent: ViewGroup, viewType: Int): PARENT_VH// 创建child的ViewHolderprotected abstract fun onCreateChildViewHolder(parent: ViewGroup, viewType: Int): CHILD_VHfinal override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {}// 当在onBindViewHolder获取到的view type是parent view type的时候调用protected abstract fun onBindParentViewHolder(viewHolder: PARENT_VH, parentPosition: Int, isDisplayedChildList: Boolean)// 当在onBindViewHolder获取到的view type是child view type的时候调用protected abstract fun onBindChildViewHolder(viewHolder: CHILD_VH,parentPosition: Int,childPosition: Int)final override fun getItemCount(): Int {}// 获取parent countprotected abstract fun getParentCount(): Int// 根据parent position获取child countprotected abstract fun getChildCountFromParent(parentPosition: Int): Intfinal override fun getItemViewType(position: Int): Int {}// 生成parent view type,这里会调用getParentViewType,子类可以根据需要去实现private fun obtainParentViewType(parentPosition: Int): Int {}protected open fun getParentViewType(parentPosition: Int) = DEFAULT_VIEW_TYPE// 生成child view type,这里会调用getChildViewType,子类可以根据需要去实现private fun obtainChildViewType(parentPosition: Int, childPosition: Int): Int {}protected open fun getChildViewType(parentPosition: Int, childPosition: Int) = DEFAULT_VIEW_TYPE// 根据parent position判断child list是否显示protected fun isDisplayedChildList(parentPosition: Int) =hideChildListParentPositionList.contains(parentPosition).not()fun setOnParentClickListener(onParentClickListener: OnParentClickListener) {this.onParentClickListener = onParentClickListener}fun setOnChildClickListener(onChildClickListener: OnChildClickListener){this.onChildClickListener = onChildClickListener}abstract class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
}
getItemViewType的实现
上面的getParentViewType和getChildViewType都是返回0,现在不要想为什么可以这样,接下来会优先实现这两个方法。因为这两个方法是比较重要的,如果这两个方法没有实现,很多方法的细节都不太好写。
getItemViewType:先实现如何判断是parent view type还是child view type。只有先把这个实现了,才能进一步实现自定义parent view type和child view type。
companion object {const val PARENT_VIEW_TYPE = 0const val CHILD_VIEW_TYPE = 10000
}final override fun getItemViewType(position: Int): Int {var positionCounter = -1for (parentPosition in 0 until getParentCount()) {positionCounter++// 如果拿到的position与positionCounter相等,则是一个parent view typeif (position == positionCounter) {return obtainParentViewType(parentPosition)}// 如果不是,并且child list display,则看看是不是一个child view typeif (isChildListDisplay(parentPosition)) {val childCount = getChildCountFromParent(parentPosition)// 如果position小于等于counter+child count,则说明这个view type是一个child view type,直接计算出child positionif (position <= positionCounter + childCount ) {// 这里需要-1是因为count不可能是一个为0的数字,所以需要-1才能得到正确的positionreturn obtainChildViewType(parentPosition, position - positionCounter - 1)}positionCounter += childCount}}throw IllegalArgumentException("unknow view type for this position:$position")
}// 这里先简单粗暴地用0和10000分别代表parent view type和child view type,我的第一个版本还真就是这样实现的
// 后面肯定会优化代码的,否则我也不可能把代码写成博客
private fun obtainParentViewType(parentPosition: Int): Int = PARENT_VIEW_TYPE
private fun obtainChildViewType(parentPosition: Int, childPosition: Int): Int = CHILD_VIEW_TYPE
在讲如何用比较优雅的方式实现view type之前,先复习一下java的位运算。
- "|“运算符:当两个数字用”|"运算的时候,bit的处理方式是:只要有一个是1,就得到1。如:111和001两个二进制数用"1"计算出来的结果就是:111。
- "&“运算符:当两个数字用”&"运算的时候,bit的处理方式是:只要有一个是0,就得到0。如:110和001两个二进制数用"1"计算出来的结果就是:000。
所以我的处理方式是:用int的两个最高位分别代表parent view type和child view type。
所以
// 10000000 00000000 00000000 00000000
const val PARENT_VIEW_TYPE = 0x80000000.toInt()
// 01000000 00000000 00000000 00000000
const val CHILD_VIEW_TYPE = 0x40000000
所以如果想要将一个view type转换成一个parent view type,就使用PARENT_VIEW_TYPE和该view type做"|“运算。想要转换成child view type,就做”&"运算。不过由于使用了这种方式,所以需要验证得到的view type,这个比较简单,下面再提。
方案想到了,但必须要验证自己的方案是否可行,否则当拿去用的时候才发现方案有问题就麻烦了,所以先写一些java代码进行验证。
public class ViewTypeTest {@Testpublic void main() {testParentViewType();testChildViewType();}private static void testParentViewType() {int PARENT_VIEW_TYPE = 0x80000000;int viewType = 1;int parentViewType = viewType | PARENT_VIEW_TYPE;// 到了这里,android studio已经告诉我是true了System.out.println((parentViewType & PARENT_VIEW_TYPE) == PARENT_VIEW_TYPE);// 使用左移和右移得到原始的view typeSystem.out.println((parentViewType << 1 >> 1) == viewType);}private static void testChildViewType() {int CHILD_VIEW_TYPE = 0x40000000;int viewType = 1;int childViewType = viewType | CHILD_VIEW_TYPE;System.out.println((childViewType & CHILD_VIEW_TYPE) == CHILD_VIEW_TYPE);System.out.println((childViewType << 2 >> 2) == viewType);}
}true
true
true
true
既然思路没问题,那就把obtainParentViewType和obtainChildViewType方法完善一下。
companion object {// 10000000 00000000 00000000 00000000private const val PARENT_VIEW_TYPE = 0x80000000.toInt()// 01000000 00000000 00000000 00000000private const val CHILD_VIEW_TYPE = 0x40000000// 取值范围为:[0,CHILD_VIEW_TYPE-1]// 00111111 11111111 11111111 11111111private const val MAX_VIEW_TYPE = 0x3fffffffconst val DEFAULT_VIEW_TYPE = 0
}// 生成parent view type,这里会调用getParentViewType,子类可以根据需要去实现
private fun obtainParentViewType(parentPosition: Int): Int {val type = getParentViewType(parentPosition)checkViewType(type)return type or PARENT_VIEW_TYPE
}/*** @see MAX_VIEW_TYPE* @see checkViewType* @return parent view type,它可以与child view type相同。然而,它不能大于MAX_VIEW_TYPE也不能为一个负数*/
protected open fun getParentViewType(parentPosition: Int) = DEFAULT_VIEW_TYPE// 生成child view type,这里会调用getChildViewType,子类可以根据需要去实现
private fun obtainChildViewType(parentPosition: Int, childPosition: Int): Int {val type = getChildViewType(parentPosition, childPosition)checkViewType(type)return type or CHILD_VIEW_TYPE
}
/*** @see MAX_VIEW_TYPE* @see checkViewType* @return child view type,它可以与parent view type相同。然而,它不能大于MAX_VIEW_TYPE也不能为一个负数*/
protected open fun getChildViewType(parentPosition: Int, childPosition: Int) = DEFAULT_VIEW_TYPEprivate fun checkViewType(viewType: Int) {if (viewType < 0 || viewType > MAX_VIEW_TYPE) {throw java.lang.IllegalArgumentException("view type :$viewType can't less than 0 or greater than 1073741823(0x3fffffff).")}
}
然后再加2个判断是否为parent view type和child view type就行了
protected fun isParentViewType(viewType: Int) = (viewType and PARENT_VIEW_TYPE) == PARENT_VIEW_TYPEprotected fun isChildViewType(viewType: Int) = (viewType and CHILD_VIEW_TYPE) == CHILD_VIEW_TYPE
getItemCount的实现
这个比较简单,只需要简单地遍历而已。
final override fun getItemCount(): Int {var count = 0for (parentPosition in 0 until getParentCount()) {count++count += if (isChildListDisplay(parentPosition)) {getChildCountFromParent(parentPosition)} else {0}}return count
}
onCreateViewHolder的实现
这个也比较简单,判断一下view type,如果是parent view type,就create一个parent view holder。如果是child view tyep,就create一个child view holder。
final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {return when {isParentViewType(viewType) -> onCreateParentViewHolder(parent, viewType shl 1 shr 1)isChildViewType(viewType) -> onCreateChildViewHolder(parent, viewType shl 2 shr 2)else -> throw RuntimeException("unknow view type:$viewType")}
}
onBindViewHolder的实现
这个需要根据拿到的position计算出实际的postion,再调用onBindParentViewHolder或onBindChildViewHolder方法。
代码看起来还是比较简单的,所以就不写注释了。
final override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {var positionCounter = -1for (parentPosition in 0 until getParentCount()) {positionCounter++if (position == positionCounter) {val isDisplayedChildList = isDisplayedChildList(parentPosition)onBindParentViewHolder(holder as PARENT_VH, parentPosition, isDisplayedChildList)holder.itemView.setOnClickListener {onParentClickListener?.invoke(parentPosition)}return}if (isDisplayedChildList(parentPosition)) {val childCount = getChildCountFromParent(parentPosition)if (position <= positionCounter + childCount) {val childPosition = position - positionCounter - 1onBindChildViewHolder(holder as CHILD_VH, parentPosition, childPosition)holder.itemView.setOnClickListener {onChildClickListener?.invoke(parentPosition, childPosition)}return} else {positionCounter += childCount}}}
}
demo
主要的代码写完了,该出demo了。
这个demo涵盖了对多种parent view type的处理,并且也包含了parent的点击事件,应该把常见的开发场景给还原出来了。
效果图:
SecondListAdapter.kt
class SecondListAdapter :BaseRecyclerExpandableAdapter<SecondListAdapter.ParentViewHolder, SecondListAdapter.ChildViewHolder>() {companion object {private const val HEADER_1_PARENT_VIEW_TYPE = 1private const val HEADER_2_PARENT_VIEW_TYPE = 2private const val NORMAL_PARENT_VIEW_TYPE = 3private const val HEADER_1_PARENT_POSITION = 0private const val HEADER_2_PARENT_POSITION = 1private const val HEADER_1_PARENT_VIEW = 1private const val HEADER_2_PARENT_VIEW = 1}val parentList = ArrayList<String>()val childMap = HashMap<String, Int>()init {setOnParentClickListener { parentPosition ->if (parentPosition == HEADER_1_PARENT_POSITION || parentPosition == HEADER_2_PARENT_POSITION) {return@setOnParentClickListener}if (isDisplayedChildList(parentPosition)) {hideChildList(parentPosition)} else {displayChildList(parentPosition)}}}override fun onCreateParentViewHolder(parent: ViewGroup, viewType: Int): ParentViewHolder =when (viewType) {HEADER_1_PARENT_VIEW_TYPE -> Header1ParentViewHolder(FrameLayout(parent.context))HEADER_2_PARENT_VIEW_TYPE -> Header2ParentViewHolder(FrameLayout(parent.context))else -> NormalParentViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_second_list, parent, false)}override fun getParentViewType(parentPosition: Int): Int {return when (parentPosition) {HEADER_1_PARENT_POSITION -> HEADER_1_PARENT_VIEW_TYPEHEADER_2_PARENT_POSITION -> HEADER_2_PARENT_VIEW_TYPEelse -> NORMAL_PARENT_VIEW_TYPE}}override fun onCreateChildViewHolder(parent: ViewGroup, viewType: Int): ChildViewHolder =ChildViewHolder(FrameLayout(parent.context))override fun onBindParentViewHolder(viewHolder: ParentViewHolder,parentPosition: Int,isDisplayedChildList: Boolean) {when (getParentViewType(parentPosition)) {HEADER_1_PARENT_VIEW_TYPE -> {val vh = viewHolder as Header1ParentViewHoldervh.textView.text = "header_1"}HEADER_2_PARENT_VIEW_TYPE -> {val vh = viewHolder as Header2ParentViewHoldervh.textView.text = "header_2"}else -> {val realPosition = getRealParentPosition(parentPosition)val vh = viewHolder as NormalParentViewHoldervh.textView.text = parentList[realPosition]}}}override fun onBindChildViewHolder(viewHolder: ChildViewHolder,parentPosition: Int,childPosition: Int) {}override fun getParentCount(): Int =HEADER_1_PARENT_VIEW + HEADER_2_PARENT_VIEW + parentList.sizeoverride fun getChildCountFromParent(parentPosition: Int): Int =when (parentPosition) {HEADER_1_PARENT_POSITION, HEADER_2_PARENT_POSITION -> 0else -> childMap[parentList[getRealParentPosition(parentPosition)]] ?: 0}private fun getRealParentPosition(parentPosition: Int) =parentPosition - HEADER_1_PARENT_VIEW - HEADER_2_PARENT_VIEWopen class ParentViewHolder(itemView: View) :BaseRecyclerExpandableAdapter.BaseViewHolder(itemView)class Header1ParentViewHolder(itemView: FrameLayout) :ParentViewHolder(itemView) {val textView = TextView(itemView.context).also {it.setTextColor(0xff000000.toInt())it.textSize = 40fit.setPadding(10, 10, 10, 10)}init {itemView.layoutParams = RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT)itemView.addView(textView)}}class Header2ParentViewHolder(itemView: FrameLayout) :ParentViewHolder(itemView) {val textView = TextView(itemView.context).also {it.setTextColor(0xffff0000.toInt())it.textSize = 60fit.setPadding(10, 10, 10, 10)}init {itemView.layoutParams = RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT)itemView.addView(textView)}}class NormalParentViewHolder(itemView: View) :ParentViewHolder(itemView) {val textView: TextView = itemView.findViewById(R.id.text)}class ChildViewHolder(itemView: FrameLayout) :BaseRecyclerExpandableAdapter.BaseViewHolder(itemView) {val imageView = ImageView(itemView.context).also {val dp40 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,40f,itemView.context.resources.displayMetrics).toInt()it.layoutParams = FrameLayout.LayoutParams(dp40, dp40)it.setPadding(10, 10, 10, 10)it.setImageResource(R.mipmap.ic_launcher)}init {itemView.layoutParams = RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT)itemView.addView(imageView)}}
}class SecondListActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_second_list)recycler.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)val adapter = SecondListAdapter()val title1 = "title1"val title2 = "title2"adapter.parentList.addAll(arrayListOf(title1, title2))adapter.childMap[title1] = 4adapter.childMap[title2] = 10recycler.adapter = adapter}
}
item_second_list.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:id="@+id/text"android:padding="10dp"android:layout_width="match_parent"android:layout_height="wrap_content" />
</FrameLayout>
可以直接到github上面把代码下载下来,然后用上面的代码试试看,点击item1/2时,会显示或隐藏child list
上面这些代码中,或许有一个地方觉得有点疑惑。HEADER_1_PARENT_VIEW
和HEADER_2_PARENT_VIEW
的值都是1
,这些写有什么意义?
这算是我在开发的时候想出来的一个小技巧,可以看到他们都被用到了这两个方法。
override fun getParentCount(): Int =HEADER_1_PARENT_VIEW + HEADER_2_PARENT_VIEW + parentList.sizeprivate fun getRealParentPosition(parentPosition: Int) =parentPosition - HEADER_1_PARENT_VIEW - HEADER_2_PARENT_VIEW
这种场景一般有多种处理方式,如:直接写2或者写-1-1,但我发现这种写法其实不存在任何的可读性。如果代码这样写,还需要补注释才能让其他人看懂这种代码。所以我就想到了这种方式,给1
起一个大家都看得懂的名字,这样就可以在不编写注释的情况下提升代码的可读性。
优化
上面这些代码是完成了需求,但性能上其实有不少问题。RecyclerView每次调用getItemCount都需要计算一次count,每次调用getItemViewType和onBindViewHolder也都需要重新计算。虽然实际运行的时候,看不出任何卡顿。但这种比较明显的性能问题,还是有必要进行优化。
注册监听方法
// 首先,调用:registerAdapterDataObserver方法,重写所有方法,并提供一个计算方法,让这些被重写的方法都调用这个计算方法。。
init {registerDataObserver()}private fun registerDataObserver() {registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {override fun onChanged() {calculateNecessaryData()}override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {calculateNecessaryData()}override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {calculateNecessaryData()}override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {calculateNecessaryData()}override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {calculateNecessaryData()}override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {calculateNecessaryData()}})}private fun calculateNecessaryData(){}
calculateNecessaryData方法的实现
companion object {const val NO_POSITION = -1
}
// 记录view type
private val viewTypeRecorder = ArrayList<Int>()
// 记录每个position实际的parent position和child position
private val realPositionRecorder = ArrayList<RealPosition>()protected class RealPosition(val parentPosition: Int, val childPosition: Int)// 代码非常简单,就不写注释了,这样处理之后,getItemCount和onBindViewHolder的实现就很简单了
private fun calculateNecessaryData() {viewTypeRecorder.clear()realPositionRecorder.clear()for (parentPosition in 0 until getParentCount()) {viewTypeRecorder.add(obtainParentViewType(parentPosition))realPositionRecorder.add(RealPosition(parentPosition, NO_POSITION))if (isDisplayedChildList(parentPosition)) {for (childPosition in 0 until getChildCountFromParent(parentPosition)) {viewTypeRecorder.add(obtainChildViewType(parentPosition, childPosition))realPositionRecorder.add(RealPosition(parentPosition, childPosition))}}}
}final override fun getItemCount(): Int = viewTypeRecorder.sizefinal override fun getItemViewType(position: Int): Int = viewTypeRecorder[position]final override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {val realPosition = realPositionRecorder[position]if (realPosition.childPosition == NO_POSITION) {val isDisplayedChildList = isDisplayedChildList(realPosition.parentPosition)onBindParentViewHolder(holder as PARENT_VH,realPosition.parentPosition,isDisplayedChildList)holder.itemView.setOnClickListener {onParentClickListener?.invoke(realPosition.parentPosition)}return}onBindChildViewHolder(holder as CHILD_VH,realPosition.parentPosition,realPosition.childPosition)holder.itemView.setOnClickListener {onChildClickListener?.invoke(realPosition.parentPosition, realPosition.childPosition)}
}protected fun getRealPosition(position: Int): RealPosition? =realPositionRecorder.getOrNull(position)
最后,需要注意的是,在将adapter设置到RecyclerView之后需要手动调用一次notify方法。因为直接设置的话并不会触发register里面的方法,此时,getItemCouunt等方法就获取不到数据,所以需要手动调用一次。
用了这种方式优化之后,每次滑动就直接从缓存中去数据,而不用重新计算。所以效率提升了不少,而且逻辑也更清晰了。第一个版本的代码,计算item count、view type等方法看起来还是比较复杂的,但用了这种方式就变得特别直观了。
二级列表的悬浮功能
这种功能百度可以找出一堆,但当我在开发的时候,发现百度找到的那些代码都不能解决我的问题。无奈只能自己想办法,刚好那个时候手头上已经有了这个adapter,所以借鉴了百度找到的代码自己实现。
先说一下为什么需要自己实现吧。是这样的,有一个列表界面,这个列表头上有几个Header。所以一开始这个界面的做法就是外部一个NestedScrollView,然后几个header view+RecyclerView,由于NestedScrollView可以将RecyclerVie的高度变得很长,所以简单粗暴地解决了问题。后面说要加悬浮的功能,将百度找到的ItemDecoration套进去,发现不行。因为这个时候发现recylerView的getChildAt(0)获取到的永远是最上面的view(header下面的view),而不是可见的第一个。这个时候才意识到用NestedScrollView会出现性能问题。然后才用view type加了几个header来解决。
具体看代码吧,如果有类似的需求,用这种方式解决比较好。而且我在百度找到的很多代码,是没办法实现点击事件的。因为那些界面都是绘制出来,不是一个实体,没办法添加。但用BaseRecyclerExpandableAdapter去实现的话,就可以添加点击事件。
效果图就看博客开头的图片,那就是完整的实现方式
// FloatItemDecoration.kt
class FloatItemDecoration(private val recyclerView: RecyclerView) : RecyclerView.ItemDecoration() {interface StickHeaderInterface {fun isStick(position: Int): Boolean}private val linearLayoutManager = recyclerView.layoutManager as LinearLayoutManagerprivate val adapter =recyclerView.adapter ?: throw RuntimeException("please set Decoration after set adapter")private val stickHeaderInterface = adapter.let {if (it !is StickHeaderInterface) {throw RuntimeException("please let your adapter implements StickHeaderInterface")}adapter as StickHeaderInterface}override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {super.onDrawOver(c, parent, state)// 获取第一个可见的view和他的positionval firstChild = parent.getChildAt(0) ?: returnval position = parent.getChildAdapterPosition(firstChild)// 向该view的上面寻找,目的是找出可以stick的viewfor (i in position downTo 0) {// 如果找到了if (stickHeaderInterface.isStick(i)) {var top = 0// 这里两个if的作用是:当position的下一个view,即屏幕可见的那个view的下一个view需要stick// 的时候,就获取该view的top,并且当该view的top大于的时候,top的值用该view的top// 这里的代码很关键,正因为有了这两个if里面的代码,才实现了两个stick view贴在一起// 一起向上或向下滚动的效果if (position + 1 < adapter.itemCount) {if (stickHeaderInterface.isStick(position + 1)) {val nextChild = parent.getChildAt(1)top = Math.max(linearLayoutManager.getDecoratedTop(nextChild), 0)}}val holder = adapter.createViewHolder(parent, adapter.getItemViewType(i))adapter.bindViewHolder(holder, i)// 注意:这里计算的是在i的位置的view的大小,不是postion的位置val measureHeight = getMeasureHeight(holder.itemView)c.save()// 只有当top小于第一个view的高度的时候,并且top大于0,画布才向上滚动if (top < measureHeight && top > 0) {c.translate(0f, ((top - measureHeight).toFloat()))}holder.itemView.draw(c)return}}}private fun getMeasureHeight(header: View): Int {val widthSpec =View.MeasureSpec.makeMeasureSpec(recyclerView.width, View.MeasureSpec.EXACTLY)val heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)header.measure(widthSpec, heightSpec)header.layout(0, 0, header.measuredWidth, header.minimumHeight)return header.measuredHeight}
}// SecondListAdapter.kt
// 实现FloatItemDecoration.StickHeaderInterface接口并且加这样一段代码
override fun isStick(position: Int): Boolean {// 如果该position是一个parent view,并且不是header,就返回true,因为header不能stickreturn getRealPosition(position)?.let { realPosition ->if (realPosition.childPosition != NO_POSITION) {return false}realPosition.parentPosition != HEADER_1_PARENT_POSITION && realPosition.parentPosition != HEADER_2_PARENT_POSITION} ?: false
}
如果需要点击事件,就在adapter里面自己加吧,这里就不赘述了。
关于抛出异常的代码
看了上面的代码,可以发现,不少代码执行到else时,就会抛出异常。这种做法可能会导致APP运行时崩溃,如果担心出现问题,可以把抛异常的代码改成log.e或log.wtf这种代码。