Android RecycleView 吸顶功能 支持LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager

Android RecycleView 吸顶功能 支持LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager,第1张

Android RecycleView 吸顶功能 支持LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager

         RecycleView吸顶功能,从网络上随便下载了一张图,类似于下图这种,


具体怎么做呢。其实可以通过 自定义 :RecyclerView.ItemDecoration  实现

先说 RecyclerView.ItemDecoration  里面常用的方法:

getItemOffsets() ---  设置 item 的间距,这个方法比较常用
onDrawOver() --- 绘制在item之上的,就是用这个来控制吸顶
onDraw() --- 绘制 item 里面的内容

这么讲不好理解,直接上图,图片也是在网上扣的,

 

其实就是在对应标题的 Item ,绘制在其上面。然后我们不管当前Item是否是标题类型,我们都把它绘制在屏幕顶部,也就是 top = 0 的地方,这样就有一块东西是固定在列表顶部显示。

然后如果新的标题即将推到列表顶部,此时就需要把就旧的标题推出屏幕,把 top = 负数,慢慢推出屏幕顶部。

But..........talk is cheap,show u my code

先上 Activity 和 对应 xml 的代码




    

class StickyTopActivity : AppCompatActivity() {

    companion object {
        fun launch(context: Context) {
            context.startActivity()
        }
    }

    
    data class MyData(val title: String, val type: Int, val typeTitle: String)

    private val items: ArrayList = arrayListOf()

    private fun initData() {
        for (index in 0..1000) {
            if (index % 10 == 0) {
                // index 为 10的倍数,为标题
                items.add(MyData("标题 ${index / 10}", 1, "标题 ${index / 10}"))
            } else {
                // 其他的为普通内容
                items.add(MyData("内容${index}", 2, "标题 ${index / 10}"))
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_sticky_top)
        initData()
        recycleView.layoutManager = LinearLayoutManager(this)
         
        recycleView.addItemDecoration(TitleItemDecoration(this, object :
            TitleItemDecoration.TitleDecorationCallback {

            override fun isHeadItem(position: Int) = items[position].type == 1

            override fun getHeadTitle(position: Int) = items[position].typeTitle

        }))
        recycleView.adapter = StickyTopAdapter(items)
    }

}

吸顶功能就是通过自定义 ItemDecoration吸顶实现,其他的,和平常用RecycleView没区别。

当然,我顺便也贴一下Adapter 和 ViewHolder 的代码。

class StickyTopAdapter(private val items: ArrayList) :
    RecyclerView.Adapter() {

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): RecyclerView.ViewHolder {
        return if (viewType == 1) {
            val view = LayoutInflater.from(parent.context)
                .inflate(R.layout.item_head, parent, false)
            MyHeadViewHolder(
                view
            )
        } else {
            MyViewHolder(
                LayoutInflater.from(parent.context)
                    .inflate(R.layout.item_content, parent, false)
            )
        }
    }

    override fun getItemCount() = items.size

    override fun getItemViewType(position: Int) = items[position].type

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (holder is MyViewHolder) {
            holder.bind(items[position])
        } else if (holder is MyHeadViewHolder) {
            holder.bind(items[position])
        }
    }
}



class MyHeadViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    private val textView = itemView.findViewById(R.id.tvTitle)
    fun bind(data: StickyTopActivity.MyData) {
        textView.text = data.title
    }
}



class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    private val textView = itemView.findViewById(R.id.textView)
    fun bind(data: StickyTopActivity.MyData) {
        textView.text = data.title
    }
}

item_head.xml  代码



    

item_content.xml 代码



    

上面贴了这么多代码,都是平常用到的平平无奇的。

而实现吸顶的关键,就是自定义 RecyclerView.ItemDecoration 的实现,来,我们看下里面怎么实现。

