HarmonyOS鸿蒙Next中关于在Tab组件子页面配置半模态面板遮挡底部导航栏的问题

HarmonyOS鸿蒙Next中关于在Tab组件子页面配置半模态面板遮挡底部导航栏的问题 开发者的鸿蒙应用在入口模块设计Main页面,通过Tab组件的TabContent()加载4个子模块的主页。其中1个子模块主页设计地图组件并设置.bindSheet属性,实现带有半模态面板的地图页。开发者希望实现半模态面板只在地图页显示,显示层级低于Tab组件,不能遮挡底部导航栏。

问题:地图页的半模态面板显示时,层级高于底部导航栏,阻碍了页面的自由切换,应该如何解决这个问题,现有代码要如何修改?

地图子页相关代码为:

  build() {
    Stack() {
      MapComponent({
        mapOptions: this.mapOptions,
        mapCallback: this.callback,
      })
        .bindSheet(
          true,
          this.SheetBuilder(),
          {
            detents: [140, 250],
            preferType: SheetType.BOTTOM,
            showClose: false,
            enableOutsideInteractive: true,
            mode:SheetMode.EMBEDDED,
            onDetentsDidChange: (heightPx: number) => {
              this.sheetHeightVp = px2vp(heightPx);
            }
          }
        )
    }
    .padding({bottom:80})
    .width('100%')
  }

  @Builder
  SheetBuilder() {
    Column() {
      // 搜索框
      Search({ placeholder: $r('app.string.searchPlaceHolder') })
        .borderRadius(24)
        .searchIcon({ src: $r('app.media.ic_search') })
        .backgroundColor(Color.White)
        .width('90%')
        .height(50)
    }
    .width('100%')
    .padding({ top: 10, bottom: 20 })
    .backgroundColor(Color.Transparent) 
  }
}

入口模块Main页相关代码为

 build() {
    Navigation(this.mainPathStack) {
      Flex({
        direction: FlexDirection.Column,
      }) {
        Tabs({ index: this.homeTabIndex }) {
          TabContent() {
            MapPage()
          }

          TabContent() {
            SpotsPage()
          }

          TabContent() {
            Notespage()
          }

          TabContent() {
            Personal()
          }
        }
        .barHeight(0)
        .scrollable(false)
        .layoutWeight(1)
        .width('100%')
        .onChange((index) => {
          this.homeTabIndex = index;
          AppStorage.set('homeTabIndex', index);
        });
        CustomTabBar({ homeTabIndex: $homeTabIndex })
          .width('100%');
      }
      .padding({ bottom: this.windowBottomHeight })
      .width('100%')
      .height('100%')
      .backgroundColor(Color.White);
    }
    .hideTitleBar(true)
    .mode(NavigationMode.Stack);
  }

更多关于HarmonyOS鸿蒙Next中关于在Tab组件子页面配置半模态面板遮挡底部导航栏的问题的实战教程也可以访问 https://www.itying.com/category-93-b0.html

9 回复
.bindSheet(
  true,
  this.SheetBuilder(),
  {
    detents: [140, 250],
    preferType: SheetType.BOTTOM,
    showClose: false,
    enableOutsideInteractive: true,
    mode: SheetMode.OVERLAY, // 改为OVERLAY模式
    onDetentsDidChange: (heightPx: number) => {
      this.sheetHeightVp = px2vp(heightPx);
    }
  }
)

试试这个

更多关于HarmonyOS鸿蒙Next中关于在Tab组件子页面配置半模态面板遮挡底部导航栏的问题的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


上面代码有报错呀: offset: { bottom: 80 }, // 动态获取底部导航高度,

SheetMode设置为EMBEDDED时,只支持挂载在Page或者NavDestination节点上,若有NavDestination优先挂载在NavDestination上。只支持在这两种页面内顶层显示。

想让半模态框显示在子页里,需要把半模态框放在NavDestination里,比如把子页的根容器设置成NavDestination,否则半模态框还是会向上查找节点,挂载到Page上,盖住底部TabBar。

需要注意多一层NavDestination可能带来的副作用,比如下面这个(情况不完全一样,但可能会有类似的副作用): 如何解决NavDestination页面做首页时跳转动画异常问题-行业常见问题-运动健康类行业实践-场景化知识 - 华为HarmonyOS开发者

NavDestination() {
  ......
  .bindSheet(
    this.showSheet!!,
    this.SheetBuilder(),
    {
      detents: [140, 250],
      preferType: SheetType.BOTTOM,
      showClose: false,
      enableOutsideInteractive: true,
      mode:SheetMode.EMBEDDED,
      onDetentsDidChange: (heightPx: number) => {
        this.sheetHeightVp = px2vp(heightPx);
      }
    }
  )
}
.height('100%')
.hideTitleBar(true)

图片

手搓一个半模态

1. 什么是半模态

半模态是一种介于传统模态(如弹窗)和非模态(如侧边栏)之间的交互形式。它通常表现为一个临时性的界面元素,既不会完全阻断用户与主界面的交互,又能提供额外的信息或操作。

2. 实现思路

2.1 设计目标

  • 不影响主界面操作
  • 提供清晰的视觉反馈
  • 支持多种触发方式
  • 易于扩展和维护

2.2 技术选型

  • 使用 React 作为前端框架
  • 采用 CSS-in-JS 方案处理样式
  • 利用 React Hooks 管理状态

