HarmonyOS鸿蒙Next中爱奇艺的Tab是怎么实现的

HarmonyOS鸿蒙Next中爱奇艺的Tab是怎么实现的 cke_313.png

如图,tabs中间的 “问AI” 效果是怎么实现的


更多关于HarmonyOS鸿蒙Next中爱奇艺的Tab是怎么实现的的实战教程也可以访问 https://www.itying.com/category-93-b0.html

11 回复

你好,从6.1.0(23) 版本开始,通过barFloatingStyle 设置页签栏为悬浮样式和沉浸光感效果。

通过 onContentWillChange  去拦截页面切换,点击‘问AI’时不进行切换。

cke_49369.gif

import {
  HdsNavigation,
  HdsNavigationTitleMode,
  HdsTabs,
  HdsTabsController,
  hdsMaterial
} from '@kit.UIDesignKit';

@Entry
@Component
export struct Index {
  private controller: HdsTabsController = new HdsTabsController();
  @State private currentIndex: number = 0
  @State private showAI: boolean = false

  build() {
    HdsNavigation() {
      HdsTabs({ controller: this.controller }) {
        TabContent() {

        }
        .backgroundColor('#80eeeeee')
        .tabBar(this.tabItem('首页', 0))

        TabContent() {
        }
        .backgroundColor('#80ffff00')
        .tabBar(this.tabItem('免费', 1))

        TabContent() {
        }
        .tabBar(this.tabItem('问AI', 2))

        TabContent() {
        }
        .backgroundColor('#800000ff')
        .tabBar(this.tabItem('会员', 3))

        TabContent() {
        }
        .backgroundColor('#80ff00ff')
        .tabBar(this.tabItem('我的', 4))
      }
      .scrollable(false)
      .animationDuration(0)
      .barOverlap(true)
      .vertical(false)
      .barPosition(BarPosition.End)
      .onContentWillChange((currentIndex, comingIndex) => {
        if (comingIndex == 2) {
          this.showAI = true
          return false;
        }
        return true;
      })
      .barFloatingStyle({
        barBottomMargin: 28,
        systemMaterialEffect: {
          materialType: hdsMaterial.MaterialType.ADAPTIVE,
          materialLevel: hdsMaterial.MaterialLevel.ADAPTIVE
        }
      })
      .onChange((index: number) => {
        this.currentIndex = index
      })
    }
    .backgroundColor('#eee')
    .mode(NavigationMode.Stack)
    .hideBackButton(true)
    .titleMode(HdsNavigationTitleMode.MINI)
    .ignoreLayoutSafeArea([LayoutSafeAreaType.SYSTEM], [LayoutSafeAreaEdge.TOP, LayoutSafeAreaEdge.BOTTOM])
  }

  @Builder
  tabItem(title: string, index: number) {
    if (title === '问AI') {
      Text(title)
        .fontColor(this.showAI ? '#fff' : '#4F9860')
        .fontSize(18)
        .fontWeight(FontWeight.Medium)
        .backgroundColor(this.showAI ? '#ff17d042' : '#D5F1DB')
        .borderRadius('50%')
        .padding(10)
        .bindPopup(this.showAI, {
          builder: this.aiBuilder,
          placement: Placement.Top,
          popupColor: '#eee',
          backgroundBlurStyle: BlurStyle.NONE,
          offset: { y: -10 },
          onStateChange: (e) => {
            if (!e.isVisible) {
              this.showAI = false
            }
          }
        })
    } else {
      Text(title)
        .fontColor(index === this.currentIndex ? '#4F9860' : '#51473F')
        .fontSize(18)
        .fontWeight(index === this.currentIndex ? FontWeight.Medium : FontWeight.Normal)
    }
  }

  @Builder
  aiBuilder() {
    Column({ space: 5 }) {
      Image($r('app.media.startIcon')).width(24)
      Text('This is ai Popup').fontSize(15)
    }
    .width('100%')
    .padding(20)
  }
}

更多关于HarmonyOS鸿蒙Next中爱奇艺的Tab是怎么实现的的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


开发者您好,这是通过设置沉浸光感HdsTabs的自定义样式实现的,可以参考我的这篇文章:

沉浸光感HDS底部页签:HdsTabs自定义tabBar解决方案
注:需了解沉浸光感适配。

试试看:

import { hdsMaterial, HdsTabs, HdsTabsController } from '@kit.UIDesignKit';

@Entry
@Component
struct Index {
	private controller: HdsTabsController = new HdsTabsController();
	@State selectedIndex: number = 0;

