自定义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) -> Unit
abstract 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? = null
private var onChildClickListener: OnChildClickListener? = null
// 记录不需要显示child list的列表
private val hideChildListParentPositionList = ArrayList<Int>()
final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
}
// 创建parent的ViewHolder
protected abstract fun onCreateParentViewHolder(parent: ViewGroup, viewType: Int): PARENT_VH
// 创建child的ViewHolder
protected abstract fun onCreateChildViewHolder(parent: ViewGroup, viewType: Int): CHILD_VH
final 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 count
protected abstract fun getParentCount(): Int
// 根据parent position获取child count
protected abstract fun getChildCountFromParent(parentPosition: Int): Int
final 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 = 0
const val CHILD_VIEW_TYPE = 10000
}
final override fun getItemViewType(position: Int): Int {
var positionCounter = -1
for (parentPosition in 0 until getParentCount()) {
positionCounter++
// 如果拿到的position与positionCounter相等,则是一个parent view type
if (position == positionCounter) {
return obtainParentViewType(parentPosition)
}
// 如果不是,并且child list display,则看看是不是一个child view type
if (isChildListDisplay(parentPosition)) {
val childCount = getChildCountFromParent(parentPosition)
// 如果position小于等于counter+child count,则说明这个view type是一个child view type,直接计算出child position
if (position <= positionCounter + childCount ) {
// 这里需要-1是因为count不可能是一个为0的数字,所以需要-1才能得到正确的position
return 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 {
@Test
public 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 type
System.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 00000000
private const val PARENT_VIEW_TYPE = 0x80000000.toInt()
// 01000000 00000000 00000000 00000000
private const val CHILD_VIEW_TYPE = 0x40000000
// 取值范围为:[0,CHILD_VIEW_TYPE-1]
// 00111111 11111111 11111111 11111111
private const val MAX_VIEW_TYPE = 0x3fffffff
const 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_TYPE
private 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_TYPE
protected fun isChildViewType(viewType: Int) = (viewType and CHILD_VIEW_TYPE) == CHILD_VIEW_TYPE
getItemCount的实现
这个比较简单,只需要简单地遍历而已。
final override fun getItemCount(): Int {
var count = 0
for (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 = -1
for (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 - 1
onBindChildViewHolder(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 = 1
private const val HEADER_2_PARENT_VIEW_TYPE = 2
private const val NORMAL_PARENT_VIEW_TYPE = 3
private const val HEADER_1_PARENT_POSITION = 0
private const val HEADER_2_PARENT_POSITION = 1
private const val HEADER_1_PARENT_VIEW = 1
private 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_TYPE
HEADER_2_PARENT_POSITION -> HEADER_2_PARENT_VIEW_TYPE
else -> 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 Header1ParentViewHolder
vh.textView.text = "header_1"
}
HEADER_2_PARENT_VIEW_TYPE -> {
val vh = viewHolder as Header2ParentViewHolder
vh.textView.text = "header_2"
}
else -> {
val realPosition = getRealParentPosition(parentPosition)
val vh = viewHolder as NormalParentViewHolder
vh.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.size
override fun getChildCountFromParent(parentPosition: Int): Int =
when (parentPosition) {
HEADER_1_PARENT_POSITION, HEADER_2_PARENT_POSITION -> 0
else -> childMap[parentList[getRealParentPosition(parentPosition)]] ?: 0
}
private fun getRealParentPosition(parentPosition: Int) =
parentPosition - HEADER_1_PARENT_VIEW - HEADER_2_PARENT_VIEW
open 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 = 40f
it.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 = 60f
it.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] = 4
adapter.childMap[title2] = 10
recycler.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">
<TextView
android: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.size
private 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.size
final 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 LinearLayoutManager
private 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和他的position
val firstChild = parent.getChildAt(0) ?: return
val position = parent.getChildAdapterPosition(firstChild)
// 向该view的上面寻找,目的是找出可以stick的view
for (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不能stick
return 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这种代码。