逼格特性:
- 使用
ItemDecoration
實現 - 支持懸浮時的
touch
事件, 以及Drawable
的狀態效果 - 同樣支持
子View
- 超簡單的使用方式, 只需要告訴
她
, 什麼位置,需要什麼懸停xml
即可.
自繪分割線通常需要重寫以下方法:
override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
checkOverDecoration(parent)
overViewHolder?.let {
if (!overDecorationRect.isEmpty) {
addHoverView(it.itemView)
if (it.itemView.parent != null) {
hoverCallback?.drawOverDecoration?.invoke(canvas, paint, it, overDecorationRect)
}
}
}
}
核心方法
思路: 沒次回調draw
時候, 遍歷RecyclerView
界面上, 所有的child
, 並且拿到child
對應的adapterPosition
,
然後通過adapterPosition
判斷是否需要分割線
如果不需要:
繼續往前查找, 直到找到需要分割線位置的child
, 拿到adapterPosition
, 或者真的沒有child
需要分割線, 則返回 -1
有了adapterPosition
就可以通過adapter
創建對應的view
, 這個時候, 調用View.Draw
方法, 就可以繪製出分割線.
如果不需要 touch
事件, 到這裏就實現了, 自定義View
的分割線了.
往下查找, 判斷緊挨着的下一個child
, 是否需要分割線
, 用來實現上推效果
, 可以通過 itemView.top
, 判斷是否需要 上推
.
/**
* 核心方法, 用來實時監測界面上 需要浮動的 分割線.
* */
internal fun checkOverDecoration(parent: RecyclerView) {
childViewHolder(parent, 0)?.let { viewHolder ->
var firstChildAdapterPosition = viewHolder.adapterPosition
if (firstChildAdapterPosition != RecyclerView.NO_POSITION) {
parent.adapter?.let { adapter ->
hoverCallback?.let { callback ->
var firstChildHaveOver = callback.haveOverDecoration.invoke(firstChildAdapterPosition)
if (!firstChildHaveOver) {
//第一個child沒有分割線, 查找之前最近有分割線的position
val findOverPrePosition = findOverPrePosition(firstChildAdapterPosition)
if (findOverPrePosition != RecyclerView.NO_POSITION) {
//找到了最近的分割線
firstChildHaveOver = true
firstChildAdapterPosition = findOverPrePosition
}
}
if (firstChildHaveOver) {
val overStartPosition = findOverStartPosition(adapter, firstChildAdapterPosition)
if (overStartPosition == RecyclerView.NO_POSITION) {
clearOverDecoration()
return
}
//創建第一個位置的child 需要分割線
val firstViewHolder =
callback.createDecorationOverView.invoke(
parent,
adapter,
overStartPosition
)
val overView = firstViewHolder.itemView
tempRect.set(overView.left, overView.top, overView.right, overView.bottom)
val nextViewHolder = childViewHolder(parent, findGridNextChildIndex())
if (nextViewHolder != null) {
//緊挨着的下一個child也有分割線, 監測是否需要上推
if (callback.haveOverDecoration.invoke(nextViewHolder.adapterPosition) &&
!callback.isOverDecorationSame.invoke(
adapter,
firstChildAdapterPosition,
nextViewHolder.adapterPosition
)
) {
//不同的分割線, 實現上推效果
if (nextViewHolder.itemView.top < overDecorationRect.height()) {
tempRect.offsetTo(
0,
nextViewHolder.itemView.top - overDecorationRect.height()
)
}
}
}
if (overStartPosition == firstChildAdapterPosition && viewHolder.itemView.top == 0) {
//第一個child, 正好是 分割線的開始位置
clearOverDecoration()
} else {
if (overAdapterPosition != overStartPosition) {
clearOverDecoration()
overViewHolder = firstViewHolder
overDecorationRect.set(tempRect)
overAdapterPosition = overStartPosition
} else if (overDecorationRect != tempRect) {
overDecorationRect.set(tempRect)
}
}
} else {
//當前位置不需要分割線
clearOverDecoration()
}
}
}
}
}
}
爲了支持Touch/Drawable狀態的妥協方法
由於分割線是draw
出來的, 所以 touch
事件是傳遞不到分割線上的. 即時你通過Rect
和touch x,y
來判斷, 也只能實現一部分效果.
Drawable
就不好控制了, 裏面的子View
, 就更不好控制了.
所以…
我這裏用了一個騷操作:
將分割線的view
, 同時attach
到window
上.
/**
* 添加懸浮view 到 Activity, 目的是爲了 系統接管 懸浮View的touch事件以及drawable的state
* */
private fun addHoverView(view: View) {
if (view.parent == null) {
windowContent?.addView(
view, 0,
FrameLayout.LayoutParams(overDecorationRect.width(), overDecorationRect.height())
)
}
}
再將touch
事件, 轉發到View
到, 就可以完美實現Touch
的控制, 和Drawable
狀態的控制.
private val itemTouchListener = object : RecyclerView.SimpleOnItemTouchListener() {
override fun onInterceptTouchEvent(recyclerView: RecyclerView, event: MotionEvent): Boolean {
val action = event.actionMasked
if (action == MotionEvent.ACTION_DOWN) {
isDownInHoverItem = overDecorationRect.contains(event.x.toInt(), event.y.toInt())
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
isDownInHoverItem = false
}
if (isDownInHoverItem) {
onTouchEvent(recyclerView, event)
}
return isDownInHoverItem
}
override fun onTouchEvent(recyclerView: RecyclerView, event: MotionEvent) {
if (isDownInHoverItem) {
overViewHolder?.apply {
//一定要調用dispatchTouchEvent, 否則ViewGroup裏面的子View, 不會響應touchEvent
itemView.dispatchTouchEvent(event)
if (itemView is ViewGroup) {
if ((itemView as ViewGroup).onInterceptTouchEvent(event)) {
itemView.onTouchEvent(event)
}
} else {
itemView.onTouchEvent(event)
}
}
}
}
}
極簡使用方式
HoverItemDecoration().attachToRecyclerView(this) {
decorationOverLayoutType = {
R.layout.item_text
}
haveOverDecoration = {
overPositionList.contains(it)
}
}
其他部分, 請直接參考源碼:
磚廠地址: https://github.com/angcyo/HoverItemDecoration
羣內有各(pian)種(ni)各(jin)樣(qun)
的大佬,等你來撩.
聯繫作者
請使用QQ掃碼加羣, 小夥伴們都在等着你哦!
關注我的公衆號, 每天都能一起玩耍哦!