Created
February 3, 2023 03:38
-
-
Save xckevin/f9eec62d889b4ea4901be8f04374afb6 to your computer and use it in GitHub Desktop.
实现RecyclerView的sticky吸顶功能,不通过ItemDecoration实现,解决click等各种event事件问题,解决webview无法渲染问题,但需要外层包一个FrameLayout
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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