ArkUI使用Tabs组件实现页签导航

732 阅读5分钟

概述

Tabs组件是一个页签导航组件,我们常见的手机最底部的导航按钮可以使用Tab组件实现。其中一个页签对应下图中微信、通讯录、发现、我的其中一个页面。

导航栏.jpg Tabs组件内只允许使用子组件TabContent,每一个TabContent对应于一个页签。下面我们直接进入主题,使用TabsTabContent实现一个页签导航。

示意图.png

对于使用Tabs切换页签的界面可以分为两个部分——内容部分TabContent和切换栏TabBar部分,TabContent为对应显示的主要内容。而tabBar则是通过给TabContent设置tabBar属性进行设置,用来进行切换时显示的内容。

Tabs组件的基础使用

declare class TabContentAttribute extends CommonMethod<TabContentAttribute> {
    tabBar(value: string | Resource | CustomBuilder | {
        icon?: string | Resource;
        text?: string | Resource;
    }): TabContentAttribute;
}

可以查看TabContent的属性查看tabBar支持传入的参数类型,可以发现其可以传入stringicon等资源文件以及一个不认识的CustomBuilder,这里我们首先使用最简单的string类型来进行尝试使用。

@Entry
@Component
struct TestPage {
    
  build() {
    Tabs() {
      TabContent() {
        Text("首页的内容").fontSize(30)
      }.tabBar("首页")
​
      TabContent() {
        Text("推荐的内容").fontSize(30)
      }.tabBar("推荐")
​
      TabContent() {
        Text("发现的内容").fontSize(30)
      }.tabBar("发现")
​
      TabContent() {
        Text("我的内容").fontSize(30)
      }.tabBar("我的")
    }
  }
}

contentOne.gif

可以发现,就这几行简单的代码已经可以实现页签的切换效果了。但我们发现这个TabBar的位置在屏幕顶上,我们期望其在最下方屏幕底部。

通过查看Tabs组件的参数,发现一个参数barPosition,有点类似设置tabBar的位置

interface TabsInterface {
    (value?: {
        barPosition?: BarPosition;
        index?: number;
        controller?: TabsController;
    }): TabsAttribute;
}
​
​
declare enum BarPosition {
    Start,
    End
}

其参数类型为BarPosition,可以设置的参数为StartEnd,默认设置为Start,那修改为End后,可以发现可以达到预期的效果,tabBar处于屏幕底部了。

image-20221201230129194.png

对于tabBar,如果要设置宽高,可以在Tabs组件上设置.barWidth("100%").barHeight(76)来设置其宽高,具体的参数由实际情况设置。

通常我们一个页签的内容不会写在一个页面中,会采用组件化的形式传入TabContent中。

首先我们创建四个组件分别为HomePageRecommendationPageFoundPageProfilePage放在view文件夹中,对应我们要切换的四个页签,分别设置为如下内容。

@Component
export struct HomePage {
  build() {
    Row() {
      Column() {
        Text("首页的内容").fontSize(40)
      }.height("100%")
    }.width("100%")
  }
}
@Component
export struct RecommendationPage {
  build() {
    Row() {
      Column() {
        Text("推荐的内容").fontSize(40)
      }.height("100%")
    }.width("100%")
  }
}
@Component
export struct FoundPage {
  build() {
    Row() {
      Column() {
        Text("寻找的内容").fontSize(40)
      }.height("100%")
    }.width("100%")
  }
}
@Component
export struct ProfilePage {
  build() {
    Row() {
      Column() {
        Text("我的内容").fontSize(40)
      }.height("100%")
    }.width("100%")
  }
}

那么分离后的Main页面的内容就变成了,效果还是和之前的内容一样,只不过我让显示的页面文字居中了。

import { FoundPage } from '../View/FoundPage';
import { RecomedationPage } from '../View/RecomedationPage';
import { ProfilePage } from '../View/ProfilePage';
import { HomePage } from '../View/HomePage';
​
​
@Entry
@Component
struct MainApp {
​
  build() {
    Tabs({ barPosition: BarPosition.End }) {
      TabContent() {
        HomePage()
      }.tabBar("首页")
​
      TabContent() {
        RecomedationPage()
      }.tabBar("推荐")
​
      TabContent() {
        FoundPage()
      }.tabBar("寻找")
​
      TabContent() {
        ProfilePage()
      }.tabBar("我的")
    }
    .barWidth("100%")
    .barHeight(86)
  }
}

页面内容暂时先不管,但我感觉我们的切换栏有点太简陋了,因为一般切换栏都会使用图标或者图片,让页面更好看。之前也介绍过,tabBar可以通过string、Resource或者CustomBuilder传入参数,icon的用法和之前的string一样,这里我们使用另外一种常用的方式,CustomBuilder来进行实现,并学习一些@Builder的知识。

