背景
一些情况下,我们的RecyclerView需要展示一些复杂的数据,比如二级关联数据,类似QQ的好友列表。但网上找了一些类似的吸顶效果,总感觉实现方式比较繁重,所有只好自己来实现一个轻量级的方式
实现思路
考虑到RecyclerVIew控件的扩展性,我第一个想到的就是利用ItemDecoration这个属性来实现吸顶效果,话不多说,直接上代码
class StickyHeaderDecoration(val onGetHeaderView: (itemView: View) -> View, val onHeaderClick: (position: Int) -> Unit): RecyclerView.ItemDecoration() {
private var floatTouchListener: MyTouchListener? = null
private var curTopPosition = 0
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State?) {
super.onDraw(c, parent, state)
if(floatTouchListener != null) {
parent.removeOnItemTouchListener(floatTouchListener)
}
parent.layoutManager?.let { layoutManager ->
curTopPosition = when(layoutManager) {
is LinearLayoutManager -> layoutManager.findFirstVisibleItemPosition()
is GridLayoutManager -> layoutManager.findFirstVisibleItemPosition()
else -> 0
}
if(curTopPosition < 0) return@let
val rectParent = Rect()
parent.getGlobalVisibleRect(rectParent)
val rectGlobal = Rect()
getItemView(parent, curTopPosition)?.getGlobalVisibleRect(rectGlobal)
if(rectGlobal.bottom < rectParent.top) {
curTopPosition += 1
if(curTopPosition < layoutManager.itemCount) {
getItemView(parent, curTopPosition)?.getGlobalVisibleRect(rectGlobal)
}
}
val rectLocal = Rect()
getItemView(parent, curTopPosition)?.getLocalVisibleRect(rectLocal)
if(rectGlobal.top > rectParent.top || rectLocal.height() < 10) return@let
val headerHeight = SizeUtils.dp2px(48f)
val realHeight = min(rectLocal.height(), SizeUtils.dp2px(48f))
val boundRect = Rect(0, 0, parent.width, realHeight)
val scrollOffset = headerHeight - realHeight
val paint = Paint()
paint.isAntiAlias = true
getHeaderView(parent, curTopPosition)?.let { header ->
view2Bitmap(header)?.let { bmp ->
val bound = Rect(0, scrollOffset, header.width, header.height)
val dest = Rect(0, 0, bound.width(), bound.height())
c.drawBitmap(bmp, bound, dest, paint)
bmp.recycle()
}
}
if(floatTouchListener == null) {
floatTouchListener = MyTouchListener(parent, boundRect)
}
parent.addOnItemTouchListener(floatTouchListener)
}
}
private fun getItemView(parent: RecyclerView, position: Int): View? {
return parent.findViewHolderForAdapterPosition(position)?.itemView
}
private fun getHeaderView(parent: RecyclerView, position: Int): View? {
return getItemView(parent, position)?.let { itemView ->
onGetHeaderView(itemView)
}
}
private inner class MyTouchListener(parent: RecyclerView, boundRect: Rect): RecyclerView.OnItemTouchListener {
val mTapDetector = GestureDetector(parent.context, SingleTapDetector(boundRect))
override fun onInterceptTouchEvent(rv: RecyclerView?, e: MotionEvent?): Boolean {
return mTapDetector.onTouchEvent(e)
}
override fun onTouchEvent(rv: RecyclerView?, e: MotionEvent?) {
}
override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
}
}
private inner class SingleTapDetector(val boundRect: Rect): GestureDetector.SimpleOnGestureListener() {
private fun isFloatArea(event: MotionEvent?): Boolean {
if(event == null) return false
var result = false
val touchX = event.x.toInt()
val touchY = event.y.toInt()
if(boundRect.contains(touchX, touchY)) {
result = true
}
return result
}
override fun onSingleTapUp(e: MotionEvent): Boolean {
if (isFloatArea(e)) {
onHeaderClick(curTopPosition)
return true
}
return false
}
override fun onDoubleTap(e: MotionEvent): Boolean {
return true
}
}
}
其中,onGetHeaderView回调是显示吸顶样式的,主要是通过复制View样式,进行绘制;
而onHeaderClick是点击吸顶区域的事件回调,
而里面有一个"view2Bitmap"方法就是将View转成bitmap的逻辑,这里就不贴出来了
使用
recyclerView?.addItemDecoration(StickyHeaderDecoration(fun(itemView: View): View {
//这里返回吸顶的样式
return itemView.item_company_department_header
}) { position ->
//这里是处理吸顶区域的点击事件
recyclerView?.findViewHolderForAdapterPosition(position)?.itemView?.item_company_depart ment_header?.performClick()
})