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 }