Harmony OS 长按与滑动手势交互探秘
本文介绍了鸿蒙HarmonyOS NEXT/5.0/API12+版本中实现手势交互功能的技术方案。通过@State装饰器管理组件状态,@Builder构建对话框内容,整合长按和滑动手势实现交互逻辑。关键点包括:1) 使用@State管理对话框显示状态、操作模式和窗口宽度;2) 通过GestureGroup实现长按显示对话框和滑动选择模式;3) 在生命周期方法中获取窗口尺寸;4) 使用@Extend
一、适用版本
本文所涉及的功能适用于 Harmony OS NEXT / 5.0 / API 12 + 版本。在这些版本的鸿蒙系统上,开发者能够借助相关 API 实现丰富且流畅的手势交互功能,为应用增添独特魅力。
二、效果展示

三、实现逻辑
(一)组件状态管理
通过 @State 装饰器定义了三个关键状态变量,它们如同组件的 “指挥棒”,掌控着整个交互过程的状态变化。
showDialog用于控制弹出对话框的显示与隐藏,就像舞台的幕布开关,决定着特定交互界面是否呈现给用户。初始值为false,意味着默认情况下对话框是隐藏的。currentMode表示当前的操作模式,其类型为自定义的SelectType枚举。枚举包含DELETE、TEXT和NONE三种模式,分别对应不同的操作含义。currentMode的初始值为SelectType.NONE,表示初始状态下没有特定的操作模式。winWidth用于存储设备窗口的宽度,这对于判断滑动操作的位置至关重要。初始值为0,随后会在组件即将显示时获取实际的窗口宽度。
(二)对话框内容构建
利用 @Builder 装饰器创建 getCountUI 方法,这个方法就像是一个精巧的设计师,负责构建弹出对话框的具体内容。
- 首先构建一个垂直方向的
Column布局,作为对话框的整体框架。 - 在
Column布局内放置一个水平方向的Row布局,用于排列操作选项。Row布局中的两个Text组件分别显示 “删除” 和 “文字”,它们通过调用扩展方法newExtendText来设置统一的样式,包括固定的宽高、圆角、文本居中对齐、白色字体以及旋转角度等。并且,这两个Text组件的背景颜色会根据currentMode的值动态变化,直观地向用户展示当前所处的操作模式。 Row布局设置了height、width和justifyContent属性,使其内部元素均匀分布在水平方向上,且占据整个父容器的空间。- 最后,
Column布局设置了整体的width、height和backgroundColor,为对话框营造出一个半透明的遮罩效果,突出对话框内的操作选项。
(三)手势交互逻辑
- 长按手势:在
Button组件上绑定了GestureGroup,其中包含长按手势LongPressGesture。当用户长按按钮时,onAction事件触发,将showDialog设置为true,如同拉开舞台幕布,显示出对话框。当长按结束时,onActionEnd事件触发,将showDialog设置为false,关闭对话框。 - 滑动手势:
GestureGroup中还包含滑动手势PanGesture。在滑动过程中,onActionUpdate事件获取手指在屏幕上的全局X坐标,并与窗口宽度的一半进行比较。如果手指的X坐标小于窗口宽度的一半,将currentMode设置为SelectType.DELETE;否则,设置为SelectType.TEXT。当滑动结束时,onActionEnd事件将currentMode重置为SelectType.NONE,为下一次操作做好准备。
(四)组件生命周期处理
- 获取窗口宽度:在组件即将显示时,
aboutToAppear生命周期方法被调用。通过display.getAllDisplays()获取设备的所有显示信息,并从返回结果中提取第一个显示设备的宽度,赋值给winWidth。这一步为后续判断滑动操作的位置提供了关键依据。 - 动态更新窗口宽度:当页面区域发生变化时,
onAreaChange事件被触发,更新winWidth的值,确保在窗口大小改变的情况下,手势交互逻辑依然能够准确运行。
(五)页面布局与交互整合
在 build 方法中,通过 Column 布局构建页面结构。页面由一个包含 Button 的 Row 组成,Button 设置了宽度、内边距和按钮类型。通过 gesture 方法绑定 GestureGroup,将长按和滑动手势整合到按钮的交互中。最后,使用 bindContentCover 方法将对话框与页面进行绑定,并设置 modalTransition 为 ModalTransition.NONE,实现对话框以无过渡效果的方式显示在页面上。
四、源码解析
import { display, promptAction } from '@kit.ArkUI'
@Entry
@Component
struct LongPressGesturePage {
@State showDialog: boolean = false
@State currentMode: SelectType = SelectType.NONE
@State winWidth: number = 0
// 拖地获取尺寸
aboutToAppear(): void {
display.getAllDisplays()
.then(res => {
this.winWidth = res[0].width as number
})
}
@Builder
getCountUI() {
Column() {
Row() {
Text("删除")
.newExtendText()
.backgroundColor(this.currentMode === SelectType.DELETE? Color.Red : Color.Gray)
Text("文字")
.newExtendText()
.backgroundColor(this.currentMode === SelectType.TEXT? Color.Green : Color.Gray)
.rotate({
angle: 20
})
}
.height('100%')
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)
}
.width('100%')
.height('100%')
.backgroundColor("rgba(0,0,0, 0.25)")
}
build() {
Column() {
Row() {
Button('语 音')
.width('80%')
.padding(10)
.type(ButtonType.Normal)
.gesture(
GestureGroup(GestureMode.Parallel,
LongPressGesture()
.onAction(() => {
this.showDialog = true
})
.onActionEnd(() => {
this.showDialog = false
}),
PanGesture()
.onActionUpdate((e) => {
let figerX = e.fingerList[0].globalX.toString()
if (this.winWidth / 2 > Number(figerX)) {
this.currentMode = SelectType.DELETE
} else {
this.currentMode = SelectType.TEXT
}
})
.onActionEnd(() => {
this.currentMode = SelectType.NONE
})
)
)
}
.padding(20)
.height('100%')
.alignItems(VerticalAlign.Bottom)
}
.width('100%')
.bindContentCover(this.showDialog, this.getCountUI(), { modalTransition: ModalTransition.NONE })
.onAreaChange((oldW, newW) => {
this.winWidth = newW.width as number
})
}
}
enum SelectType {
DELETE,
TEXT,
NONE
}
@Extend(Text)
function newExtendText() {
.width(50)
.height(50)
.borderRadius(25)
.textAlign(TextAlign.Center)
.fontColor(Color.White)
.rotate({
angle: -20
})
}
- 导入模块:从
@kit.ArkUI导入display和promptAction模块。display用于获取设备显示相关信息,在代码中用于获取窗口宽度;promptAction虽然在当前代码中未实际使用,但它通常用于显示弹窗等提示信息,为后续功能扩展提供了可能性。 - 组件定义:使用
@Entry和@Component装饰器定义LongPressGesturePage组件,这是整个交互功能的核心组件。 - 状态变量:如前文所述,
showDialog、currentMode和winWidth三个状态变量分别控制对话框显示、操作模式和窗口宽度,它们在组件的交互过程中起着关键作用。 aboutToAppear方法:在组件即将显示时,通过display.getAllDisplays()获取设备的显示信息,并从中提取窗口宽度赋值给winWidth,确保在组件显示时能够准确获取窗口尺寸,为手势交互提供准确的数据支持。getCountUI方法:通过@Builder装饰器构建对话框的 UI 结构。内部使用Column和Row布局,以及Text组件来展示操作选项,并根据currentMode的值动态改变Text组件的背景颜色,同时为Text组件设置了统一的样式。build方法:构建页面的整体布局。Button组件绑定了包含长按和滑动手势的GestureGroup,实现了按钮的长按和滑动交互功能。通过bindContentCover方法将对话框与页面进行绑定,实现对话框的弹出显示。onAreaChange方法用于在页面区域变化时更新winWidth的值,保证手势交互逻辑在窗口大小改变时依然准确。- 枚举定义:
SelectType枚举定义了三种操作模式,为currentMode提供了取值范围,使代码的可读性和可维护性更强。 - 扩展方法:通过
@Extend(Text)为Text组件扩展newExtendText方法,为Text组件设置了固定的宽高、圆角、文本对齐方式、字体颜色和旋转角度等样式,简化了Text组件的样式设置过程。
五、可能的优化方向
- 增加操作反馈:在用户进行长按和滑动操作时,可以添加一些视觉或触觉反馈,如按钮的轻微缩放、震动反馈等,让用户更清晰地感知到操作的响应,提升交互的趣味性和直观性。
- 优化对话框样式:当前对话框的样式较为简单,可以进一步优化,如添加阴影效果、调整透明度渐变等,使其在视觉上更加突出和美观,与应用的整体风格更加契合。
- 代码结构优化:可以将手势相关的逻辑提取到单独的方法中,使
build方法更加简洁明了,提高代码的可读性和可维护性。例如:
private setupGestures() {
return GestureGroup(GestureMode.Parallel,
LongPressGesture()
.onAction(() => {
this.showDialog = true
})
.onActionEnd(() => {
this.showDialog = false
}),
PanGesture()
.onActionUpdate((e) => {
let figerX = e.fingerList[0].globalX.toString()
if (this.winWidth / 2 > Number(figerX)) {
this.currentMode = SelectType.DELETE
} else {
this.currentMode = SelectType.TEXT
}
})
.onActionEnd(() => {
this.currentMode = SelectType.NONE
})
)
}
build() {
Column() {
Row() {
Button('语 音')
.width('80%')
.padding(10)
.type(ButtonType.Normal)
.gesture(this.setupGestures())
}
.padding(20)
.height('100%')
.alignItems(VerticalAlign.Bottom)
}
.width('100%')
.bindContentCover(this.showDialog, this.getCountUI(), { modalTransition: ModalTransition.NONE })
.onAreaChange((oldW, newW) => {
this.winWidth = newW.width as number
})
}
4.错误处理优化:在获取窗口宽度的过程中,display.getAllDisplays() 方法返回的 Promise 可能会 reject,当前代码没有处理这种情况。可以添加错误处理逻辑,例如:
aboutToAppear(): void {
display.getAllDisplays()
.then(res => {
this.winWidth = res[0].width as number
})
.catch(error => {
console.error('获取屏幕宽度失败:', error)
// 可以在这里添加提示用户的逻辑,比如显示一个错误弹窗
})
}
关于鸿蒙应用开发中手势交互的更多技巧和优化方法,我在博客中还有更详细的分享,感兴趣的话不妨前往查看,相信能为你的开发工作带来更多启发。你在开发过程中有没有遇到过类似的手势交互问题呢?欢迎在评论区留言交流。
更多推荐



所有评论(0)