实现RecyclerView二级列表

news/2024/4/27 1:36:08/文章来源:https://blog.csdn.net/android_upl/article/details/129263009

自定义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的实现

上面的getParentViewTypegetChildViewType都是返回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_VIEWHEADER_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这种代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_75484.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Kotlin学习:5.2、异步数据流 Flow

Flow一、Flow1、Flow是什么东西&#xff1f;2、实现功能3、特点4、冷流和热流5、流的连续性6、流的构建器7、流的上下文8、指定流所在协程9、流的取消9.1、超时取消9.2、主动取消9.3、密集型任务的取消10、背压和优化10.1、buffer 操作符10.2、 flowOn10.3、conflate 操作符10.…

同为(TOWE)防雷产品助力福建移动南平分公司防雷改造

01 公司简介中国移动通信集团福建有限公司南平分公司属于福建移动地级分公司&#xff0c;所属行业为电信、广播电视和卫星传输服务。现已建成覆盖范围广、业务品种多、通信质量高的综合通信网络&#xff0c;具备行业领先的经营管理制度。移动通信大楼的综合防雷及地接系统&…

Fedora系统安装KubeVela

话不多说直接看命令 Docker安装 Vela安装需要先安装Docker sudo yum -y install docker只需这行命令便可以自动添加 yum和dnf理论上都能成功&#xff0c;但是很看网速&#xff0c;&#xff0c;&#xff0c;实践证明yum是最好的。 如果发生报错mirrors trieds大概率就是网速超…

Kubernetes06:Controller (Deployment无状态应用)

Kubernetes06:Controller 1、什么是controller 管理和运行容器的对象&#xff0c;是一个物理概念 在集群上管理和运行容器的对象 2、Pod和Controller之间的关系 Pod是通过controller来实现应用的运维 比如伸缩、滚动升级等等操作Pod和Controller之间通过 label 标签建立关系…

Java 常用 API

文章目录一、Math二、System三、Object1. toString() 方法2. equals() 方法四、Arrays1. 冒泡排序2. Arrays 常用方法五、基本类型包装类1. Integer2. int 和 String 相互转换3. 字符串中数据排序4. 自动装箱和拆箱六、日期类1. Date2. SimpleDateFormat3. Calendar4. 二月天一…

来面试阿里测开工程师,HR问我未来3-5年规划,我给HR画个大饼。

在面试的过程中是不是经常被面试官问未来几年的职业规划?你会答吗&#xff1f;是不是经常脑袋里一片空白&#xff0c;未来规划&#xff1f;我只是想赚更多的钱啊&#xff0c;哈哈哈&#xff0c;今天我来教大家&#xff0c;如何给面试官画一个大饼&#xff0c;让他吃的不亦乐乎…

C++ STL:迭代器 Iterator

文章目录1、迭代器的类型2、traitsiterator_traitstype_traits泛化的指针&#xff0c;容器与算法的桥梁。提供一种方法&#xff0c;按照一定顺序访问一个聚合对象中各个元素&#xff0c;而又不暴露该对象的内部表示。既能对容器进行遍历&#xff0c;又可以对外隐藏容器的底层实…

数据库多主键in查询组合篇(sqlserver特殊)

此篇介绍的是oracle、mysql、sqlserver、达梦、人大金仓、南大通用数据库的单主键和复合主键select in的查询总结。 Mysql Select id,name from t_db_task where (id,name) in((915,Oracle内到外全表同步),(916,Oracle外到内全表同步),(921,Oracle外到内的触发同步)); selec…

ElasticSearch 学习笔记总结(三)

文章目录一、ES 相关名词 专业介绍二、ES 系统架构三、ES 创建分片副本 和 elasticsearch-head插件四、ES 故障转移五、ES 应对故障六、ES 路由计算 和 分片控制七、ES集群 数据写流程八、ES集群 数据读流程九、ES集群 更新流程 和 批量操作十、ES 相关重要 概念 和 名词十一、…

Java9之HttpClientAPI实战详解

