☰
swipe-item-cj 侧滑组件实战指南:SwipeItemLayout 左右滑动容器 API 详解与源码剖析
2026/10/15 2:40:26 网站建设 项目流程
  • UI组件
  • 移动开发
  • OpenHarmony

【免费下载链接】swipe-item-cj

自定义侧滑操作UI组件

项目地址:https://gitcode.com/Cangjie-TPC/swipe-item-cj
点击查看免费下载

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 Int640组件索引,供构建器区分不同列表项
mWidthFloat64100.0组件宽度,单位百分比,范围0 ~ 100.0;源码在aboutToAppear中会将超过 100.0 的值钳制为 100.0
mHeightFloat6490.0组件高度,单位 vp;特性文档约定当小于0.0时取0.0
swipeRightWidthFloat64120.0右侧布局宽度,单位 vp,范围0 ~ 当前设备屏幕宽度
swipeLeftWidthFloat64120.0左侧布局宽度,单位 vp,范围0 ~ 当前设备屏幕宽度
rightDisableBoolfalse为true时禁用右侧滑动
leftDisableBoolfalse为true时禁用左侧滑动
mDurationInt32200展开/收回动画持续时间,单位 ms

三条关键布局约定

特性文档特别给出三条推荐,直接决定滑动效果是否正常显示:

  1. surfaceView(滑动内容组件)的宽度、高度推荐与组件的mWidth、mHeight保持一致;
  2. 右侧滑动布局(文档中称为bottomViewRight,代码中对应swipeRightView)的宽度推荐与swipeRightWidth保持一致;
  3. 左侧滑动布局(文档中称为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) } }

示例中需要重点理解的三件事:

  1. 三个@Builder各司其职:

    • surfaceView(itemContent, itemId):内容层,宽高与组件保持一致(100.percent/45),实现第一条布局约定;
    • swipeRightView(itemId):右侧"删除"按钮,红色背景、白色文字,点击后弹出PromptAction.showToast;
    • swipeLeftView(itemId):左侧"选择"按钮,蓝色背景,点击后弹出PromptAction.showToast。
  2. 参数传入方式:surfaceView: surfaceView直接传函数引用,而swipeLeftView/swipeRightView使用this.显式引用,两种写法在 Cangjie 中均可;itemContent: content将页面状态传入@Link。

  3. 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 提供了两种接入方式,均无需修改组件源码:

方式一:模块拷贝

  1. 克隆下载项目,将swipeitem模块整体拷贝到目标工程下;
  2. 在目标应用entry的 oh-package.json5 的dependencies字段添加本地模块依赖:
{ "name": "entry", "version": "1.0.0", "dependencies": { "swipeitem": "file:../swipeitem" } }
  1. 在目标应用entry/src/main/cangjie下的cjpm.toml的[dependencies]字段添加仓颉包依赖(与仓库 entry 的 cjpm.toml 写法一致):
[dependencies] swipeitem = {path = "../../../../swipeitem/src/main/cangjie", version = "1.0.0"}
  1. 在业务代码中引入并直接使用组件:
import swipeitem.*

方式二:git submodule 三方库依赖

  1. 在目标工程third-party目录下以 submodule 方式引入https://gitcode.com/Cangjie-TPC/swipe-item-cj.git;
  2. 修改目标应用cjpm.toml:
[dependencies] swipeitem = {path = "../../../../third-party/swipe-item-cj/swipeitem/src/main/cangjie", version = "1.0.0"}
  1. 同样通过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组件

项目地址:https://gitcode.com/Cangjie-TPC/swipe-item-cj
点击查看免费下载
上一篇:B站直播场控神器:可编程弹幕机器人让你告别手忙脚乱
下一篇:Viper 异步 NetBIOS 扫描模块(NextnetByPE)实战指南:多网段内网主机发现与网卡地址枚举

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询