Android相关经典问题方案安卓开发

RecyclerView吸顶效果

2021-03-11  本文已影响0人  Cabe

背景

一些情况下,我们的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
上一篇下一篇

猜你喜欢

热点阅读