fix: 下拉手势改用Activity.dispatchTouchEvent检测

前两种方案(SwipeDownLayout.onInterceptTouchEvent、
RecyclerView.OnItemTouchListener)均无法检测到下拉。

改用Activity.dispatchTouchEvent——触摸事件链最顶层,
在任何View处理之前就能看到所有触摸事件,不可能被拦截。

HomeFragment注册回调,onDestroyView时清理避免泄漏。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
dongliang
2026-04-28 21:13:46 +09:30
parent 68f3b2d890
commit b274012019
2 changed files with 51 additions and 43 deletions

View File

@@ -2,6 +2,7 @@ package com.xiaoqu.watch.app
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import android.os.Bundle import android.os.Bundle
import android.view.MotionEvent
import android.view.View import android.view.View
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@@ -10,6 +11,7 @@ import com.xiaoqu.watch.service.manager.SystemStateMonitor
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.abs
/** /**
* 主 ActivityLauncher 模式,单 Activity + 多 Fragment 架构) * 主 ActivityLauncher 模式,单 Activity + 多 Fragment 架构)
@@ -54,6 +56,43 @@ class MainActivity : AppCompatActivity() {
systemStateMonitor.unregister() systemStateMonitor.unregister()
} }
// ===== 下拉手势检测Activity 级别,不可能被子 View 拦截) =====
/** 下拉回调(由 HomeFragment 注册) */
var onSwipeDown: (() -> Unit)? = null
private var touchStartY = 0f
private var touchStartX = 0f
private var swipeTriggered = false
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
// 在事件分发给任何 View 之前,先检测下拉手势
if (onSwipeDown != null) {
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
touchStartY = ev.rawY
touchStartX = ev.rawX
swipeTriggered = false
}
MotionEvent.ACTION_MOVE -> {
if (!swipeTriggered) {
val dy = ev.rawY - touchStartY
val dx = ev.rawX - touchStartX
// 下滑超过 50px 且垂直方向为主
if (dy > 50 && abs(dy) > abs(dx) * 1.5f) {
swipeTriggered = true
onSwipeDown?.invoke()
}
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
swipeTriggered = false
}
}
}
return super.dispatchTouchEvent(ev)
}
/** /**
* 物理返回键拦截: * 物理返回键拦截:
* - 已绑定用户 → 开启 NFC 打卡模式(后续模块实现) * - 已绑定用户 → 开启 NFC 打卡模式(后续模块实现)

View File

@@ -2,12 +2,10 @@ package com.xiaoqu.watch.ui.home
import android.os.Bundle import android.os.Bundle
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
import android.widget.Toast import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
@@ -124,6 +122,12 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
observePunchState() observePunchState()
} }
override fun onDestroyView() {
super.onDestroyView()
// 清理 Activity 下拉回调,避免泄漏
(activity as? com.xiaoqu.watch.app.MainActivity)?.onSwipeDown = null
}
// ===== 打卡面板 ===== // ===== 打卡面板 =====
/** 初始化打卡面板 */ /** 初始化打卡面板 */
@@ -215,52 +219,17 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
// ===== 下拉手势 ===== // ===== 下拉手势 =====
/** 触摸起始 Y */
private var touchStartY = 0f
/** 触摸起始 X */
private var touchStartX = 0f
/** 本次手势是否已触发过下拉 */
private var swipeTriggered = false
/** /**
* 初始化下拉手势检测 * 初始化下拉手势检测
* 通过 RecyclerView.OnItemTouchListener 监听 ViewPager2 内部触摸 * 在 Activity.dispatchTouchEvent 中检测——触摸事件链最顶层,不会被任何子 View 拦截
* 这是最可靠的方式——RecyclerView 在处理事件前会先通知所有 listener
*/ */
private fun initSwipeDownDetector() { private fun initSwipeDownDetector() {
// ViewPager2 内部 RecyclerView 需等布局完成后获取 (activity as? com.xiaoqu.watch.app.MainActivity)?.onSwipeDown = {
binding.viewPager.post {
val recyclerView = binding.viewPager.getChildAt(0) as? RecyclerView
recyclerView?.addOnItemTouchListener(object : RecyclerView.SimpleOnItemTouchListener() {
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
when (e.action) {
MotionEvent.ACTION_DOWN -> {
touchStartY = e.rawY
touchStartX = e.rawX
swipeTriggered = false
}
MotionEvent.ACTION_MOVE -> {
if (swipeTriggered) return false
// 只在主页page=1且面板未展开时响应 // 只在主页page=1且面板未展开时响应
if (binding.viewPager.currentItem != 1) return false if (binding.viewPager.currentItem == 1 && !punchPanel.isShowing) {
if (punchPanel.isShowing) return false
val dy = e.rawY - touchStartY
val dx = e.rawX - touchStartX
// 下滑超过 60px 且方向以垂直为主
if (dy > 60 && Math.abs(dy) > Math.abs(dx) * 1.5f) {
swipeTriggered = true
showPunchPanel() showPunchPanel()
} }
} }
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
swipeTriggered = false
}
}
return false // 不拦截ViewPager2 正常工作
}
})
}
} }
// ===== 主页 ===== // ===== 主页 =====