跳到主要内容

接入与生命周期

本页解决“组件放进页面后如何长期稳定工作”的问题。核心只有一句话:一个可见的 Viewer 绑定一个稳定 Controller;导入串行进行;页面永久离开才 dispose。 如果只是查字段,请跳到 VoxelViewer API 参考

Controller 生命周期

一个 VoxelViewer 对应一个稳定的 VoxelViewerController

创建 Options → 创建 Controller → 配置资源/渲染参数 → 挂载 Viewer → 串行 load* → dispose
阶段必须做不要做
页面初始化创建 Options 与 Controller。build() 创建。
首次加载前设置 unitGridTexture、背景、Overlay 策略和 Tuning。先加载再补纹理,导致回退描边。
导入期间等待当前 Promise 结束。并发调用两个 load*()
页面可见把同一 Controller 传给同一 Viewer。外部调用 bindStateListener()
永久退出调用 dispose()释放后继续复用 Controller。

dispose() 会取消仍未完成的边线任务、立即收束载入动画并清除当前 Scene 引用。再次展示预览必须新建 Controller 和 Viewer。

常见生命周期写法

@Entry
@Component
struct AssetDetailPage {
private readonly previewOptions = new VoxelViewerOptions();
private readonly previewController = new VoxelViewerController(this.previewOptions);

aboutToAppear(): void {
this.previewOptions.unitGridTexture = $rawfile('voxel/voxel_grid.png');
this.previewController.loadUri(this.assetUri, this.assetName)
.catch((error: Error) => console.error(error.message));
}

build() {
VoxelViewer({ controller: this.previewController })
.width('100%')
.height(520)
}

aboutToDisappear(): void {
this.previewController.dispose();
}
}

如果页面只是被覆盖又会返回,不要仅为短暂不可见而销毁/重建 Controller;是否在离开时释放由页面自身的生存期决定。永久离开或确定不再复用时才调用 dispose()。调用后重新 load*() 不属于支持用法。

内置手势

手势行为
单指拖拽以模型 Pivot 为中心的球面旋转;横向环绕,纵向俯仰。
双指平移相机目标点平移。
双指捏合缩放。
载入动画期间手势暂时忽略,避免临时动画 Geometry 与相机状态错位。

双指平移和捏合会在同一事件轮次合并相机更新。宿主不应额外给 Viewer 套一层会修改相机或争夺同类手势的组件。

手势范围与产品提示

  • 用户不能通过公开 API 设置相机初始坐标、固定某个旋转角,或脚本化推进相机。
  • Viewer 不是地图控件:两指是平移与缩放,而不是第二套旋转手势。
  • 载入动画时有意锁定输入,防止临时动画几何与相机状态错位;Promise 成功不等于动画结束,但 UI 会在动画结束时自动恢复触控。
  • 希望用户快速理解操作时,在 Viewer 外部放一条“单指旋转,双指平移/缩放”的辅助文案即可,不要覆盖一个透明的手势层。

Options 与动态更新

const options = this.controller.getOptions();
options.darkBackground = true;
options.orbitSensitivity = 1.2;
options.panSensitivity = 0.9;
this.controller.setOptions(options);

setOptions() 立即更新背景、当前地面网格和当前场景动画时长;动画模式和是否播放动画由下一次加载读取。autoRotate 是保留字段,当前版本无效果。

为避免配置失效,修改同一对象后仍需提交:

const options = this.controller.getOptions();
options.darkBackground = true;
options.showGroundGrid = false;
this.controller.setOptions(options);

如果只改网格,使用 setGroundGridVisible(false) 更直接;它会同步写入 Options 并立即操作当前场景。

背景四选一

模式何时使用宿主代码
BUILTIN要默认工作室视觉。仅切换 darkBackground
BUILTIN_CUSTOM要品牌渐变或一张图片。设置六段颜色,或 backgroundImage
BUILDER要在组件内部完全替换背景。backgroundBuilder
EXTERNAL页面已有全屏背景。在父 Stack 绘制背景。

图片背景必须由宿主创建 ArkUI Resource,例如 $rawfile('images/background.jpg');HAR 不接收路径或 URI,也不管理图片权限/缓存。

背景 Builder 示例

@Builder
function BrandBackground(state: VoxelBackgroundState) {
Column()
.width('100%')
.height('100%')
.linearGradient({
angle: 180,
colors: state.darkBackground
? [['#10243A', 0], ['#05070C', 1]]
: [['#FBFCFF', 0], ['#DCEBFF', 1]]
})
}

// 配置:options.backgroundMode = VoxelBackgroundMode.BUILDER;
VoxelViewer({ controller: this.controller, backgroundBuilder: BrandBackground })

BUILDER 是组件内部背景;EXTERNAL 是页面外部背景。前者随 Viewer 尺寸裁切,后者可与标题、按钮、说明共享同一张整页背景。

Loading UI 四选一

模式HAR 渲染宿主做什么
BUILTIN遮罩、状态卡片、错误提示全部内置。无。
BUILTIN_CUSTOM保留内置结构。VoxelLoadOverlayStyle 文案/颜色/圆角。
BUILDERHAR 管理遮罩可见性。loadOverlayBuilder 替换提示卡片。
EXTERNAL不画任何遮罩或错误。监听 onLoadOverlayStateChanged

EXTERNAL 示例:

options.loadOverlayMode = VoxelLoadOverlayMode.EXTERNAL;
options.onLoadOverlayStateChanged = (state: VoxelLoadOverlayState): void => {
// 将 state.phase / state.visible / state.message 写入宿主 @State。
};
controller.setOptions(options);

阶段枚举为 IDLEIMPORTINGPREPARINGANIMATINGERRORHIDDEN。回调仅表达 UI 生命周期,不表示读取字节百分比或 TaskPool 实际进度。

外置 Loading 的推荐模式

@State private overlayVisible: boolean = false;
@State private overlayMessage: string = '';

private configureOverlay(): void {
this.options.loadOverlayMode = VoxelLoadOverlayMode.EXTERNAL;
this.options.onLoadOverlayStateChanged = (state: VoxelLoadOverlayState): void => {
this.overlayVisible = state.visible;
this.overlayMessage = state.errorMessage.length > 0 ? state.errorMessage : state.message;
};
this.controller.setOptions(this.options);
}

宿主使用 visible 控制自己的遮罩,使用 phase 决定是显示进度样式、动画样式还是错误样式。不要根据 message 文本做条件判断;文案是面向用户的内容,可能随版本优化。

切换模型时的规则

  1. 等待当前 load*() Promise 完成(无论成功或失败)。
  2. 使用同一个 Controller 发起下一次 load*()
  3. 组件会取消前一场景尚未完成的延迟描边,替换为新场景。
  4. 不要在切换时先 dispose() 再继续 load*();这会把生命周期变成未定义状态。

在支持“重新导入”按钮的产品中,按钮应在导入期间禁用,或让业务层维护一个串行队列;不要让用户的连续点击并发进入两个导入调用。