HarmonyOS 7 / API 26 3DGS 多设备预览适配:折叠屏、平板和窗口态下视口怎么稳住

HarmonyOS 7 3DGS 多设备预览适配

3D 预览到了大屏不是简单放大

3DGS 模型预览在手机竖屏里能跑,不代表放到折叠屏、平板、鸿蒙电脑窗口态里也能跑得舒服。二维列表页面放大以后最多是空白多一点,3D 页面放大以后会出现更明显的问题:模型被裁切、手势冲突、相机距离不合适、右侧属性面板遮住主体、窗口缩小时状态没有恢复。

这篇专门看 3DGS 预览在多设备形态下的适配。它不是普通响应式布局文章,因为 3D 页面除了 ArkUI 布局,还要同时处理 3D 视口、相机参数和手势区域。

先分清三个尺寸

很多问题来自一个误区:只拿页面宽度判断布局。3D 预览页至少要看三个尺寸:

尺寸含义常见错误
Window Size当前应用窗口大小只按屏幕大小,不按窗口变化
Preview Viewport真正承载 3D 场景的区域属性面板出现后仍按整屏算相机
Model Bounds模型自己的空间范围相机距离固定,模型大了就被裁切

3DGS 页面要稳,不能只写“宽度大于 840 就双栏”。双栏以后,3D 视口变窄了,相机也要跟着重新算。

案例一:平板双栏后模型被右侧面板挡住

第一个案例是平板双栏。左边是 3D 预览,右边是模型信息和操作。页面看起来很合理,但如果相机仍按整屏宽度算,模型主体就可能偏到右侧,被面板遮住。

复现步骤

  1. 手机竖屏打开 3DGS 预览,模型居中;
  2. 切到平板或展开折叠屏;
  3. 页面改成左预览、右信息面板;
  4. 如果 3D 相机仍按窗口宽度计算,主体容易偏移;
  5. 改成按 previewViewport 计算相机,问题消失。
  6. interface Size2D {
      width: number;
      height: number;
    }
    
    interface PreviewLayoutInfo {
      windowSize: Size2D;
      viewportSize: Size2D;
      sidePanelWidth: number;
      mode: 'phone' | 'tablet' | 'desktopWindow';
    }
    
    export function calculatePreviewLayout(windowSize: Size2D): PreviewLayoutInfo {
      if (windowSize.width < 720) {
        return { windowSize, viewportSize: windowSize, sidePanelWidth: 0, mode: 'phone' };
      }
    
      const sidePanelWidth = Math.min(360, Math.floor(windowSize.width * 0.32));
      return {
        windowSize,
        sidePanelWidth,
        viewportSize: {
          width: windowSize.width - sidePanelWidth,
          height: windowSize.height
        },
        mode: windowSize.width > 1100 ? 'desktopWindow' : 'tablet'
      };
    }

    这里的关键是 viewportSize。3D 场景不关心整个窗口有多宽,它只关心自己实际能画的区域有多大。

    相机跟着视口重新算

    interface CameraConfig {
      distance: number;
      fieldOfView: number;
      targetOffsetX: number;
      targetOffsetY: number;
    }
    
    export class ResponsiveCameraPlanner {
      plan(layout: PreviewLayoutInfo, bounds: ModelBounds): CameraConfig {
        const aspect = layout.viewportSize.width / Math.max(layout.viewportSize.height, 1);
        const baseDistance = Math.max(bounds.radius, 1) * 2.6;
    
        if (layout.mode === 'phone') {
          return { distance: baseDistance * 1.08, fieldOfView: 48, targetOffsetX: 0, targetOffsetY: 0 };
        }
    
        if (aspect > 1.4) {
          return { distance: baseDistance * 0.95, fieldOfView: 42, targetOffsetX: 0, targetOffsetY: 0 };
        }
    
        return { distance: baseDistance * 1.15, fieldOfView: 46, targetOffsetX: 0, targetOffsetY: 0 };
      }
    }

    我不会在 3D 页面里写一个固定相机距离。模型大小、视口宽高比、双栏状态都会影响第一屏效果。相机参数跟随 viewport 计算,才不会出现大屏反而看不清模型的问题。

    案例二:窗口缩放后手势区域和 3D 区域错位

    第二个案例出现在鸿蒙电脑窗口态或平板分屏里。用户拖动窗口后,页面 UI 变了,但手势识别区域还停留在旧尺寸,结果滑动旋转模型时,手势落点和模型不一致。

    复现步骤

    1. 打开 3DGS 预览页;
    2. 拖动窗口变窄;
    3. 右侧面板从常驻变成底部抽屉;
    4. 如果手势区域没有重新绑定,旋转或缩放会异常;
    5. 在 onSizeChange 后同步更新 viewport 和 gesture zone。
    6. interface GestureZone {
        left: number;
        top: number;
        width: number;
        height: number;
      }
      
      export class SpatialGestureZoneStore {
        private zone: GestureZone = { left: 0, top: 0, width: 0, height: 0 };
      
        updateFromViewport(viewport: Size2D): GestureZone {
          this.zone = {
            left: 0,
            top: 0,
            width: viewport.width,
            height: viewport.height
          };
          return { ...this.zone };
        }
      
        canHandle(pointX: number, pointY: number): boolean {
          return pointX >= this.zone.left &&
            pointX <= this.zone.left + this.zone.width &&
            pointY >= this.zone.top &&
            pointY <= this.zone.top + this.zone.height;
        }
      }

      3D 手势不要全屏接管。页面里可能还有返回按钮、底部面板、参数滑块、模型信息卡片。手势区域必须跟实际 3D 视口一致,否则用户操作会变得很怪。

      页面状态和 3D 状态要一起刷新

      interface SpatialPreviewResizeState {
        layout: PreviewLayoutInfo;
        camera: CameraConfig;
        gestureZone: GestureZone;
        updatedAt: number;
      }
      
      export class SpatialPreviewResizeController {
        private cameraPlanner = new ResponsiveCameraPlanner();
        private gestureStore = new SpatialGestureZoneStore();
      
        resize(windowSize: Size2D, bounds: ModelBounds): SpatialPreviewResizeState {
          const layout = calculatePreviewLayout(windowSize);
          const camera = this.cameraPlanner.plan(layout, bounds);
          const gestureZone = this.gestureStore.updateFromViewport(layout.viewportSize);
      
          return {
            layout,
            camera,
            gestureZone,
            updatedAt: Date.now()
          };
        }
      }

      这里我把 resize 单独封装。原因是窗口变化时要更新的不止 ArkUI 布局,还包括相机和手势。只改页面 Column、Row、Stack,不改 3D 视口状态,问题还是会复现。

      多设备验证清单

      场景必查点通过标准
      手机竖屏单栏预览模型主体首屏可见,底部按钮不挡主体
      折叠屏展开左预览右信息相机按预览区计算,不按整屏计算
      平板横屏双栏或浮层信息手势只作用在 3D 区域
      鸿蒙电脑窗口拖动窗口大小相机、手势、布局同时更新
      分屏视口变窄面板降级,模型不裁切

      最后总结

      3DGS 多设备适配不能只看 ArkUI 断点。真正要稳定的是 3D 视口、默认相机和手势区域。窗口变了,预览区变了,相机和手势也要跟着变。

      HarmonyOS 7 / API 26 的 3DGS 体验如果只在手机竖屏里验证,很容易漏掉大屏问题。折叠屏、平板、窗口态都是活动要求里的重要方向。把 viewport、camera、gesture 三个状态统一起来,后面接更多 3D 模型和页面形态会稳很多。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