- UI组件
- 移动开发
- OpenHarmony
【免费下载链接】swipe-item-cj
自定义侧滑操作UI组件
swipe-item-cj 是一个基于 Cangjie(仓颉)语言、运行于 OpenHarmony 应用层的自定义侧滑操作 UI 组件库,核心能力是为任意内容组件(如列表项)附加左侧与右侧两个可滑出的操作区。本文以特性文档 doc/feature_api.md 为主线,完整讲解
SwipeItemLayout组件全部 API 参数、可运行的示例代码、滑动交互的底层实现原理,并给出接入自有工程的两种方式,读完即可在 Cangjie 应用中实现"左滑删除、右滑选择"等交互。
一、组件定位:为列表项附加左右操作区的滑动容器
按特性文档的定位,"swipe-item-cj 支持组件左右滑动",仓库 README 进一步描述为"支持组件左侧和右侧的滑动布局"。它不依赖任何第三方滑动框架,而是由swipeitem库中的SwipeItemLayout组件独立实现一套手势识别 + 位移动画逻辑,典型应用场景包括:
- 列表项左滑露出"删除"操作(如邮件、消息列表);
- 列表项右滑露出"选择/置顶/收藏"等操作;
- 全宽操作面板:右滑区域占满屏幕宽度,承载图文、按钮组合(仓库示例
LargrWidthView即是)。
仓库目录结构与本文主题的对应关系如下:
| 目录/文件 | 作用 |
|---|---|
| swipeitem/src/main/cangjie/src/swipe_item_layout.cj | 左右侧滑动容器核心实现 |
| entry/src/main/cangjie/src/page/ | 三个示例页面(单条、列表、全宽) |
| doc/feature_api.md | 组件 API 特性文档(本文主体) |
| CHANGELOG.md | 版本记录:v0.0.1 支持左右侧滑动布局 |
二、SwipeItemLayout 组件 API 全量参数说明
SwipeItemLayout是一个@Component公开组件,对外暴露 12 个参数(含 3 个@BuilderParam、2 个@Link)。特性文档中的声明如下:
@Component public class SwipeItemLayout { @BuilderParam let surfaceView: (String, Int64) -> Unit // 滑动组件 @Link var itemContent: String = "" // 组件内容 @Link var itemId: Int64 = 0 // 组件索引 @BuilderParam let swipeRightView: (Int64) -> Unit // 设置右侧滑动布局样式 @BuilderParam let swipeLeftView: (Int64) -> Unit // 设置左侧滑动布局样式 var mWidth: Float64 // 设置组件宽度,单位 百分比,范围(0 ~ 100.0) var mHeight: Float64 // 设置组件高度,单位 vp,当小于 0.0 时,取 0.0 var swipeRightWidth: Float64 // 设置右侧布局宽度,单位 vp,范围(0 ~ 当前设备屏幕宽度) var swipeLeftWidth: Float64 // 设置左侧布局宽度,单位 vp,范围(0 ~ 当前设备屏幕宽度) var rightDisable: Bool // 是否禁用右侧滑动,默认 false var leftDisable: Bool // 是否禁用左侧滑动,默认 false var mDuration: Int32 // 设置动画持续时间,默认 200 ms }对照核心实现 swipe_item_layout.cj,各参数的类型、默认值与语义汇总如下:
| 参数 | 类型 | 默认值(源码) | 语义与取值约定 |
|---|---|---|---|
surfaceView | @BuilderParam (String, Int64) -> Unit | — | 内容层构建器,接收(itemContent, itemId),即被滑动的表面组件 |
swipeRightView | @BuilderParam (Int64) -> Unit | — | 右侧滑动布局构建器,接收itemId |
swipeLeftView | @BuilderParam (Int64) -> Unit | — | 左侧滑动布局构建器,接收itemId |
itemContent | @Link String | "" | 组件内容文本,与外部状态双向同步 |
itemId | @Link Int64 | 0 | 组件索引,供构建器区分不同列表项 |
mWidth | Float64 | 100.0 | 组件宽度,单位百分比,范围0 ~ 100.0;源码在aboutToAppear中会将超过 100.0 的值钳制为 100.0 |
mHeight | Float64 | 90.0 | 组件高度,单位 vp;特性文档约定当小于0.0时取0.0 |
swipeRightWidth | Float64 | 120.0 | 右侧布局宽度,单位 vp,范围0 ~ 当前设备屏幕宽度 |
swipeLeftWidth | Float64 | 120.0 | 左侧布局宽度,单位 vp,范围0 ~ 当前设备屏幕宽度 |
rightDisable | Bool | false | 为true时禁用右侧滑动 |
leftDisable | Bool | false | 为true时禁用左侧滑动 |
mDuration | Int32 | 200 | 展开/收回动画持续时间,单位 ms |
三条关键布局约定
特性文档特别给出三条推荐,直接决定滑动效果是否正常显示:
surfaceView(滑动内容组件)的宽度、高度推荐与组件的mWidth、mHeight保持一致;- 右侧滑动布局(文档中称为
bottomViewRight,代码中对应swipeRightView)的宽度推荐与swipeRightWidth保持一致; - 左侧滑动布局(文档中称为
bottomViewLeft,代码中对应swipeLeftView)的宽度推荐与swipeLeftWidth保持一致。
这样做的原因是:侧滑视图是绝对定位在容器两侧的(详见第四章),如果其实际渲染宽度与传入的swipeXXXWidth不一致,就会出现"露出的操作区宽度与滑动位移量不匹配"的问题。
三、运行示例:从零搭建一个左右侧滑动页面
特性文档给出了一个完整的@Entry页面示例,直接在build()中放置一个SwipeItemLayout即可看到"左滑删除、右滑选择"的效果。仓库中的 normal_view.cj 与之完全对应:
import ohos.base.* import ohos.component.* import ohos.state_manage.* import ohos.state_macro_manage.* import swipeitem.* import ohos.prompt_action.* import ohos.hilog.Hilog @Entry @Component class NormalView { var content: String = "侧滑内容子组件侧滑内容子组件侧滑内容子组件" protected override func aboutToAppear() { AppLog.info("aboutToAppear") } @Builder func surfaceView(itemContent: String, itemId: Int64) { Text(itemContent).fontSize(16).width(100.percent).height(45).fontColor(Color(0x999999)).backgroundColor( Color(0xEBEBEB)).padding(left: 10.0) } @Builder func swipeRightView(itemId: Int64) { Text("删除").fontSize(14).backgroundColor(0xFFFF1E38).fontColor(Color.WHITE).width(100.percent).height( 100.percent).textAlign(TextAlign.Center).onClick( { evt => PromptAction.showToast( message: "delete", duration: 1500, ) }) } @Builder func swipeLeftView(itemId: Int64) { Text("选择").fontSize(14).backgroundColor(0x317aff).fontColor(Color.WHITE).width(100.percent).height( 100.percent).textAlign(TextAlign.Center).onClick( { evt => PromptAction.showToast( message: "choose", duration: 1500, ) }) } func build() { Row { Column() { ColumnSplit() { SwipeItemLayout( mWidth: 100.0, mHeight: 45.0, swipeLeftWidth: 85.0, swipeRightWidth: 85.0, surfaceView: surfaceView, swipeLeftView: this.swipeLeftView, swipeRightView: this.swipeRightView, itemContent: content, itemId: 0 ) }.height(10.percent) }.width(100.percent) }.height(100.percent) } }示例中需要重点理解的三件事:
三个
@Builder各司其职:surfaceView(itemContent, itemId):内容层,宽高与组件保持一致(100.percent/45),实现第一条布局约定;swipeRightView(itemId):右侧"删除"按钮,红色背景、白色文字,点击后弹出PromptAction.showToast;swipeLeftView(itemId):左侧"选择"按钮,蓝色背景,点击后弹出PromptAction.showToast。
参数传入方式:
surfaceView: surfaceView直接传函数引用,而swipeLeftView/swipeRightView使用this.显式引用,两种写法在 Cangjie 中均可;itemContent: content将页面状态传入@Link。import 集合:除
swipeitem.*外,还需引入ohos.base.*、ohos.component.*、ohos.state_manage.*、ohos.state_macro_manage.*(状态管理)、ohos.prompt_action.*(Toast)等基础模块。
四、滑动交互的源码实现原理
SwipeItemLayout的滑动能力全部由 swipe_item_layout.cj 实现,核心思路可概括为三句话:一个可平移的 Row 容器、三个分层子视图、一套基于 onTouch 的手势状态机。
4.1 容器结构与三层视图
从build()的结构看,组件是一个Row容器(最终通过.position(x: offsetX, y: offsetY)整体平移),内部按顺序摆放三层:
Row() { if (!leftDisable) { Row() { this.swipeLeftView(itemId) } .width(this.swipeLeftWidth).position(x: -this.swipeLeftWidth, y: 0.0).onTouch(...) } Row() { this.surfaceView(itemContent, itemId) }.onTouch(...) if (!rightDisable) { Row() { this.swipeRightView(itemId) } .height(this.mHeight).width(this.swipeRightWidth).onTouch(...) } }.width(this.mWidth.percent).height(this.mHeight).position(x: this.offsetX, y: this.offsetY)从源码结构看:
- 左侧视图被绝对定位在
x: -swipeLeftWidth,即初始位于可视区域左侧之外,只有容器右移(offsetX > 0)时才会进入视野; - 右侧视图紧跟在
surfaceView之后排布,初始位于可视区域右侧之外,只有容器左移(offsetX < 0)时才会进入视野; - 因此,右滑(
moveX > 0)露出左侧"选择"区,左滑(moveX < 0)露出右侧"删除"区,与示例页面的按钮布局一一对应。
4.2 手势判定:横向优先 + 纵向容差
surfaceView与两侧视图都注册了onTouch,统一采用"三阶段事件"模型:
- Down:记录按下坐标
sX、sY; - Move:计算位移
moveX = screenX - sX、moveY = screenY - sY,先做方向判定——只有abs(moveX) > abs(moveY)(横向滑动占主导)且moveY落在(-30, 30)vp 的纵向容差带内,才进入侧滑逻辑,从而避免与列表纵向滚动冲突; - Up:根据松手时的位移决定展开还是收回。
滑动过程中,offsetX被实时更新:左滑时在0 ~ -swipeRightWidth区间跟手,右滑时在0 ~ swipeLeftWidth区间跟手,超出边界即被钳制(如offsetX = -swipeRightWidth),实现"拖到尽头停住"的效果。
4.3 松手判定:40 vp 阈值与回弹动画
Up 事件中通过animateTo驱动收尾动画,判定阈值在源码中固定为40 vp:
- 展开状态(
offsetX > 0,即左侧区露出):moveX >= 40保持展开、moveX > 0收回、moveX <= -40收回、moveX < 0保持展开; - 收回状态(
offsetX < 0,即右侧区露出):方向相反同理。
动画参数为:
animateTo( AnimateParam( duration: this.mDuration, // 默认 200 ms tempo: 1.0, curve: Curve.FastOutLinearIn, // 快速进入、线性退出,符合侧滑手感 iterations: 1, playMode: PlayMode.Normal, onFinish: { => this.rightSlideEnable = true; this.leftSlideEnable = true } ), { => /* 依据 moveX 设置最终 offsetX */ } )动画结束后通过onFinish复位两个互斥标志位rightSlideEnable/leftSlideEnable,允许下一次手势继续操作。
4.4 禁用开关与手势互斥
rightDisable/leftDisable是布局层与交互层双重开关:为true时对应视图不再构建,同时 Move 判定中对应方向的滑动手势被忽略;rightSlideEnable/leftSlideEnable是运行期互斥标志:当一侧视图已展开、用户反向滑动时,先收回已展开的一侧(置对应标志为false),避免两侧操作区同时露出。
4.5 参数钳制:aboutToAppear 里的边界保护
在 aboutToAppear 中,组件会对传入参数做一次防御性校正:
- 通过
getDefaultDisplaySync().width获取屏幕宽度(px 转 vp); swipeRightWidth/swipeLeftWidth:大于等于屏幕宽度时钳制为屏幕宽度,小于0.0时钳制为0.0;mWidth:大于100.0时钳制为100.0。
这解释了特性文档中"范围(0 ~ 当前设备屏幕宽度)""范围(0 ~ 100.0)"等取值约束的来源——即使调用方传了越界值,组件也会在挂载阶段自动修正。
五、实战进阶:官方三个示例页面解读
仓库entry模块的 page 目录 提供了三种由浅入深的用法,入口页 index.cj 通过Router.push分别跳转到三个页面:
5.1 单条示例 NormalView
即第三章的完整代码,一个SwipeItemLayout直接放在ColumnSplit中,用于理解组件的最小使用形态。
5.2 多行列表示例 MoreList
more_list.cj 演示了组件在List中的典型用法:用ForEach遍历 26 个元素的数组,每个ListItem内放置一个SwipeItemLayout,itemContent传入item.toString()、itemId传入index,从而让每个列表项拥有独立的滑动状态:
List(initialIndex: 0) { ForEach( this.arr, itemGeneratorFunc: { item: Int64, index: Int64 => ListItem() { SwipeItemLayout( mWidth: 100.0, mHeight: 60.0, swipeLeftWidth: 85.0, swipeRightWidth: 85.0, surfaceView: surfaceView, swipeLeftView: this.swipeLeftView, swipeRightView: this.swipeRightView, itemContent: item.toString(), itemId: index ) } } ) }.divider(strokeWidth: 5.px, color: Color(0xd2d3cc)).width(100.percent)由于组件内部用@State offsetX管理位移、用@Link itemId区分条目,多列表场景下各条目之间互不干扰,这正是itemId参数存在的意义。
5.3 全宽右侧操作区示例 LargrWidthView
large_width_view.cj 展示了两个高级用法:
swipeRightWidth: Float64(this.disWidth):将右侧操作区宽度直接设为屏幕宽度(在aboutToAppear中通过getDefaultDisplaySync().width获取),形成"整行全宽删除面板"效果;leftDisable: true:禁用左侧滑动,只保留右侧操作,说明leftDisable/rightDisable可用于按业务需要裁剪交互方向。
同时该示例的右侧构建器不止一个按钮,而是Image + Text + Blank + Button的组合布局,证明侧滑视图可以承载任意复杂度 UI:
@Builder func swipeRightView(itemId: Int64) { Image(@r(app.media.trash)).backgroundColor(0xff64e3b9).width(10.percent).height(100.percent).padding(10).objectFit( ImageFit.Contain).onClick({ evt => PromptAction.showToast(message: "Magnifier", duration: 1500) }) Text("Delete item?").fontSize(14).height(100.percent).fontColor(Color.WHITE).backgroundColor(Color.RED).padding(5).onClick( { evt => PromptAction.showToast(message: "Click on surface", duration: 1500) }) Blank().backgroundColor(Color.RED) Button("Yes,Delete").backgroundColor(Color.WHITE).fontColor(0xe52626).fontSize(14).width(35.percent).height( 70.px).shape(ShapeType.Capsule).margin(right: 10.0).onClick( { event => PromptAction.showToast(message: "click delete", duration: 1500) }) }六、将 swipeitem 接入自有 Cangjie 工程
README 提供了两种接入方式,均无需修改组件源码:
方式一:模块拷贝
- 克隆下载项目,将
swipeitem模块整体拷贝到目标工程下; - 在目标应用
entry的 oh-package.json5 的dependencies字段添加本地模块依赖:
{ "name": "entry", "version": "1.0.0", "dependencies": { "swipeitem": "file:../swipeitem" } }- 在目标应用
entry/src/main/cangjie下的cjpm.toml的[dependencies]字段添加仓颉包依赖(与仓库 entry 的 cjpm.toml 写法一致):
[dependencies] swipeitem = {path = "../../../../swipeitem/src/main/cangjie", version = "1.0.0"}- 在业务代码中引入并直接使用组件:
import swipeitem.*方式二:git submodule 三方库依赖
- 在目标工程
third-party目录下以 submodule 方式引入https://gitcode.com/Cangjie-TPC/swipe-item-cj.git; - 修改目标应用
cjpm.toml:
[dependencies] swipeitem = {path = "../../../../third-party/swipe-item-cj/swipeitem/src/main/cangjie", version = "1.0.0"}- 同样通过
import swipeitem.*使用。
无论哪种方式,cjpm.toml中path的相对基准都是entry/src/main/cangjie,实际开发时请按自身目录结构调整../../层数。
七、约束与验证环境
根据 README 的约束与限制说明,该组件在以下环境验证通过:
- DevEco Studio NEXT Developer Beta2(5.0.3.906);
- Cangjie Support Plugin 5.0.3.906;
- Cangjie Compiler:0.53.13(cjnative)。
此外,仓库内entry与swipeitem的 cjpm.toml 均声明cjc-version = "0.48.2",接入时建议保持编译器版本不低于该值。本项目基于 MIT License 开源,可直接自由使用与二次开发;当前版本(v0.0.1,见 CHANGELOG.md)核心能力即"支持左侧和右侧的滑动布局"。
小结
SwipeItemLayout用极简的参数面(3 个构建器 + 7 个配置项)封装了复杂的侧滑交互:@BuilderParam让内容层与左右操作区完全由调用方定制,@Link打通页面数据,rightDisable/leftDisable按需裁剪方向,mDuration控制回弹手感,而源码内部的横向手势判定、40 vp 阈值与animateTo(FastOutLinearIn)动画则保证了与系统列表滚动共存时的流畅体验。无论是单条演示、多行列表还是全宽操作面板,均可参照 entry 示例 直接落地到业务代码中。
- UI组件
- 移动开发
- OpenHarmony
【免费下载链接】swipe-item-cj
自定义侧滑操作UI组件
相关推荐
swipe-item-cj 实战指南:用 Cangjie 语言打造 HarmonyOS 左右侧滑动操作组件
swipe item cj 实战指南:用 Cangjie 语言打造 HarmonyOS 左右侧滑动操作组件 swipe item cj 是一个基于 Cangji
UI组件移动开发OpenHarmonyswipe-item-cj侧滑动画实现揭秘:animateTo回弹逻辑与FastOutLinearIn曲线解析
swipe item cj侧滑动画实现揭秘:animateTo回弹逻辑与FastOutLinearIn曲线解析 swipe item cj 是基于仓颉(Cang
UI组件移动开发OpenHarmonyamis Slider 左右滑动容器组件:JSON 配置、事件监听与源码原理详解
amis Slider 左右滑动容器组件:JSON 配置、事件监听与源码原理详解 导读 本文围绕 amis 前端低代码框架中的 slider 组件(左右滑动容器
前端低代码UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考