	build() {
		Column() {
				HdsTabs({ controller: this.controller }) {
					TabContent() {

					}
					.tabBar(this.tabBuilder(0, "首页"))

					TabContent() {

					}
					.tabBar(this.tabBuilder(1, "免费"))

					TabContent() {

					}
					.tabBar(this.tabBuilder(2, "问AI"))

					TabContent() {

					}
					.tabBar(this.tabBuilder(3, "会员"))

					TabContent() {

					}
					.tabBar(this.tabBuilder(4, "我的"))

				}
				.barOverlap(true)
				.barPosition(BarPosition.End)
				.vertical(false)
				// 设置页签栏悬浮样式。
				.barFloatingStyle({
					barWidth: { smallWidth: 300 },
					barBottomMargin: 28,
					gradientMask: { maskColor: '#66F1F3F5', maskHeight: 92 },
					systemMaterialEffect: {
							materialType: hdsMaterial.MaterialType.IMMERSIVE,
							materialLevel: hdsMaterial.MaterialLevel.ADAPTIVE
					},
				})
				.onTabBarClick((index: number) => {
					this.selectedIndex = index;
				})
		}
	}

	@Builder
	tabBuilder(index: number, name: string) {
		Column() {
			Text(name)
				.fontColor(this.selectedIndex === index ? "#079A46" : "#ff535654")
				.fontWeight(this.selectedIndex === index ? 500 : 400)
		}
		.width('100%')
		.height(40)
		.justifyContent(FlexAlign.Center)
		.borderRadius(50)
		.backgroundColor(this.selectedIndex === index ? "#9ab8dcc4" : Color.Transparent)
	}

}

cke_865.png

背景知识:

可以使用 HdsNavigation + HdsTabs 来实现,barOverlap 数据来确定使用漂浮tab。

问题解决:

示例代码:

import { HdsNavigation, HdsTabs, HdsTabsController, ScrollEffectType, hdsMaterial } from '@kit.UIDesignKit';
import { LengthMetrics, promptAction } from '@kit.ArkUI';
import common from '@ohos.app.ability.common';

interface HomeTabItem {
    title: string;
    subTitle: string;
    tag: string;
}

@Entry
@Component
struct HdsNavigationPage {
    private tabsController: HdsTabsController = new HdsTabsController();
    @State currentIndex: number = 0;
    private tabTitles: string[] = [
        '首页',
        '免费',
        'Ai',
        '会员',
        '我的'
    ];
    context = this.getUIContext().getHostContext() as common.UIAbilityContext

    build() {
        HdsNavigation() {
            HdsTabs({ controller: this.tabsController, index: this.currentIndex }) {
                TabContent() {
                    this.TabPage(this.tabTitles[0])
                }
                .tabBar(this.tabTitles[0])

                TabContent() {
                    this.TabPage(this.tabTitles[1])
                }
                .tabBar(this.tabTitles[1])

                TabContent() {
                    this.TabPage(this.tabTitles[2])
                }
                .tabBar(this.tabTitles[2])

                TabContent() {
                    this.TabPage(this.tabTitles[3])
                }
                .tabBar(this.tabTitles[3])

                TabContent() {
                    this.TabPage(this.tabTitles[4])
                }
                .tabBar(this.tabTitles[4])
            }
            .width('100%')
            .height('100%')
            .barPosition(BarPosition.End)
            .vertical(false)
            //设置是否 悬浮的tab
            .barOverlap(true)
            .onChange((index: number) => {
                console.log(`HdsNavigation index=${index}`)
                this.currentIndex = index;
            })
            .onContentWillChange((currentIndex: number, comingIndex: number) => {
              //注意请使用 comingIndex 来判断
                if (comingIndex == 2) {
                    //可以在这里处理点击ai后进行处理点击ai tab的按钮逻辑
                    this.getUIContext().getPromptAction().showToast({
                        message: "Ai",
                        duration: 2000,
                        bottom: 80
                    })
                    return false
                } else {
                    return true
                }
                // onContentWillChange 方法的返回值很明确,内容将改变吗? true 改变 false不改变

            })
            //悬浮tab样式
            .barFloatingStyle({
                barBottomMargin: 28,
                systemMaterialEffect: {
                    materialType: hdsMaterial.MaterialType.ADAPTIVE,
                    materialLevel: hdsMaterial.MaterialLevel.ADAPTIVE
                }
            })
        }
        .width('100%')
        .height('100%')
        .backgroundColor($r('sys.color.ohos_id_color_background'))
        .mode(NavigationMode.Stack)
        .hideBackButton(true)

    }

    @Builder
    private TabPage(title: string) {
        Scroll() {
            Column({ space: 12 }) {
                this.Banner(title)
            }
            .width('100%')
            .padding({
                left: 16,
                right: 16,
                top: 16,
                bottom: 24
            })
        }
        .width('100%')
        .height('100%')
        .backgroundColor(Color.Grey)
        .edgeEffect(EdgeEffect.Spring)
    }

    @Builder
    private Banner(title: string) {
        Column({ space: 8 }) {
            Text(title)
                .fontSize(28)
                .fontWeight(FontWeight.Bold)
                .fontColor(Color.White)
            Text('HdsNavigation + HdsTabs')
                .fontSize(15)
                .fontColor('#EAF1FF')
        }
        .alignItems(HorizontalAlign.Start)
        .justifyContent(FlexAlign.End)
        .width('100%')
        .height(168)
        .padding(20)
        .borderRadius(16)
        .linearGradient({
            angle: 135,
            colors: [
                ['#0A59F7', 0],
                ['#19A08E', 1]
            ]
        })
    }
}

