ArkUI Tabs 的使用和自定义页签

389 阅读6分钟

ArkUI 中 Tabs 的使用

当页面信息较多时,为了让用户能够聚焦于当前显示的内容,需要对页面内容进行分类,提高页面空间利用率。Tabs 组件可以在一个页面内快速实现视图内容的切换,一方面提升查找信息的效率,另一方面精简用户单次获取到的信息量。

基础用法

一、 容器的基本写法

  1. Tabs 容器内仅可以有 TabContent 子组件
  2. 每个 TabContent 容器都代表一个页面, 且其内部仅可有一个子组件
  3. TabContent 组件的 tabBar() 方法为其所对应的标签页的标题
// 选项卡容器写法
Tabs(){
	// 选项卡 1
	TabContent(){
		Text("Page_1")
	}
	.tabBar("Page_1")
	
	// 选项卡 2
	TabContent(){
		Text("Page_2")
	}
	.tabBar("Page_2")
}

TabContent.gif

二、设置标签栏的位置

​ 底部导航是应用中最常见的一种导航方式。底部导航位于应用一级页面的底部,用户打开应用,能够分清整个应用的功能分类,以及页签对应的内容,并且其位于底部更加方便用户单手操作。底部导航一般作为应用的主导航形式存在,其作用是将用户关心的内容按照功能进行分类,迎合用户使用习惯,方便在不同模块间的内容切换。

  • 使用 BarPositon( BarPosition.End ) 方法将标签栏的位置设置在页面的下方

    Tabs(){
    	// 选项卡 1
    	TabContent(){
    		Text("Page_1")
    	}
    	.tabBar("Page_1")
    	
    	// 选项卡 2
    	TabContent(){
    		Text("Page_2")
    	}
    	.tabBar("Page_2")
    }
    // 默认为 BarPosition.Start 页面标签显示在上方
    .barPosition( BarPosition.End )
    

barposition.png

​ 侧边导航是应用较为少见的一种导航模式,更多适用于横屏界面,用于对应用进行导航操作,由于用户的视觉习惯是从左到右,侧边导航栏默认为左侧侧边栏。

  • 使用 vertical ( true ) 方法将标签栏的位置设置在页面的左方

    Tabs(){
    	// 选项卡 1
    	TabContent(){
    		Text("Page_1")
    	}
    	.tabBar("Page_1")
    	
    	// 选项卡 2
    	TabContent(){
    		Text("Page_2")
    	}
    	.tabBar("Page_2")
    }
    // 默认为 false 页面标签显示在左方
    .vertical( true )
    

    !!! 配合 BarPositon.End 可以实现将标签栏的位置设置在页面的右方

vertical.png

三、关闭滑动切换页签

​ 默认情况下,导航栏都支持滑动切换,在一些内容信息量需要进行多级分类的页面,如支持底部导航+顶部导航组合的情况下,底部导航栏的滑动效果与顶部导航出现冲突,此时需要限制底部导航的滑动,避免引起不好的用户体验。

  • 对外面的 Tabs 用上 scrollable( false ) 方法限制页签的切换
Tabs(){
	TabContent(){
		// 嵌套 Tabs
		Tabs(){
			TabContent(){
				Text("Page 1-1")
			}
			.tabBar("Page 1-1")
			
			TabContent(){
				Text("Page 1-2")
			}
			.tabBar("Page 1-2")
		}
	}
	.tabBar("Page_1")
	
	TabContent(){
	}
	.tabBar("Page_2")
}
.barPosition ( BarPosition.End )

// 设置为 false 时禁用滑动切换页签
.scrollable ( false )

四、页签栏相关的设置

  1. 设置默认显示的页签

    ​ 默认在进入时会显示第一个页面 ( 索引值为 0 ),在某些场景下需要默认显示其他的页面此时可以设置容器参数 index 属性使进入页面时显示指定索引值对应的页面 ( 索引值从 0 开始 )。

    ​ 在传入的值为状态变量时如果后续改变了该状态变量的值,那么 tabs 显示的页面也会发生相应的变化 ( 在值的大小超出页面的数量时会默认显示索引值为0的页面 )

    Tabs( {index: 1} ){}
    
  2. 设置页签的显示方式

    在默认模式下页签会平方标题栏的空间,所以在页签过多时无法正常显示页签内容 ( 见 图 )

    此时使用 barMode () 方法使页签可以滚动显示。 参数 BarMod 为枚举类型,共有两个值: Fixed 表示平均分配空间、Scrollable 表示页签可以滚动显示;

    Tabs(){
    	TabContent().tabBar("TabBarName")
    	TabContent().tabBar("TabBarName")
    	TabContent().tabBar("TabBarName")
    	TabContent().tabBar("TabBarName")
    	TabContent().tabBar("TabBarName")
    	...
    }
    .barMod ( BarMod.Scrollable )
    