class TitleItemDecoration(
    private val context: Context,
    private val callback: TitleDecorationCallback
) : RecyclerView.ItemDecoration() {

    private val mTitleHeight: Int

    // 直接引用标题ViewHolder对应的layout -- item_head.xml
    private val titleLayout: View = LayoutInflater.from(context).inflate(R.layout.item_head, null)
    private val tvTitle = titleLayout.findViewById(R.id.tvTitle)

    init {
        
        titleLayout.measure(View.MeasureSpec.AT_MOST, View.MeasureSpec.UNSPECIFIED)
        mTitleHeight = titleLayout.measuredHeight
    }

    
    override fun onDrawOver(
        canvas: Canvas,
        recyclerView: RecyclerView,
        state: RecyclerView.State
    ) {
        super.onDrawOver(canvas, recyclerView, state)
        // 获取第一个可见 Item 对应的 Position
        val firstVisiblePosition = findFirstVisibleItemPosition(recyclerView.layoutManager!!)
        if (firstVisiblePosition <= -1 || firstVisiblePosition >= recyclerView.adapter!!.itemCount - 1) {
            // 安全检测,防止越界
            return
        }
        // 获取第一个可见 Item 对应 View
        val firstVisibleView =
            recyclerView.findViewHolderForAdapterPosition(firstVisiblePosition)!!.itemView

        // 因为我们要绘制在列表顶部,所以先获取RecycleView 左右上 三个坐标
        val left = recyclerView.paddingLeft
        val right = recyclerView.width - recyclerView.paddingRight
        var top = recyclerView.paddingTop
        
         
        if (nextLineIsTitle(
                firstVisibleView,
                firstVisiblePosition,
                recyclerView
            ) && firstVisibleView.bottom < mTitleHeight
        ) {
            top = if (mTitleHeight <= firstVisibleView.height) {
                val d = firstVisibleView.height - mTitleHeight
                
                firstVisibleView.top + d
            } else {
                val d = mTitleHeight - firstVisibleView.height
                firstVisibleView.top - d
            }
        }
        // 去绘制头部
        drawTitle(canvas, top, firstVisiblePosition, left, right)
    }

    private fun drawTitle(canvas: Canvas, top: Int, position: Int, left: Int, right: Int) {
        canvas.save()
        // 设置偏移,dx=0,即代表向左对齐
        canvas.translate(0f, top.toFloat())
        tvTitle.text = callback.getHeadTitle(position)
        titleLayout.layout(left, 0, right, mTitleHeight)
        titleLayout.draw(canvas)
        canvas.restore()
    }

    
    private fun findFirstVisibleItemPosition(layoutManager: RecyclerView.LayoutManager): Int {
        return when (layoutManager) {
            is LinearLayoutManager -> {
                layoutManager.findFirstVisibleItemPosition()
            }
            is GridLayoutManager -> {
                layoutManager.findFirstVisibleItemPosition()
            }
            is StaggeredGridLayoutManager -> {
                layoutManager.findFirstVisibleItemPositions(null)[0]
            }
            else -> {
                throw RuntimeException("咱不支持 类型为:${layoutManager.javaClass.name} 的LayoutManager ,可以自己判断类型,转成自己的LayoutManager,去获取第一个可见Item的position ")
            }
        }
    }

    
    private fun nextLineIsTitle(
        currentView: View,
        currentPosition: Int,
        parent: RecyclerView
    ): Boolean {
        for (nextLinePosition in currentPosition + 1 until parent.adapter!!.itemCount) {
            val nextItemView = parent.findViewHolderForAdapterPosition(nextLinePosition)!!.itemView
            if (nextItemView.bottom > currentView.bottom) {
                // 找到下一行的 Position
                return callback.isHeadItem(nextLinePosition)
            }
        }
        return false
    }

    interface TitleDecorationCallback {
        
        fun isHeadItem(position: Int): Boolean

        
        fun getHeadTitle(position: Int): String
    }
}

具体原理和实现,代码和注释已经很清晰了。

运行起来,看结果

没问题!!!!

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/zaji/5719937.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-12-18
下一篇2022-12-17

发表评论

登录后才能评论

评论列表(0条)

    保存