# quick-preveiw **Repository Path**: leggodd/quick-preveiw ## Basic Information - **Project Name**: quick-preveiw - **Description**: No description available - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2025-12-26 - **Last Updated**: 2026-07-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # QuickPreview #### 介绍 全网唯一支持按需加载的鸿蒙原生预览组件 —— 图片、视频、动图一站支持。 - **按需加载**:基于 `LazyForEach` 实现,万张图片依旧行云流水,内存占用极低 - **开箱即用**:一行代码唤起,图片 / 视频 / LivePhoto 自动分派 - **手势完备**:捏合缩放、双击放大、拖拽关闭、长图纵向浏览 - **深度可定制**:从预览内容到浮层工具栏全面开放,也支持完全接管渲染 - **纯 ArkUI V2**:全量使用 `@ComponentV2` / `@ObservedV2` / `@Trace`,渲染精细可控 #### 安装教程 ```bash ohpm install @xcx/quick-preview ``` #### 效果预览 | 版本>= 1.0.1 | |:-------------------------------------| | ![preview](https://s3.xchuxing.com/xchuxing/ins/2026/07/29/51d02202607291601296803.gif) | --- ## 使用示例 ### 基础用法 ```typescript import { DefaultMediaModel, DefaultMediaType, QuickPreview, QuickPreviewOptions } from '@xcx/quick-preview' @ComponentV2 struct Demo { @Local resources: DefaultMediaModel[] = [ new DefaultMediaModel({ src: 'http资源' }), new DefaultMediaModel({ src: 'http资源', type: DefaultMediaType.VIDEO }), new DefaultMediaModel({ src: 'http资源', video: 'http资源', type: DefaultMediaType.LIVE_PHOTO }) ] @Local options: QuickPreviewOptions = new QuickPreviewOptions() build() { Button('图片预览') .onClick(() => { this.options .setMedias(this.resources) .setInitIndex(0) .setBackListener(() => { // 预览已关闭 }) .setLongPressListener((item: DefaultMediaModel) => { if (item.type == DefaultMediaType.IMAGE) { // 长按下载图片等 } }) QuickPreview.open(this.getUIContext(), this.options) }) } } ``` ### 主动关闭 ```typescript // 关闭最近打开的一个预览 QuickPreview.close() // 多层预览时,用 open() 返回的 dialogId 精确关闭 const dialogId = QuickPreview.open(this.getUIContext(), this.options) QuickPreview.close({ dialogId: dialogId, animated: true }) ``` ### 打开后动态增删数据 预览内容运行在独立的弹窗层,调用方的响应式状态不会自动穿透。**必须通过下列方法改数据**,它们会自动刷新预览: ```typescript this.options.addMedia(new DefaultMediaModel({ src: 'http资源' })) // 追加 this.options.spliceMedias(1, [modelA, modelB]) // 指定位置插入 this.options.deleteMedia(0) // 删除 this.options.setMedias(newList) // 整体替换 ``` > 数据被删空时预览会自动关闭。 --- ## API ### QuickPreview 预览的入口类。 | 方法 | 签名 | 说明 | |:---|:---|:---| | **open** | `open(uiContext: UIContext, options: QuickPreviewOptions): string` | 打开预览,返回本次弹窗的 `dialogId`,多层预览时用于精确定位 | | **close** | `close(params?: PreviewCloseParam): void` | 关闭预览。不传参数时关闭最近打开的一个 | **PreviewCloseParam** | 字段 | 类型 | 默认值 | 说明 | |:---|:---|:---|:---| | dialogId | `string?` | - | 目标弹窗 id,来自 `QuickPreview.open()` 的返回值。不传则作用于最近打开的一个 | | animated | `boolean?` | `true` | 关闭时是否播放动画 | --- ### QuickPreviewOptions 预览的全部配置,Builder 模式,所有 `setXxx()` 均返回自身,支持链式调用。 ```typescript constructor(medias: T[] = [], initIndex: number = 0) ``` #### 属性 **基础配置** | 属性 | 类型 | 默认值 | 对应方法 | 说明 | |:---|:---|:---|:---|:---| | **dataList** | `T[]` | `[]` | `setMedias` | 数据源,元素必须继承 `MediaModel` | | **initIndex** | `number` | `0` | `setInitIndex` | 初始展示的索引(从 0 开始),越界会自动收敛到合法范围 | | **backgroundColor** | `ResourceColor` | `'#000000'` | `setBackgroundColor` | 预览背景色 | | **duration** | `number` | `300` | `setDuration` | 开场 / 关闭 / 缩放动画时长,单位 ms | | **showPageText** | `boolean` | `true` | `setShowPageText` | 是否显示顶部页码。仅在数据多于 1 条时出现 | | **indicator** | `DotIndicator \| DigitIndicator \| boolean` | `false` | `setIndicator` | Swiper 指示器样式,`false` 为不显示 | **交互控制** | 属性 | 类型 | 默认值 | 对应方法 | 说明 | |:---|:---|:---|:---|:---| | **dismissOnTap** | `boolean` | `true` | `setDismissOnTap` | 单击任意位置关闭预览 | | **dismissOnDrag** | `boolean` | `true` | `setDismissOnDrag` | 下拉拖动关闭预览。关闭该选项后仍可跟手拖动,但松手会回弹 | | **dismissOnSystemBack** | `boolean` | `true` | `setDismissOnSystemBack` | 是否响应系统返回(三键返回、侧滑、ESC) | | **minScale** | `number` | `0.5` | `setMinScale` | 捏合过程中允许缩小到的下限,松手后回弹到 1 倍 | | **maxScale** | `number` | `3` | `setMaxScale` | 放大上限,双击放大也以此为准 | | **extraScale** | `number` | `0.5` | `setExtraScale` | 捏合过程中允许超出 `maxScale` 的额外比例,用于提升手感 | **自定义扩展** | 属性 | 类型 | 默认值 | 对应方法 | 说明 | |:---|:---|:---|:---|:---| | **contentBuilder** | `(item: T, index: number) => void` | - | `setContentBuilder` | 自定义预览内容,需传 `@Builder`。设置后完全接管默认的图片 / 视频 / 动图分派 | | **toolBuilder** | `() => void` | - | `setToolBuilder` | 自定义浮层工具栏,需传 `@Builder`,覆盖在预览内容之上 | | **idBuilder** | `(item: T, index: number) => string` | `item.mediaUniqueId` | `setIdBuilder` | 每一项的唯一标识,同时作为按需加载的复用依据。**同一个 model 实例请勿重复放入 `dataList`,否则标识冲突** | | **thumbBuilder** | `(item: T, index: number) => void` | - | `setThumbBuilder` | 缩略图扩展点,**当前版本尚未接入** | **事件回调** | 属性 | 类型 | 对应方法 | 说明 | |:---|:---|:---|:---| | **onPageChangedListener** | `(item: T, index: number) => void` | `setPageChangedListener` | 翻页回调,返回当前项与索引。首次打开时也会触发 | | **onLongPressListener** | `(item: T, index: number) => void` | `setLongPressListener` | 长按回调,常用于保存图片、识别二维码。**使用 `contentBuilder` 自定义后失效** | | **onBackListener** | `() => void` | `setBackListener` | 预览关闭时的回调 | | **onZoomEnable** | `(item: T, index: number) => boolean` | `setZoomEnable` | 逐项控制是否允许缩放,默认全部允许 | **弹窗层级**(需系统 API 15 及以上,低版本自动忽略) | 属性 | 类型 | 对应方法 | 说明 | |:---|:---|:---|:---| | **levelMode** | `LevelMode` | `setLevelMode` | 弹窗层级模式,可让预览随页面跳转一起消失 | | **levelUniqueId** | `number \| string` | `setLevelUniqueId` | 指定预览在哪个页面内弹出,可传组件 id 或 uniqueId | | **immersiveMode** | `ImmersiveMode` | `setImmersiveMode` | 页面级弹窗的蒙层样式 | #### 方法 除上表中的 setter 外,另有一组数据操作方法。它们会自动刷新已打开的预览,**改数据请务必走这些方法**: | 方法 | 签名 | 说明 | |:---|:---|:---| | **setMedias** | `setMedias(medias: T[]): this` | 整体替换数据源 | | **addMedia** | `addMedia(media: T): this` | 在末尾追加一条 | | **spliceMedias** | `spliceMedias(index: number, medias: T[]): this` | 在指定位置插入多条 | | **deleteMedia** | `deleteMedia(index: number): this` | 删除指定位置的一条 | --- ### 数据模型 **MediaModel**(抽象基类) 所有自定义数据模型都必须继承它,它提供预览按需加载所依赖的稳定标识。 | 属性 | 类型 | 说明 | |:---|:---|:---| | **mediaUniqueId** | `readonly string` | 实例创建时自动生成,无需也不应手动赋值 | **DefaultMediaModel**(开箱即用的默认实现) ```typescript new DefaultMediaModel({ src?: ResourceStr, video?: ResourceStr, type?: DefaultMediaType }) ``` | 属性 | 类型 | 默认值 | 说明 | |:---|:---|:---|:---| | **type** | `DefaultMediaType` | `IMAGE` | 媒体类型 | | **sourceSrc** | `ResourceStr?` | `''` | 图片 / 视频资源地址;LivePhoto 时为封面图 | | **videoSrc** | `ResourceStr?` | - | 仅 LivePhoto 使用,动图对应的视频地址 | **DefaultMediaType**(枚举) | 值 | 说明 | |:---|:---| | `IMAGE` | 图片 | | `VIDEO` | 视频 | | `LIVE_PHOTO` | 动图(LivePhoto) | --- ### MediaPreviewComponent 预览的核心组件,`QuickPreview.open()` 内部即基于它。完全接管渲染时可直接使用。 | 属性 | 类型 | 必填 | 说明 | |:---|:---|:---|:---| | **options** | `QuickPreviewOptions` | 是 | 预览配置 | | **contentBuilder** | `(item: ESObject, index: number) => void` | 是 | 预览内容,`@BuilderParam` | | **toolBuilder** | `() => void` | 否 | 浮层工具栏,`@BuilderParam` | --- ## 自定义 按需求深度分两档,**绝大多数场景用第一档就够了**。 ### 一、定制内容与工具栏(推荐) 仍然使用 `QuickPreview.open()`,只需传入自己的 `@Builder`: ```typescript @ComponentV2 struct Demo { @Local options: QuickPreviewOptions = new QuickPreviewOptions() @Builder myContent(item: DefaultMediaModel, index: number) { // 你自己的预览内容 Image(item.sourceSrc).width('100%') } @Builder myTool() { // 你自己的浮层工具栏 Row() { Button('保存').onClick(() => { /* ... */ }) }.width('100%').justifyContent(FlexAlign.End).padding(16) } build() { Button('预览').onClick(() => { this.options .setMedias(this.resources) .setContentBuilder(this.myContent) .setToolBuilder(this.myTool) QuickPreview.open(this.getUIContext(), this.options) }) } } ``` > 只想改工具栏就只传 `setToolBuilder`,图片 / 视频 / 动图的默认分派会照常工作。 ### 二、完全接管渲染 想连顶层结构一起换掉时,自行组装 `MediaPreviewComponent` 并通过 `PromptActionTool.openCustomDialog()` 弹出。 `DefaultImageItem`、`DefaultVideoItem`、`DefaultLivePhotoItem` 未对外导出,可从源码复制到自己工程后按需改造。 ```typescript import { MediaPreviewComponent, QuickPreviewOptions, PromptActionTool } from '@xcx/quick-preview' //下面3个是你完全自定义的组件 import { DefaultImageItem } from './DefaultImageItem' import { DefaultLivePhotoItem } from './DefaultLivePhotoItem' import { DefaultVideoItem } from './DefaultVideoItem' //这个非必须,如果不自定义,就在最上面的 import 引入 import { DefaultMediaModel, DefaultMediaType } from './DefaultMediaModel' @ComponentV2 export struct MyPreview { @Require @Param option: QuickPreviewOptions build() { MediaPreviewComponent({ options: this.option, contentBuilder: this.option.contentBuilder ? this.option.contentBuilder : (item: DefaultMediaModel, index: number) => { if (item.type == DefaultMediaType.IMAGE) { this.imageBuilder(item, index) } else if (item.type == DefaultMediaType.VIDEO) { this.videoBuilder(item, index) } else if (item.type == DefaultMediaType.LIVE_PHOTO) { this.livePhotoBuilder(item, index) } }, toolBuilder: this.option.toolBuilder, }) .width('100%') .height('100%') } @Builder imageBuilder(item: DefaultMediaModel, index: number) { DefaultImageItem({ resource: item, index: index }) } @Builder videoBuilder(item: DefaultMediaModel, index: number) { DefaultVideoItem({ resource: item, index: index }) } @Builder livePhotoBuilder(item: DefaultMediaModel, index: number) { DefaultLivePhotoItem({ resource: item, index: index }) } } @Builder export function MyPreviewBuilder(option: QuickPreviewOptions) { MyPreview({ option: option }) } ``` 打开时不再走 `QuickPreview.open()`,改为: ```typescript PromptActionTool.openCustomDialog(this.getUIContext(), { builder: wrapBuilder(MyPreviewBuilder), params: this.options }) // 关闭 PromptActionTool.closePreview({ dialogId: this.options.dialogId }) ``` > `QuickPreview.open()` 内部固定使用库自带的顶层 Builder,**只复制并改造上面的组件是不会生效的**,必须换成 `PromptActionTool.openCustomDialog()` 才能接管。 #### 开源协议 [Apache-2.0](LICENSE)