自定义页签样式

对于底部导航栏,一般作为应用主页面功能区分,为了更好的用户体验,会组合文字以及对应语义图标表示页签内容,这种情况下,需要自定义导航页签的样式。

image.png

一、自定义 tabBar 样式

在 TabContent 对应tabBar属性中传入自定义函数组件,并传递相应的参数。页签的样式就会变成所对应的构建函数。

  1. Builder 函数参数:
    • title: 页签对应的文字
    • path:页签所对应的图标
    • index: 表示页签对应的索引值 ( 后面有用 )
Tabs(){
	TabContent().tabBar( this.MyBar("Page1", "/image/img_1.jpg", 0) )
	TabContent().tabBar( this.MyBar("Page2", "/image/img_2.jpg", 1) )
}

// title: 页签对应的文字
// path:页签所对应的图标
// index: 表示页签对应的索引值 ( 后面有用 )
@Builder MyBuilder(title: string, path: string, index: number){
	Column(){
		Image(path)
		.width(20)
		
		Text(title)
	}
}
方法一: 事件监听
  1. 为了区别选中状态和非选中状态的页签需要实现相应的样式。
  2. 当页面发生变化时将索引值 index 传入 currentIndex,由于他是状态变量所以 currentIndex 的值的变化会令其他用到该变量的组件发生变化。
  3. 在 Builder 内部使用三元运算符判断当前页签的索引值 与 局部定义的 状态变量 curtentIndex 的值是否相等 ( 判断当前切换到的页面是否对应自身 ) 使当前被选中的页签的颜色与其他非选中组件的有所区别。
// 需写在 build() 函数外
@State currentIndex: number = 0

...

Tabs(){
	TabContent().tabBar( this.MyBar("Page1", "/image/img_1.jpg", 0) )
	TabContent().tabBar( this.MyBar("Page2", "/image/img_2.jpg", 1) )
}
// 注册事件监听 Tabs 容器的页面变化,在在进入新页面时执行内部代码并将新页面的索引值传入 index
.onchange ((index: number) => {
	this.currentIndex = number
})


@Builder MyBuilder(title: string, path: string, index: number){
	// currentIndex 只会等于一个值,所以只会有一个被选中的组件
	// 三元运算符为 真: 表示当前页签的索引值等于当前显示的页面,让组件的颜色变成红色
	//		   为 假: 表示当前页签的索引值不等于当前显示的页面,让组件的颜色变成灰色
	Column(){
		// 只有使用 SVG 类型的图片才能改变图片的填充图片
		Image(path)
		.fillColor(this.currentIndex == index ? Color.Red : Color.Gray )
		.width(20)
		
		Text(title)
		.fontColor(this.currentIndex == index ? Color.Red : Color.Gray )
	}
}
方法二: 双向绑定
  1. 为了区别选中状态和非选中状态的页签需要实现相应的样式。
  2. Tabs 中的 index 值为当前所显示的页面的索引值。在没有进行双向绑定时仅能使 Tabs 页面显示对应索引值的页面,在使用滑动或点击的方式切换页面时状态变量无法获得对应的值。而使用双向绑定后就可以获取到当前显示页面的值
  3. 此时使用双向绑定的 currentIndex 就可以替代 Onchange()
// 需写在 build() 函数外
@State currentIndex: number = 0

...

// 将状态变量 currentIndex 与Tabs 的 index 参数进行双向绑定
Tabs({ index: $$currentIndex }){
	TabContent().tabBar( this.MyBar("Page1", "/image/img_1.jpg", 0) )
	TabContent().tabBar( this.MyBar("Page2", "/image/img_2.jpg", 1) )
}



@Builder MyBuilder(title: string, path: string, index: number){
	// currentIndex 只会等于一个值,所以只会有一个被选中的组件
	// 三元运算符为 真: 表示当前页签的索引值等于当前显示的页面,让组件的颜色变成红色
	//		   为 假: 表示当前页签的索引值不等于当前显示的页面,让组件的颜色变成灰色
	Column(){
		// 只有使用 SVG 类型的图片才能改变图片的填充图片
		Image(path)
		.fillColor(this.currentIndex == index ? Color.Red : Color.Gray )
		.width(20)
		
		Text(title)
		.fontColor(this.currentIndex == index ? Color.Red : Color.Gray )
	}
}

二、自定义页签

对于底部导航栏,一般作为应用主页面功能区分,为了更好的用户体验,会组合文字以及对应语义图标表示页签内容,这种情况下,需要自定义导航页签的样式。而内部的 tabBar 限制较多为实现下图效果只能摈弃 Tabs 的 tabBar,使用自己写的 tabbar 然后绑定 Tabs 容器

