RecyclerView吸顶效果

作者: Cabe | 来源:发表于2021-03-11 16:50 被阅读0次

    背景

    一些情况下,我们的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()
            })
    

    效果

    SVID_20210311_162714_1 (1).gif

    相关文章

      网友评论

        本文标题:RecyclerView吸顶效果

        本文链接:https://www.haomeiwen.com/subject/uhcoqltx.html