declare class TabContentAttribute extends CommonMethod<TabContentAttribute> {
    tabBar(value: string | Resource | CustomBuilder | {
        icon?: string | Resource;
        text?: string | Resource;
    }): TabContentAttribute;
}

tabBar的特殊设置实现活跃页签状态高亮

CustomBuilder其实就是一个参数类型的组件,但这个组件需要是被@Builder装修器装饰的一个函数组件。意思其实就是写一个小组件并使用@Builder装饰,作为参数传入tabBar中,显示在切换栏中。这里我准备了四个图片。

导航图片.png

使用一个数组存储其`src`路径,并且对应的文字也用数组存储。
ImageSrc:Array<string> = ["/asserts/TabImg/HomeImg.png",
                          "/asserts/TabImg/RecomedationImg.png",
                          "/asserts/TabImg/FoundImg.png",
                          "/asserts/TabImg/ProfileImg.png"]
​
tabContent:Array<string> = ["首页","推荐","发现","我的"]

并且使用index来从数组中取出对应的src路径,这就是我们所使用的tabBar的参数。

@Builder TabBuilder(index: number) {
    Column() {
        Image(this.ImageSrc[index])
            .objectFit(ImageFit.Contain)
            .width(50)
            .height(50)
        Text(this.tabContent[index]).fontSize(14)
​
    }.width('100%')
}

MainApp中,以this.TabBuilder(index)的方式对tabBar的参数传入其对应的index,按照顺序分别为0,1,2,3

@Entry
@Component
struct MainApp { 
    // 对应的图片路径数组和文字内容数组
    ...
    
  @Builder TabBuilder(index: number) {
    Column() {
      Image(this.currentIndex == index?this.SelectImgSrc[index]:this.ImageSrc[index])
        .objectFit(ImageFit.Contain)
        .width(50)
        .height(50)
      Text(this.tabContent[index]).fontSize(14)
​
    }.width('100%')
  }
​
  build() {
    Tabs({ barPosition: BarPosition.End}) {
      TabContent() {
        HomePage()
      }.tabBar(this.TabBuilder(0))
​
      TabContent() {
        RecomedationPage()
      }.tabBar(this.TabBuilder(1))
​
      TabContent() {
        FoundPage()
      }.tabBar(this.TabBuilder(2))
​
      TabContent() {
        ProfilePage()
      }.tabBar(this.TabBuilder(3))
    }.onChange((index: number) => {
      this.currentIndex = index
    })
    .barWidth("100%")
    .barHeight(76)
  }
}

contentTwo.gif

但是我们发现,点击的时候确实发生了内容的切换,但是tabBar中却没有感知,这是由于我们没有对选中状态下的tabBar做特殊处理。一般文字我们会使用不同颜色来表示,而图片则使用不同颜色的图片进行特殊表示,所以对于当前点击的页签还需要做特殊处理,这里我准备了另一组图片,用来提示当前活跃的页面。

image-20221201234126111.png

具体实现如下,我还是使用一个数组,来存储“高亮”的图片路径。
SelectImgSrc:Array<string> = ["/asserts/TabImg/HomeSelectImg.png",
                              "/asserts/TabImg/RecomedationSelectImg.png",
                              "/asserts/TabImg/FoundSelectImg.png",
                              "/asserts/TabImg/ProfileSelectImg.png"]

由于需要记录当前是那一组处于活跃状态,所以需要定义一个currentIndex来标志当前活跃的页签。

@State currentIndex: number = 0

而我们的TabBuilder也需要进行相应的修改,要使currentIndex对应的tabBar使用“高亮”的图片也就是其src路径要取自SelectImgSrc中,而其他处于未选中状态的TabBar则取自ImageSrc中。这里就是一个简单的判断

if(index == current) {
    src = SelectImgSrc[index]
}else {
    src = ImageSrc[index]
}//伪代码

那么再简单点就是使用三目运算符,currentIndex == index ? SelectImgSrc[index]:Image[index],运用到需要传入Src对应的位置如下。

  @Builder TabBuilder(index: number) {
    Column() {
      Image(this.currentIndex == index?this.SelectImgSrc[index]:this.ImageSrc[index])
        .objectFit(ImageFit.Contain)
        .width(50)
        .height(50)
      Text(this.tabContent[index]).fontSize(14)
​
    }.width('100%')
  }

最终的效果如下,这样我们就简单实现了一个页签导航的效果,具体每一页的内容按照需求完善即可。

contentThree.gif

参考链接:

更多的Tabs组件详细用法参考:gitee.com/openharmony…