关联用户滑动效果
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
package com.example.defenseapplication.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewParent
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.OverScroller
|
||||
|
||||
class SlideLayout @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : FrameLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private val scroller = OverScroller(context)
|
||||
|
||||
private var downX: Float = 0f
|
||||
private var downY: Float = 0f
|
||||
private var lastX: Float = 0f
|
||||
|
||||
private var isHorizontalDragging: Boolean = false
|
||||
|
||||
private var listener: OnSlideChangeListener? = null
|
||||
private var shouldMenuBeOpen: Boolean = false
|
||||
|
||||
/** 缓存菜单宽度,避免 RecyclerView 复用时 measuredWidth 读取不到 */
|
||||
private var cachedMenuWidth: Int = 0
|
||||
|
||||
interface OnSlideChangeListener {
|
||||
fun onMenuOpen(slideLayout: SlideLayout)
|
||||
fun onMenuClose(slideLayout: SlideLayout)
|
||||
fun onClick(slideLayout: SlideLayout)
|
||||
}
|
||||
|
||||
fun setOnSlideChangeListener(listener: OnSlideChangeListener) {
|
||||
this.listener = listener
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
// 缓存菜单宽度,在 onLayout 和 onTouchEvent 中使用
|
||||
cachedMenuWidth = if (childCount >= 2) getChildAt(0).measuredWidth else 0
|
||||
}
|
||||
|
||||
private fun getMenuWidth(): Int {
|
||||
// 优先使用缓存值,RecyclerView 复用时 measuredWidth 可能尚未更新
|
||||
val w = cachedMenuWidth
|
||||
return if (w > 0) w else {
|
||||
if (childCount >= 2) {
|
||||
val measured = getChildAt(0).measuredWidth
|
||||
if (measured > 0) measured else getChildAt(0).width
|
||||
} else 0
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||
super.onLayout(changed, left, top, right, bottom)
|
||||
if (childCount < 2) return
|
||||
val contentWidth = right - left
|
||||
val menu = getChildAt(0)
|
||||
val menuW = getMenuWidth()
|
||||
// 内容左移 scrollX → 菜单右边缘逐渐露出
|
||||
// 菜单左边界 = 内容右边缘 - scrollX
|
||||
menu.layout(contentWidth - scrollX, top, contentWidth - scrollX + menuW, bottom)
|
||||
}
|
||||
|
||||
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
|
||||
when (ev.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
downX = ev.x
|
||||
downY = ev.y
|
||||
lastX = ev.x
|
||||
isHorizontalDragging = false
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
val dx = Math.abs(ev.x - downX)
|
||||
val dy = Math.abs(ev.y - downY)
|
||||
if (dx > dy && dx > 10f) {
|
||||
isHorizontalDragging = true
|
||||
disallowInterceptChain(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.onInterceptTouchEvent(ev)
|
||||
}
|
||||
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
downX = event.x
|
||||
downY = event.y
|
||||
lastX = event.x
|
||||
disallowInterceptChain(true)
|
||||
return true
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
val dx = lastX - event.x
|
||||
lastX = event.x
|
||||
|
||||
if (Math.abs(event.x - downX) > 10f) {
|
||||
isHorizontalDragging = true
|
||||
}
|
||||
|
||||
if (isHorizontalDragging) {
|
||||
val menuW = getMenuWidth()
|
||||
var newScrollX = scrollX + dx.toInt()
|
||||
if (newScrollX < 0) newScrollX = 0
|
||||
if (newScrollX > menuW) newScrollX = menuW
|
||||
scrollTo(newScrollX, 0)
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
val wasDragging = isHorizontalDragging
|
||||
isHorizontalDragging = false
|
||||
disallowInterceptChain(false)
|
||||
|
||||
if (!wasDragging) {
|
||||
listener?.onClick(this)
|
||||
} else {
|
||||
val menuW = getMenuWidth()
|
||||
if (scrollX > menuW / 2) {
|
||||
openMenu()
|
||||
} else {
|
||||
closeMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_CANCEL -> {
|
||||
isHorizontalDragging = false
|
||||
disallowInterceptChain(false)
|
||||
// 弹窗等外部中断:按当前菜单是否露出状态决定动画关闭或保持关闭
|
||||
if (scrollX > 0) {
|
||||
closeMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun disallowInterceptChain(disallow: Boolean) {
|
||||
var parent: ViewParent? = parent
|
||||
while (parent != null) {
|
||||
parent.requestDisallowInterceptTouchEvent(disallow)
|
||||
parent = parent.parent
|
||||
}
|
||||
}
|
||||
|
||||
override fun computeScroll() {
|
||||
if (scroller.computeScrollOffset()) {
|
||||
scrollTo(scroller.currX, scroller.currY)
|
||||
postInvalidateOnAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
fun openMenu() {
|
||||
val menuW = getMenuWidth()
|
||||
if (shouldMenuBeOpen && scrollX == menuW) return
|
||||
shouldMenuBeOpen = true
|
||||
scroller.startScroll(scrollX, 0, menuW - scrollX, 0, 300)
|
||||
postInvalidateOnAnimation()
|
||||
listener?.onMenuOpen(this)
|
||||
}
|
||||
|
||||
fun closeMenu() {
|
||||
if (!shouldMenuBeOpen && scrollX == 0) return
|
||||
shouldMenuBeOpen = false
|
||||
scroller.startScroll(scrollX, 0, -scrollX, 0, 300)
|
||||
postInvalidateOnAnimation()
|
||||
listener?.onMenuClose(this)
|
||||
}
|
||||
|
||||
fun isMenuOpened(): Boolean = shouldMenuBeOpen
|
||||
}
|
||||
Reference in New Issue
Block a user