Skip to content

Instantly share code, notes, and snippets.

@xckevin
Created February 3, 2023 03:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xckevin/f9eec62d889b4ea4901be8f04374afb6 to your computer and use it in GitHub Desktop.
Save xckevin/f9eec62d889b4ea4901be8f04374afb6 to your computer and use it in GitHub Desktop.
实现RecyclerView的sticky吸顶功能,不通过ItemDecoration实现,解决click等各种event事件问题,解决webview无法渲染问题,但需要外层包一个FrameLayout
import android.graphics.Canvas
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.OnScrollListener
import androidx.recyclerview.widget.StaggeredGridLayoutManager
class RecyclerViewSticky(private val recyclerView: RecyclerView, private val predicate: (RecyclerView, Int) -> Boolean) : OnScrollListener() {
companion object {
fun bindSticky(recyclerView: RecyclerView, predicate: (RecyclerView, Int) -> Boolean) {
LokiSticky(recyclerView, predicate).attach()
}
}
private var mAdapter: RecyclerView.Adapter<*>? = null
var pinView: View? = null
var pinPos: Int = -1
private val resetTask = Runnable {
doReset()
}
fun attach() {
recyclerView.addOnScrollListener(this)
recyclerView.addOnLayoutChangeListener { v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
checkCache(recyclerView)
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
tryPinView(recyclerView)
}
private fun hidePinView() {
pinView?.visibility = View.GONE
}
private fun showPinView() {
pinView?.visibility = View.VISIBLE
}
private fun destroyPinView() {
pinView?.parent?.let {
(it as ViewGroup).removeView(pinView)
pinView = null
}
}
private fun tryPinView(recyclerView: RecyclerView) {
val firstVisiblePos = findFirstVisiblePosition(recyclerView.layoutManager)
val pinPos = findPinnedHeaderPosition(recyclerView, firstVisiblePos)
if (pinPos < 0 || firstVisiblePos < pinPos) {
hidePinView()
return
}
if (this.pinPos == pinPos && pinView != null) {
showPinView()
return
}
destroyPinView()
val type = recyclerView.adapter?.getItemViewType(pinPos)
val holder = recyclerView.adapter?.createViewHolder(recyclerView, type!!)
recyclerView.adapter?.bindViewHolder(holder!!, pinPos)
holder?.itemView.let {
var layout: FrameLayout? = null
var vp = recyclerView.parent
while (vp != null) {
if (vp is FrameLayout) {
layout = vp
break
}
vp = vp.parent
}
if (layout != null) {
pinView = it
this.pinPos = pinPos
layout.addView(
pinView,
ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
)
}
}
}
private fun findPinnedHeaderPosition(rv: RecyclerView, fromPosition: Int): Int {
val adapter = rv.adapter
if (adapter != null) {
for (position in fromPosition downTo 0) {
if (predicate.invoke(rv, position)) {
return position
}
}
}
return -1
}
private fun findFirstVisiblePosition(layoutManager: RecyclerView.LayoutManager?): Int {
var firstVisiblePosition = 0
if (layoutManager is GridLayoutManager) {
firstVisiblePosition = layoutManager.findFirstVisibleItemPosition()
} else if (layoutManager is LinearLayoutManager) {
firstVisiblePosition = layoutManager.findFirstVisibleItemPosition()
} else if (layoutManager is StaggeredGridLayoutManager) {
val into = IntArray(layoutManager.spanCount)
layoutManager.findFirstVisibleItemPositions(into)
firstVisiblePosition = Int.MAX_VALUE
for (pos in into) {
firstVisiblePosition = Math.min(pos, firstVisiblePosition)
}
}
return firstVisiblePosition
}
private fun checkCache(parent: RecyclerView) {
val adapter = parent.adapter
if (mAdapter !== adapter) {
reset()
mAdapter = adapter
if (mAdapter != null) {
(mAdapter as RecyclerView.Adapter<*>).registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
super.onChanged()
reset()
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
super.onItemRangeChanged(positionStart, itemCount)
reset()
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
super.onItemRangeChanged(positionStart, itemCount, payload)
reset()
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
super.onItemRangeInserted(positionStart, itemCount)
reset()
}
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
super.onItemRangeRemoved(positionStart, itemCount)
reset()
}
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
super.onItemRangeMoved(fromPosition, toPosition, itemCount)
reset()
}
})
}
}
}
private fun reset() {
recyclerView.removeCallbacks(resetTask)
recyclerView.post(resetTask)
}
private fun doReset() {
pinPos = -1
destroyPinView()
tryPinView(recyclerView)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment