要点
滑动——手指在屏幕表面快速滑动的动作,以抬起手指结束。滑动可以是短促的(滚动元素)或长距离的(打开帘幕),但关键特征是离散性:系统将其识别为单个事件,而不是连续的坐标序列。
移动操作系统根据三个参数定义滑动:最小距离(阈值)、最小速度(velocity)和方向。如果手指移动缓慢或距离不足,系统将手势解释为平移(pan)或点击。阈值因平台而异:iOS 使用 UISwipeGestureRecognizer 常量固定阈值,Android 使用 ViewConfiguration.getScaledTouchSlop(),根据屏幕密度返回约 8–16 dp。
根据 Android Developers Documentation,GestureDetector 中的 onFling(滑动)在速度超过 100 px/s 且距离超过 50px 时触发。在 IT Sectr,我们将滑动用于导航(图库、卡片界面、引导)——这种手势对所有年龄段的用户都直观易懂。
滑动和平移(pan/drag)——不同类型的手势,尽管两者都使用手指在屏幕上的移动。滑动是离散的,意思是„执行一个操作”,而平移是连续的,意思是„只要手指在屏幕上就移动对象”。
| 参数 | 滑动(Swipe) | 平移(Pan/Drag) |
|---|---|---|
| 类型 | 离散 | 连续 |
| 事件 | 一个(识别/未识别) | 多个(.began → .changed → .ended) |
| 速度 | 高(快速移动) | 任意(慢速移动) |
| iOS 示例 | UISwipeGestureRecognizer | UIPanGestureRecognizer |
| Android 示例 | GestureDetector.onFling() | GestureDetector.onScroll() |
| 场景 | 删除邮件、翻页 | 移动地图、拖拽文件 |
| 坐标追踪 | 否(仅方向) | 是(持续更新坐标) |
在滑动和平移之间的选择取决于任务。对于元素的操作(删除、归档、打开菜单),使用滑动。对于直接操作(移动、缩放)——使用平移。在同一元素上混合手势需要 require(toFail:) 或自定义逻辑。
UISwipeGestureRecognizer——UIGestureRecognizer 的子类,识别四个方向之一的离散滑动。与 UIPanGestureRecognizer 不同,它不生成中间 .changed 事件:识别时 action 仅被调用一次。
direction 属性(UISwipeGestureRecognizer.Direction)设置跟踪的方向——.left、.right、.up、.down。可以通过掩码组合:水平滑动使用 [.left, .right]。numberOfTouchesRequired 属性确定手指数量——默认为 1。UISwipeGestureRecognizer 没有可配置的距离阈值——iOS 使用约 20px 的内置值,足以进行可靠识别。
由于 UISwipeGestureRecogniser 是离散识别器,它与 UIPanGestureRecognizer 不冲突:慢速移动时触发 pan,快速移动时触发 swipe。但是当与 UITapGestureRecognizer 共存于同一视图时,可能需要 require(toFail:) 以防止误识别点击。
在 Android 中,识别滑动的主要 API 是GestureDetector 及其回调 OnGestureListener,具体是 onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) 方法。GestureDetector 分析手指的起始和结束位置、沿轴移动的速度和距离,然后在超过阈值时调用 onFling。
最小阈值通过 ViewConfiguration 配置:VelocityTracker.getMinVelocity()(约 100 px/s)和 ViewConfiguration.getScaledTouchSlop()(约 8–16 dp)。自定义 OnSwipeTouchListener 是一种简化 API 的常见模式:开发者无需手动计算方向和速度。Android Support Library 不包含内置的„SwipeDetector”,因此自定义包装器是标准解决方案。
对于列表中的滑动,Google 建议使用 RecyclerView 中的 ItemTouchHelper.SimpleCallback,它抽象了手势检测、动画和 onSwiped() 回调。在 IT Sectr,我们为 ViewPager2 和卡片界面使用自定义 SwipeListener——这提供了对滑动灵敏度的控制,同时不影响性能。
对于列表(UITableView、RecyclerView),Apple 和 Google 提供专门的 API,替代手动添加 UISwipeGestureRecognizer。这些 API 支持操作按钮、自定义动画和„滑动删除”手势——移动应用的标准模式。
在 iOS(11+)中使用 UISwipeActionsConfiguration:tableView(_:leadingSwipeActionsConfigurationForRowAt:) 方法返回 UIContextualAction 数组——带有标题、样式(destructive、normal)和处理程序的按钮。例如,„删除”——红色背景的 .destructive,„归档”——灰色的 .normal。系统自动为按钮的出现添加动画,并在按下后调用 completion 块。
在 Android 中,ItemTouchHelper.SimpleCallback 通过 ItemTouchHelper.attachToRecyclerView() 附加到 RecyclerView。参数——方向标志(start、end、up、down)和回调 onSwiped(viewHolder, direction)。在 onSwiped 内部调用 adapter.notifyItemRemoved(position) 以动画方式删除元素。ItemTouchHelper 自动处理手势、与滚动的冲突以及滑动未完成时元素的恢复。根据 Google I/O 2024 的数据,ItemTouchHelper 被 70% 的具有列表滑动操作的 Android 应用使用。
将 UISwipeGestureRecognizer 添加到视图以返回上一个屏幕。代码展示了使用 .left 方向的识别器的最小配置。
import UIKit
class CardViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let swipeLeft = UISwipeGestureRecognizer(
target: self,
action: #selector(handleSwipeLeft(_:))
)
swipeLeft.direction = .left
swipeLeft.numberOfTouchesRequired = 1
view.addGestureRecognizer(swipeLeft)
}
@objc private func handleSwipeLeft(_: UISwipeGestureRecognizer) {
guard let nav = navigationController else { return }
if nav.viewControllers.count > 1 {
nav.popViewController(animated: true)
} else {
nav.dismiss(animated: true)
}
}
}
识别器绑定到控制器的根视图。navigationController.viewControllers.count 的检查确保 pop 仅在堆栈中存在上一个屏幕时执行。对于模态控制器使用 dismiss。代码不需要状态检查——UISwipeGestureRecognizer 是离散的,仅在成功识别时调用 action。
实现一个简单的 GestureDetector 包装器,确定滑动方向并调用 onSwipeLeft/Right 回调。
import android.content.Context
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
open class OnSwipeTouchListener(context: Context) : View.OnTouchListener {
private val gestureDetector = GestureDetector(context, GestureListener())
override fun onTouch(v: View, event: MotionEvent): Boolean {
return gestureDetector.onTouchEvent(event)
}
private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
companion object {
private const val SWIPE_THRESHOLD = 100
private const val SWIPE_VELOCITY_THRESHOLD = 100
}
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
val diffX = e2.x - e1!!.x
val diffY = e2.y - e1.y
if (Math.abs(diffX) > Math.abs(diffY)) {
if (Math.abs(diffX) > SWIPE_THRESHOLD
&& Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD
) {
if (diffX > 0) onSwipeRight()
else onSwipeLeft()
return true
}
} else {
if (Math.abs(diffY) > SWIPE_THRESHOLD
&& Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD
) {
if (diffY > 0) onSwipeDown()
else onSwipeUp()
return true
}
}
return false
}
}
open fun onSwipeLeft() {}
open fun onSwipeRight() {}
open fun onSwipeUp() {}
open fun onSwipeDown() {}
}
OnSwipeTouchListener 扩展 SimpleOnGestureListener,实现 onFling。diffX/diffY 确定方向,SWIPE_THRESHOLD(100px)和 SWIPE_VELOCITY_THRESHOLD(100 px/s)——阈值。用法:view.setOnTouchListener(OnSwipeTouchListener(context).apply { onSwipeLeft = { dismiss() } })。
在向左滑动表格单元格时添加删除按钮。使用 iOS 11+ 的内置 API,无需手动手势识别。
func tableView(
_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath
) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(
style: .destructive,
title: "删除"
) { _, _, completionHandler in
items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
completionHandler(true)
}
deleteAction.backgroundColor = .systemRed
let config = UISwipeActionsConfiguration(actions: [deleteAction])
config.performsFirstActionWithFullSwipe = true
return config
}
该方法返回 UISwipeActionsConfiguration 及 UIContextualAction 数组。style: .destructive 自动设置红色背景。performsFirstActionWithFullSwipe = true 允许通过完整滑动执行操作而无需额外按压。在 IT Sectr,我们将此 API 用于所有带操作的列表——它是标准化的,支持 VoiceOver,且不需要自定义 GestureRecognizer。
常见问题
滑动是离散手势,快速移动并抬起手指;平移是连续拖拽,追踪坐标。UISwipeGestureRecognizer 触发一次,UIPanGestureRecognizer 生成 .began → .changed → .ended。选择取决于场景:滑动用于操作(删除、翻页),平移用于移动(地图、滑块)。
手势逻辑相同,但 API 不同。iOS 使用UISwipeGestureRecognizer 配合 direction。Android 使用 GestureDetector.onFling() 配合自定义阈值。结果——不同实现下的相同用户体验。跨平台框架(Flutter、React Native)通过统一的滑动 API 抽象了差异。
在 iOS(11+)中,请在 UITableViewDelegate 中使用UISwipeActionsConfiguration。在 Android 中——为 RecyclerView 使用ItemTouchHelper.SimpleCallback。这些 API 替代了手动 GestureRecognizer,支持操作按钮和动画,正确处理与滚动的冲突,并适应平台 HIG。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。