3. 核心代码实现

3.1 组件结构

import React, { useState } from 'react';
import styled from 'styled-components';

const SemiModal = ({ children, trigger }) => {
  const [isVisible, setIsVisible] = useState(false);

  const handleTrigger = () => {
    setIsVisible(!isVisible);
  };

  return (
    <Container>
      <Trigger onClick={handleTrigger}>
        {trigger}
      </Trigger>
      {isVisible && (
        <ModalContent>
          {children}
        </ModalContent>
      )}
    </Container>
  );
};

3.2 样式定义

const Container = styled.div`
  position: relative;
  display: inline-block;
`;

const Trigger = styled.div`
  cursor: pointer;
  padding: 8px 16px;
  background-color: #f0f0f0;
  border-radius: 4px;
  &:hover {
    background-color: #e0e0e0;
  }
`;

const ModalContent = styled.div`
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 1000;
  min-width: 200px;
  padding: 16px;
  background-color: white;
  border: 1px solid #ddd;
  border-radius: 4px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
`;

4. 使用示例

// 在应用中使用半模态组件
const App = () => {
  return (
    <div>
      <h1>半模态示例</h1>
      <SemiModal
        trigger={<button>点击触发</button>}
      >
        <div>
          <h3>半模态内容</h3>
          <p>这是一个半模态的示例内容。</p>
          <button>确认</button>
          <button>取消</button>
        </div>
      </SemiModal>
    </div>
  );
};

5. 优化建议

5.1 动画效果

可以添加淡入淡出动画,提升用户体验:

const ModalContent = styled.div`
  /* 原有样式 */
  opacity: ${props => props.isVisible ? 1 : 0};
  transform: ${props => props.isVisible ? 'translateY(0)' : 'translateY(-10px)'};
  transition: opacity 0.3s, transform 0.3s;
`;

5.2 点击外部关闭

添加点击组件外部区域关闭的功能:

useEffect(() => {
  const handleClickOutside = (event) => {
    if (containerRef.current && !containerRef.current.contains(event.target)) {
      setIsVisible(false);
    }
  };

  document.addEventListener('mousedown', handleClickOutside);
  return () => {
    document.removeEventListener('mousedown', handleClickOutside);
  };
}, []);

6. 总结

通过以上实现,我们创建了一个简单的半模态组件。这种组件在需要提供额外信息但又不希望完全打断用户操作时非常有用。可以根据具体需求进一步扩展功能,如支持不同位置显示、多种动画效果等。

不会手搓呀,

找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17

这个是类似高德地图那种效果,需要自己实现了,

不能用 bindSheet来做 。

在HarmonyOS Next中,Tab组件子页面配置半模态面板时,若面板遮挡底部导航栏,可通过调整面板的modalTransition属性或使用bindSheet方法控制面板弹出位置与动画。确保面板的height属性未覆盖导航栏区域,或利用offset参数设置面板偏移量。检查布局层级,避免面板与Tab组件层级冲突。

问题在于半模态面板(Sheet)的层级默认高于Tab组件,导致其遮挡了自定义的底部导航栏。根据您提供的代码,可以通过调整Sheet的mode参数和修改布局结构来解决。

核心解决方案是:将Sheet的modeSheetMode.EMBEDDED改为SheetMode.OVERLAY,并确保Sheet的父容器(即地图页的Stack)不占据底部导航栏的区域。

具体修改如下:

  1. 修改地图页的Sheet配置: 在bindSheet的参数中,将mode: SheetMode.EMBEDDED 替换为 mode: SheetMode.OVERLAYOVERLAY模式允许Sheet以覆盖层形式显示,其层级管理更灵活,更容易实现位于Tab内容区之上但位于全局导航栏之下的效果。

    .bindSheet(
      true,
      this.SheetBuilder(),
      {
        detents: [140, 250],
        preferType: SheetType.BOTTOM,
        showClose: false,
        enableOutsideInteractive: true,
        // 关键修改:将 EMBEDDED 改为 OVERLAY
        mode: SheetMode.OVERLAY,
        onDetentsDidChange: (heightPx: number) => {
          this.sheetHeightVp = px2vp(heightPx);
        }
      }
    )
    
  2. 调整地图页布局,为底部导航栏预留空间: 您已经通过.padding({bottom:80})为底部预留了空间,这很好。请确保这个80的数值与您自定义底部导航栏(CustomTabBar)的高度(包括Main页面中可能存在的底部安全距离windowBottomHeight)相匹配。如果导航栏总高度不是80,请修改此值为实际高度。 这个内边距确保了地图内容本身不会被导航栏遮挡,Sheet的内容也会基于此布局进行定位。

原理说明SheetMode.EMBEDDED模式将Sheet作为当前组件树的一部分进行嵌入和裁剪,在复杂的嵌套组件(如TabContent)中,其层级关系可能超出预期。而SheetMode.OVERLAY模式使Sheet更接近于一个全局的覆盖层,通过系统协调其与页面中其他固定元素(如底部导航栏)的显示关系,配合底部内边距,即可实现Sheet内容在导航栏之上弹出,但Sheet的背景遮罩或交互区域不至于挡住导航栏本身的效果。

修改后,半模态面板应能正常在地图页显示,且不影响底部Tab栏的点击切换操作。

回到顶部