跳到主要内容

接入与生命周期

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

Controller 生命周期​

一个 VoxelViewer 对应一个稳定的 VoxelViewerController:

创建 Options → 创建 Controller → 配置资源/渲染参数 → 挂载 Viewer → 串行 load* → dispose
阶段必须做不要做
页面初始化创建 Options 与 Controller。在 build() 创建。
首次加载前设置背景、Overlay、Tuning 和宿主 unitGridTexture。先加载再补纹理,导致当前 Scene 已走兼容描边回退。
导入期间等待当前 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 套一层会修改相机或争夺同类手势的组件。

手势范围与产品提示​

  • 通过 controller.setGestureLocks() 可分别锁定旋转、平移、缩放、点击编辑和框选;setGestureLocked(true) 用于完整冻结用户输入。锁定不会阻断程序化相机控制。
  • 通过 setCameraRotation()、setCameraTranslation()、setCameraTransform() 和 resetCamera() 可在不暴露内部 ArkGraphics Camera 的前提下控制机位。旋转使用弧度,分别为俯仰 X、方位 Y、视线滚转 Z;平移会让相机与焦点一起移动。
  • 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() 立即更新背景、当前地面网格、当前场景动画时长与自动旋转策略;动画模式和是否播放载入动画由下一次加载读取。自动旋转也可通过 setAutoRotateEnabled() 和 setAutoRotateDurationMs() 单独控制。

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

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);

阶段枚举为 IDLE、IMPORTING、PREPARING、ANIMATING、ERROR、HIDDEN。回调仅表达 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*();这会把生命周期变成未定义状态。

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