
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
}
}
具体原理和实现,代码和注释已经很清晰了。
运行起来,看结果
没问题!!!!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)