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
|
package com.xiaoqu.watch.ui.home
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.GestureDetector
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
@@ -77,9 +75,6 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||||||
private var debugTapCount = 0
|
private var debugTapCount = 0
|
||||||
private var lastTapTime = 0L
|
private var lastTapTime = 0L
|
||||||
|
|
||||||
// ===== 下拉手势检测 =====
|
|
||||||
private lateinit var gestureDetector: GestureDetector
|
|
||||||
|
|
||||||
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentHomeBinding {
|
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentHomeBinding {
|
||||||
return FragmentHomeBinding.inflate(inflater, container, false)
|
return FragmentHomeBinding.inflate(inflater, container, false)
|
||||||
}
|
}
|
||||||
@@ -112,7 +107,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||||||
initConfigPage()
|
initConfigPage()
|
||||||
|
|
||||||
// 初始化下拉手势
|
// 初始化下拉手势
|
||||||
initGestureDetector()
|
initSwipeDownDetector()
|
||||||
|
|
||||||
// 启动时钟定时器
|
// 启动时钟定时器
|
||||||
startClockUpdater()
|
startClockUpdater()
|
||||||
@@ -150,9 +145,10 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||||||
showRevokeConfirm()
|
showRevokeConfirm()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 面板关闭时恢复 ViewPager2 滑动
|
// 面板关闭时恢复 ViewPager2 滑动和下拉检测
|
||||||
punchPanel.onDismiss = {
|
punchPanel.onDismiss = {
|
||||||
binding.viewPager.isUserInputEnabled = true
|
binding.viewPager.isUserInputEnabled = true
|
||||||
|
binding.swipeDownLayout.swipeEnabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,8 +204,9 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||||||
/** 展开打卡面板 */
|
/** 展开打卡面板 */
|
||||||
private fun showPunchPanel() {
|
private fun showPunchPanel() {
|
||||||
if (punchPanel.isShowing) return
|
if (punchPanel.isShowing) return
|
||||||
// 禁用 ViewPager2 滑动
|
// 禁用 ViewPager2 滑动和下拉检测
|
||||||
binding.viewPager.isUserInputEnabled = false
|
binding.viewPager.isUserInputEnabled = false
|
||||||
|
binding.swipeDownLayout.swipeEnabled = false
|
||||||
// 查询考勤状态
|
// 查询考勤状态
|
||||||
punchViewModel.fetchAttendance()
|
punchViewModel.fetchAttendance()
|
||||||
// 展开面板
|
// 展开面板
|
||||||
@@ -218,33 +215,17 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||||||
|
|
||||||
// ===== 下拉手势 =====
|
// ===== 下拉手势 =====
|
||||||
|
|
||||||
/** 初始化下拉手势检测器 */
|
/**
|
||||||
private fun initGestureDetector() {
|
* 初始化下拉手势检测
|
||||||
gestureDetector = GestureDetector(requireContext(),
|
* 使用 SwipeDownLayout 在 onInterceptTouchEvent 中检测下拉
|
||||||
object : GestureDetector.SimpleOnGestureListener() {
|
* 比 setOnTouchListener 更可靠——能在事件被子 View 消费前看到
|
||||||
override fun onFling(
|
*/
|
||||||
e1: MotionEvent?,
|
private fun initSwipeDownDetector() {
|
||||||
e2: MotionEvent,
|
binding.swipeDownLayout.onSwipeDown = {
|
||||||
velocityX: Float,
|
// 只在主页(page=1)且面板未展开时响应
|
||||||
velocityY: Float
|
if (binding.viewPager.currentItem == 1 && !punchPanel.isShowing) {
|
||||||
): 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()
|
showPunchPanel()
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 在 ViewPager2 上设置触摸监听
|
|
||||||
binding.viewPager.getChildAt(0)?.setOnTouchListener { _, event ->
|
|
||||||
gestureDetector.onTouchEvent(event)
|
|
||||||
false // 不消费事件,让 ViewPager2 正常处理
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- 首页容器:固定状态栏 + ViewPager2 + 打卡面板覆盖层 -->
|
<!-- 首页容器:固定状态栏 + 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_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/background">
|
android:background="@color/background">
|
||||||
@@ -43,4 +44,4 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
</FrameLayout>
|
</com.xiaoqu.watch.ui.widget.SwipeDownLayout>
|
||||||
|
|||||||
Reference in New Issue
Block a user