真机演示:

cke_3081.gif

您好,这是通过沉浸光感Hds Tabs的自定义Tabs所实现的

这个就是自定义的,底部放一个全屏 Stack,然后下面是内容区,中间叠一个自定义 TabBar

至于选择后的凸起,可以参考下面文档实现效果

HarmonyOS 如何实现tabbar被选中时有凸起效果(API12+)-华为开发者问答 | 华为开发者联盟

尊敬的开发者,您好:

如果您是想在常态下,让中间的“问AI”Tab以特定样式展示,可以在TabBar Builder中通过条件控制TabBar样式的展示。 可以在2楼代码的基础上,修改tabBuilder的内容,如下:

@Builder
tabBuilder(index: number, name: string) {
  Column() {
    Text(name)
      .fontColor(name === '问AI' ? '#079A46' : (this.selectedIndex === index ? '#079A46' : '#ff535654'))
      .fontWeight(this.selectedIndex === index ? 500 : 400);
  }
  .width('100%')
  .height(40)
  .justifyContent(FlexAlign.Center)
  .borderRadius(50)
  .backgroundColor(name === '问AI' ? '#9ab8dcc4' : Color.Transparent);
}

做上述修改后可以让中间“问AI”的样式常驻,这里通过 name === ‘问AI’ 进行判断,使用时判断条件根据实际修改即可。

可参考文档:HdsTabs

这种效果一般不是默认 Tabs 直接给出来的,而是自定义 TabBar/底部导航。

实现思路可以这样拆:

  1. 用 Tabs 只负责内容页和 currentIndex 状态,或者直接用 Stack + 条件渲染切页面。
  2. 底部自己画一层 Row,普通 tab item 等宽排列;中间“问AI”用单独组件放在 Stack 上层,设置更高 zIndex、圆角、渐变/阴影,并向上偏移。
  3. 点击普通项时更新 currentIndex;点击中间按钮可以切到对应 tab,也可以打开独立 AI 页面或弹窗。
  4. 如果仍想基于 Tabs,可以给各 TabContent 配自定义 tabBar;但中间凸起、不等宽、悬浮按钮这类效果,自己绘制底部导航通常更灵活。

关键点是把“视觉形状”和“页面切换状态”分离,不要强依赖默认等宽 TabBar。

自定义tab啊,

在HarmonyOS Next中,爱奇艺的Tab通常基于ArkTS的Tabs组件实现,通过TabContent和自定义TabBar构建。利用@State管理选中索引,结合@Builder动态渲染页面内容,或使用LazyForEach优化列表性能。部分场景可能通过自定义indicator和动画完成切换。

可通过自定义 Tabs 组件的 tabBar 实现“问AI”这种中间凸起按钮。核心是利用 @Builder 构建不同样式的 Tab 项,让中间项使用负的 margintranslate 向上偏移,并配以圆形背景。

简要代码示例:

@Entry
@Component
struct AiTabDemo {
  @State currentIndex: number = 0
  private tabsController: TabsController = new TabsController()

  @Builder tabBarBuilder(title: string, index: number, isCenter: boolean = false) {
    Column() {
      if (isCenter) {
        // 中间凸起按钮
        Image($r('app.media.ai_icon'))
          .width(48).height(48)
          .borderRadius(24)
          .backgroundColor('#FF5500')
          .margin({ top: -20 })   // 向上偏移,形成凸起效果
      } else {
        Text(title)
          .fontSize(14)
          .fontColor(this.currentIndex === index ? '#FF5500' : '#182431')
      }
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .onClick(() => {
      this.currentIndex = index
      this.tabsController.changeIndex(index)
    })
  }

  build() {
    Tabs({ controller: this.tabsController, index: 0 }) {
      TabContent() { Text('首页内容') }
        .tabBar(this.tabBarBuilder('首页', 0))

      TabContent() { Text('发现内容') }
        .tabBar(this.tabBarBuilder('发现', 1))

      TabContent() { Text('AI 内容') }
        .tabBar(this.tabBarBuilder('', 2, true))   // 中间项

      TabContent() { Text('消息内容') }
        .tabBar(this.tabBarBuilder('消息', 3))

      TabContent() { Text('我的内容') }
        .tabBar(this.tabBarBuilder('我的', 4))
    }
    .barMode(BarMode.Fixed)
    .onChange((index: number) => {
      this.currentIndex = index
    })
  }
}

要点

  • 中间 Tab 项传 isCenter: true,使用 Image 并设置 margin.top 为负值,使其视觉上突出于 TabBar。
  • 点击时通过 tabsController.changeIndex() 切换页面,保持与其他 Tab 一致的切换逻辑。
  • 为避免背景遮挡,可设置 TabBar 的背景为透明,或在 Tabs 上通过 barHeight 适当留高,给凸起按钮留出空间。

这样即可实现类似“问AI”的凸起 Tab 效果。

回到顶部