Java9 之 HttpClientAPI 实战详解 前言 相信关注 java9 的小伙伴们都知道 java9 版本内置模块提供了 Http 功能&#xff0c;当然并不是说之前 jdk 之前并不支持&#xff0c;那么这次更新又多了什么呢&#xff1f;或者是解决了什么问题&#xff1f; 说明 自 JDK 1.0 以来&…

mac安装 Termius

1.下载安装包 链接: https://pan.baidu.com/s/1f5xmvYnVehCkMUD291SbsA?pwdy43k 提取码: y43k 2.打开系统偏好设置 -> 安全性与隐私 -> 通用&#xff0c;勾选“任何来源” 显示文件损坏的情况下执行下面操作 3.打开terminal终端 3.1 输入&#xff1a;sudo spctl --m…

“来源可靠、程序规范、要素合规”与“四性”

《从技术可行性的视角看电子档案的“四性”》一文中已经明确&#xff0c;笔者认为的电子档案“四性”是指“真实性、完整性、可用性和安全性”。而《从特斯拉“刹车失灵”事件看电子档案的法定要求》一文中&#xff0c;笔者对于“来源可靠、程序规范、要素合规”的解读如下&…

解决windows安装wxPython安装失败、速度过慢及PyCharm上wx包爆红问题

网上关于wxPython安装失败&#xff0c;安装速度过慢&#xff0c;以及安装成功后PyCharm中import wx仍然爆红的文章有很多&#xff0c;也特别杂&#xff0c;解决起来特别困难&#xff0c;今天在这里对问题的处理进行一个整合&#xff0c;希望能帮助到大家。 安装wxPython这里运用…

MySQL表的增删查改(基础)

gitee:博客中的所有操作整合新增语法:insert [into] table_name values(value_list)[案例] 创建一个学生表进行数据插入1.1单行数据全列插入[提示]我们可以想在记事本上写下命令,让后复制到数据库客户端,这样可以在出错的时候进行快速修改.同时为了美观和明了,我们可以进行适当…

计算机的发展

个人简介&#xff1a;云计算网络运维专业人员&#xff0c;了解运维知识&#xff0c;掌握TCP/IP协议&#xff0c;每天分享网络运维知识与技能。个人爱好: 编程&#xff0c;打篮球&#xff0c;计算机知识个人名言&#xff1a;海不辞水&#xff0c;故能成其大&#xff1b;山不辞石…

低代码开发平台选型必看指南

低代码开发是近年来逐渐兴起的一种新型软件开发方式。它通过封装常见的软件开发流程和代码&#xff0c;使得非专业的开发者也能够轻松创建复杂的应用程序。这种开发方式已经受到了许多企业的青睐&#xff0c;成为提高生产效率、降低开发成本的一种有效途径。 低代码开发的核心…

docker部署zabbix6.2.7+grafana

目录 1、下载docker 2、下载相关镜像文件 3、创建一个供zabbix系统使用的网络环境 4、创建一个供mysql数据库存放文件的目录 5、启动mysql容器 6、为zabbix-server创建一个持久卷 7、启动zabbix-server容器 8、创建语言存放目录 9、启动zabbix-web容器 10、启动zabbix…

【解锁技能】学会Python条件语句的终极指南!

文章目录前言一. python条件语句的介绍1.1 什么是条件语句1.2 条件语句的语法1.3 关于内置函数bool()二. 分支语句之单分支三. 多分支语句3.1 二分支语句3.2 多分支语句3.3 嵌套循环总结前言 &#x1f3e0;个人主页&#xff1a;欢迎访问 沐风晓月的博客 &#x1f9d1;个人简介&…

EPICS synApps介绍

一、synApps是什么&#xff1f; 1&#xff09; 一个用于同步束线用户的EPICS模块集合。 2&#xff09; EPICS模块 alive, autosave, busy, calc, camac, caputRecorder, dac128V, delaygen, dxp, ip, ip330, ipUnidig, love, mca, measComp, modbus, motor, optics, quadEM,…

【蓝桥杯选拔赛真题38】python目标值判断 青少年组蓝桥杯python 选拔赛STEMA比赛真题解析

目录 python目标值判断 一、题目要求 1、编程实现 2、输入输出 二、解题思路