fix: 修复下拉手势无响应,改用自定义SwipeDownLayout
ViewPager2内部RecyclerView会拦截触摸事件,导致setOnTouchListener 和GestureDetector都收不到下拉手势。 改用SwipeDownLayout(自定义FrameLayout),在onInterceptTouchEvent中 检测下拉——事件到达子View之前就能观察到,原理同SwipeRefreshLayout。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
package com.xiaoqu.watch.ui.home
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.GestureDetector
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
@@ -77,9 +75,6 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
||||
private var debugTapCount = 0
|
||||
private var lastTapTime = 0L
|
||||
|
||||
// ===== 下拉手势检测 =====
|
||||
private lateinit var gestureDetector: GestureDetector
|
||||
|
||||
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentHomeBinding {
|
||||
return FragmentHomeBinding.inflate(inflater, container, false)
|
||||
}
|
||||
@@ -112,7 +107,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
||||
initConfigPage()
|
||||
|
||||
// 初始化下拉手势
|
||||
initGestureDetector()
|
||||
initSwipeDownDetector()
|
||||
|
||||
// 启动时钟定时器
|
||||
startClockUpdater()
|
||||
@@ -150,9 +145,10 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
||||
showRevokeConfirm()
|
||||
}
|
||||
|
||||
// 面板关闭时恢复 ViewPager2 滑动
|
||||
// 面板关闭时恢复 ViewPager2 滑动和下拉检测
|
||||
punchPanel.onDismiss = {
|
||||
binding.viewPager.isUserInputEnabled = true
|
||||
binding.swipeDownLayout.swipeEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,8 +204,9 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
||||
/** 展开打卡面板 */
|
||||
private fun showPunchPanel() {
|
||||
if (punchPanel.isShowing) return
|
||||
// 禁用 ViewPager2 滑动
|
||||
// 禁用 ViewPager2 滑动和下拉检测
|
||||
binding.viewPager.isUserInputEnabled = false
|
||||
binding.swipeDownLayout.swipeEnabled = false
|
||||
// 查询考勤状态
|
||||
punchViewModel.fetchAttendance()
|
||||
// 展开面板
|
||||
@@ -218,33 +215,17 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
||||
|
||||
// ===== 下拉手势 =====
|
||||
|
||||
/** 初始化下拉手势检测器 */
|
||||
private fun initGestureDetector() {
|
||||
gestureDetector = GestureDetector(requireContext(),
|
||||
object : GestureDetector.SimpleOnGestureListener() {
|
||||
override fun onFling(
|
||||
e1: MotionEvent?,
|
||||
e2: MotionEvent,
|
||||
velocityX: Float,
|
||||
velocityY: Float
|
||||
): Boolean {
|
||||
// 只在主页(page=1)且面板未展开时响应下拉
|
||||
if (binding.viewPager.currentItem != 1) return false
|
||||
if (punchPanel.isShowing) return false
|
||||
|
||||
// 下拉:velocityY > 0 且垂直速度大于水平速度
|
||||
if (velocityY > 500 && Math.abs(velocityY) > Math.abs(velocityX)) {
|
||||
showPunchPanel()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
// 在 ViewPager2 上设置触摸监听
|
||||
binding.viewPager.getChildAt(0)?.setOnTouchListener { _, event ->
|
||||
gestureDetector.onTouchEvent(event)
|
||||
false // 不消费事件,让 ViewPager2 正常处理
|
||||
/**
|
||||
* 初始化下拉手势检测
|
||||
* 使用 SwipeDownLayout 在 onInterceptTouchEvent 中检测下拉
|
||||
* 比 setOnTouchListener 更可靠——能在事件被子 View 消费前看到
|
||||
*/
|
||||
private fun initSwipeDownDetector() {
|
||||
binding.swipeDownLayout.onSwipeDown = {
|
||||
// 只在主页(page=1)且面板未展开时响应
|
||||
if (binding.viewPager.currentItem == 1 && !punchPanel.isShowing) {
|
||||
showPunchPanel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.xiaoqu.watch.ui.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import android.widget.FrameLayout
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
* 支持下拉手势检测的 FrameLayout
|
||||
* 在 onInterceptTouchEvent 中检测向下滑动,触发回调
|
||||
* 不消费事件,子 View 的水平滑动(如 ViewPager2)不受影响
|
||||
*
|
||||
* 原理同 SwipeRefreshLayout:在事件分发给子 View 之前先观察,
|
||||
* 只有明确的垂直下拉才拦截
|
||||
*/
|
||||
class SwipeDownLayout @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : FrameLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
companion object {
|
||||
/** 最小下拉距离(像素) */
|
||||
private const val SWIPE_THRESHOLD_PX = 80
|
||||
/** 触摸容差:垂直距离必须大于水平距离的倍数 */
|
||||
private const val DIRECTION_RATIO = 1.5f
|
||||
}
|
||||
|
||||
/** 下拉触发回调 */
|
||||
var onSwipeDown: (() -> Unit)? = null
|
||||
|
||||
/** 是否启用下拉检测(面板打开时禁用) */
|
||||
var swipeEnabled = true
|
||||
|
||||
private var startX = 0f
|
||||
private var startY = 0f
|
||||
private var swiped = false
|
||||
|
||||
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
|
||||
if (!swipeEnabled) return false
|
||||
|
||||
when (ev.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
startX = ev.x
|
||||
startY = ev.y
|
||||
swiped = false
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
if (swiped) return false
|
||||
|
||||
val dy = ev.y - startY
|
||||
val dx = ev.x - startX
|
||||
|
||||
// 向下滑动超过阈值,且垂直方向为主
|
||||
if (dy > SWIPE_THRESHOLD_PX && abs(dy) > abs(dx) * DIRECTION_RATIO) {
|
||||
swiped = true
|
||||
onSwipeDown?.invoke()
|
||||
// 不拦截事件(return false),让子 View 继续处理
|
||||
// 这样 ViewPager2 的左右滑动不会被打断
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||
swiped = false
|
||||
}
|
||||
}
|
||||
// 始终返回 false:不拦截事件,只观察
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 首页容器:固定状态栏 + ViewPager2 + 打卡面板覆盖层 -->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<com.xiaoqu.watch.ui.widget.SwipeDownLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/swipeDownLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/background">
|
||||
@@ -43,4 +44,4 @@
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
</com.xiaoqu.watch.ui.widget.SwipeDownLayout>
|
||||
|
||||
Reference in New Issue
Block a user