image.png

方法一:注册事件
  1. 创建 Tabs 时内部的 TabContent 页面容器不要设置 tabBar 页签,且使用 .barHeight( 0 ) 将页签栏的高度设置为 0 为后续自定义的页签栏留出位置。
  2. 创建 Tabs 控制器对象 TabController,并将其与 Tabs 进行绑定。
  3. 设置自定义页签栏 ( 内部样式可以完全自定义 ),页签的样式会随着 currentIndex 的值变化而变化
  4. 在 Tabs 上注册 onChange() 事件监听页面的变化并将值赋给 currentIndex。 ↑↑↑↑↑↑↑↑↑↑↑↑ ( 页面的变化带动自定义页签的变化 )
  5. 在自定义页签上注册 onClick() 事件,在页签被点击时借助 Tabs 控制器对象 tabscontroller 的 changeIndex( 索引值 )方法改变当前正在显示的页面。 ( 点击页签切换页面 ) !!!! 需要将当前的索引值赋值给 currentIndex 不然页签的样式无法更新
@Entry
@Component
struct Test2 {
  @State CureentIndex: number = 0
  
  // 创建 Tabs 容器的控制器对象
  tabcontroller: TabsController = new TabsController()

  build() {
    Column(){
      // 将 Tabs 控制器对象绑定 Tabs 容器
      Tabs({controller: this.tabcontroller}){
        TabContent(){ Text("Page_1") }
        TabContent(){ Text("Page_2") }
        TabContent(){ Text("Page_3") }
      }
      .barHeight(0)
      .height("80%")
      // 监听 Tabs 页面的变化, 以带动页签的变化
      .onChange((index: number) => {
        this.CureentIndex = index
      })

      Row({space: 50}){
          Text("Page_1")
          	// 注册点击事件,借助 this.tabcontroller.changeIndex(0) 切换显示页面
            .onClick(() => {
            	// !!!! 需要将当前的索引值赋值给 currentIndex 不然页签的样式无法更新
            	this.currentIndex = 0
            	this.tabcontroller.changeIndex(0)
            })
            .fontColor(this.CureentIndex == 0 ? Color.Red : Color.Black)

          Text("Page_2")
            .onClick(() => {
            	this.currentIndex = 1
            	this.tabcontroller.changeIndex(1)
            })
            .fontColor(this.CureentIndex == 0 ? Color.Red : Color.Black)

          Text("Page_3")
            .onClick(() => {
            	this.currentIndex = 2
            	this.tabcontroller.changeIndex(2)
            })
            .fontColor(this.CureentIndex == 0 ? Color.Red : Color.Black)

        }
        .width("100%")
      }
  }
}
方法二: 双向绑定
  1. 创建 Tabs 时内部的 TabContent 页面容器不要设置 tabBar 页签,且使用 .barHeight( 0 ) 将页签栏的高度设置为 0 为后续自定义的页签栏留出位置。
  2. 设置自定义页签栏 ( 内部样式可以完全自定义 ),在 Text 上绑定点击事件令 Tabs 显示文本对应的索引值的页面。
  3. 将 Tabs 的 index 参数和 CurrentIndex 进行双向绑定。当使用滑动或切换改变页面时 currentIndex 的值也会发生变化,当状态变量变化时 Text 的样式会重新渲染。
@Entry
@Component
struct Test2 {
  @State CureentIndex: number = 0

  build() {
    Column(){
      Tabs({index: $$this.CureentIndex}){
        TabContent(){ Text("Page_1") }
        TabContent(){ Text("Page_2") }
        TabContent(){ Text("Page_3") }
      }
      .barHeight(0)
      .height("80%")

	  // 自定义 TabBar, 内部样式可以完全自定义
      Row({space: 50}) {
      	  Text("Page_1")
      	  // 点击该文本时将 Tabs 切换为索引值为 0 的页面
      	  .onClick(() => { this.CureentIndex = 0})
      	  
      	  // 当状态变量发生变化时判断当前显示的页面与自身的索引是否一样,以显示不同的文字样式
      	  .fontColor(this.CureentIndex == 0 ? Color.Red : Color.Black)
      
          Text("Page_2")
          .onClick(() => { this.CureentIndex = 1})
          .fontColor(this.CureentIndex == 1 ? Color.Red : Color.Black)
          
          Text("Page_3")
          .onClick(() => { this.CureentIndex = 2})
          .fontColor(this.CureentIndex == 2 ? Color.Red : Color.Black)

        }
        .width("100%")
      }
  }
}