鸿蒙6.0应用开发——Tabs页签变更效果

在使用Tabs组件进行开发时,特别是当Tabs组件作为二级导航使用时,业务需求往往需要对Tabs的标签页进行更精细的控制。下文将介绍几种定制标签页显示逻辑的场景。

显示指定页签与预加载

Tabs组件的TabContent默认在首次切换到该标签页时加载。如果TabContent中的内容或初始化逻辑较为复杂,加载速度较慢,则会影响标签页切换的流畅性,进而影响用户体验。此时,如果应用能在切换前预加载相应的标签页,将显著提升使用流畅度。

在这里插入图片描述

实现原理

通过TabControllerpreloadItem()方法可以预加载指定子节点。该方法参数为需要预加载的index数组,无参调用此方法时,会一次性加载所有指定的子节点。因此,为了性能考虑,建议分批加载子节点。代码示例这里做法是当切换到某页签时,预加载所选页签左右两侧的页签内容。

开发步骤

定义subsController属性,并在Tabs的onChange函数中调用preloadItem()预加载当前页签两侧页签。

@Component
export default struct InTabsComponent {
  // ...
  private subsController: TabsController = new TabsController();
  // ...
  build() {
    // ...
            Tabs({
              // ...
              controller: this.subsController,
              // ...
            }) {
              // ...
            }
            // ...
            .onChange((index: number) => {
              this.focusIndex = index;
              this.tabBarItemScroller.scrollToIndex(index, true, ScrollAlign.CENTER);
              // preload the left and right item
              let preloadItems: number[] = [];
              if (index - 1 >= 0) {
                preloadItems.push(index - 1);
              }
              if (index + 1 < this.selectTabsViewModel.selectedTabs.length) {
                preloadItems.push(index + 1);
              }
              this.subsController.preloadItems(preloadItems);
            })
            // ...
  }
}

切换到指定页签

Tabs组件除了自带的滑动切换和点击切换功能外,还提供了两种可编程方式来切换页签。第一种是通过调用TabsController的changeIndex()方法,切换到指定的index;第二种是定义一个由@State修饰的变量currentIndex,并将其绑定到Tabs,通过修改currentIndex的值来触发页签切换。

在这里插入图片描述

开发步骤

定义currentIndex变量和tabController属性,并绑定到Tabs。在按钮onClick函数中,调用tabController.changeIndex()或者直接修改currentIndex变量切换页签。

@Component
export default struct SwitchTabComponent {
  // ...
  @State currentIndex: number = 0;
  private tabController: TabsController = new TabsController();

  // ...

  build() {
    Column() {
      Row() {
        Button('Previous Tab')
          .onClick(() => {
            this.tabController.changeIndex((this.currentIndex + 3) % 4); // call tabController.changeIndex() to switch tab
          })
           // ...

        Button('Next Tab')
          .onClick(() => {
            this.currentIndex = (this.currentIndex + 1) % 4; // change currentIndex to switch tab
          })
           // ...
      }

      Tabs({
        controller: this.tabController,
        index: $$this.currentIndex // use $$ for two-way data binding
      }) {
        // ...
      }
    }

  }
}

此外,Tabs可注册切换前的处理函数,进一步控制切换行为。详情请参见切换至指定页签

增删Tabs页签

在日常的应用开发中,经常需要实现用户自定义选择频道的功能。通常,这些自定义选择的频道会通过Tabs组件来展示,因此需要动态地更新Tabs的页签。本示例设计了一对父子组件来演示这一功能。父组件负责显示页签及其内容,并在页签栏的最右侧设置一个“更多”按钮。点击此按钮会弹出一个窗口,供用户选择需要显示的页签。该弹窗内容由子组件提供,关闭弹窗后,父组件的页签将被更新。

在这里插入图片描述

实现原理

定义selectTabsViewModel对象,其中的数组allTabs表示所有可选择页签,数组selectedTabs表示选中的需要显示的页签,并通过@Link绑定到父组件InTabComponent和子组件SelectTabsComponent中。子组件SelectTabsComponent作为一个弹窗用于选择需要显示的页签。选择完成后,关闭弹窗并更新 selectTabsViewModel对象中的选中页签数组 selectedTabs,以触发父组件InTabComponent的页签更新。

在这里插入图片描述

开发步骤

  1. 定义SelectTabsViewModel类,包含所有可选择页签数组allTabs属性,和需要显示的页签数组selectedTabs属性,及更新显示页签数组的方法updateSelectedTabs()。

    @Observed
    class TabItemArray extends Array<TabItemViewModel> {
    }
    
    @Observed
    export default class SelectTabsViewModel {
      allTabs: TabItemArray = new TabItemArray();
      selectedTabs: TabItemArray = new TabItemArray();
      // ...
    
      async loadTabs(ctx: Context) {
        // ...
      }
    
      // apply changes to the selected tabs
      updateSelectedTabs() {
        let tempTabs: TabItemViewModel[] = [];
        for (let tab of this.allTabs) {
          if (tab.isChecked) {
            tempTabs.push(tab);
          }
        }
        this.selectedTabs = tempTabs;
      }
    }
    
  2. 在InTabsComponent中定义selectTabsViewModel属性,并且在aboutToAppear()方法中初始化。

    @Component
    export default struct InTabsComponent {
      @State selectTabsViewModel: SelectTabsViewModel = new SelectTabsViewModel();
      // ...
      async aboutToAppear() {
        // ...
    
        await this.selectTabsViewModel.loadTabs(this.ctx);
        // ...
      }
      // ...
    }
    
  3. 利用ForEach组件将selectTabsViewModel.selectedTabs属性绑定到Tabs的页签上。

    Tabs({
      // ...
    }) {
      // bind selected tabs to ui
      ForEach(this.selectTabsViewModel.selectedTabs, (tab: TabItemViewModel, index: number) => {
        if (index === this.selectTabsViewModel.selectedTabs.length - 1) {
          TabContent() {
            // ...
          }
          .tabBar(this.tabBuilder(index, tab))
          // ...
        } else {
          // ...
        }
      }, (tab: TabItemViewModel, index: number) => index + '_' + JSON.stringify(tab))
    }
    
  4. 在更多按钮的弹窗中初始化SelectTabsComponent,并将selectTabsViewModel属性作为双向绑定属性传入。在关闭弹窗处理函数中调用selectTabsViewModel.updateSelectedTabs()方法,更新需要显示的组件。

    @Builder
    sheetBuilder() {
      //select tabs to show
      SelectTabsComponent({ selectTabsViewModel: this.selectTabsViewModel })
    }
    build() {
      Scroll() {
        Column() {
          BannerComponent()
    
          Stack({ alignContent: Alignment.TopEnd }) {
            Row() {
              Image($r('app.media.more'))
                // ...
                .onClick(() => {
                  this.showSelectTabsComponent = !this.showSelectTabsComponent;
                })
            }
            // ...
            .zIndex(1)
            .bindSheet($$this.showSelectTabsComponent, this.sheetBuilder(), {
              detents: [SheetSize.MEDIUM, SheetSize.MEDIUM, 500],
              preferType: SheetType.BOTTOM,
              title: { title: $r('app.string.bind_sheet_title') },
              onWillDismiss: (dismissSheetAction: DismissSheetAction) => {
                // update tab when closing modal box
                this.selectTabsViewModel.updateSelectedTabs();
                if (this.selectTabsViewModel.selectedTabs.length > 0) {
                  this.subsController.changeIndex(0);
                }
                dismissSheetAction.dismiss();
              }
            })
            // ...
          }
        }
      }
      // ...
    }
    
  5. 在SelectTabsComponent中将selectTabsViewModel.allTabs属性渲染成toggle组件,并且注册toggle组件的切换处理函数onChange(),在其中修改该页签的选择状态isChecked属性,供更新显示页签方法selectTabsViewModel.updateSelectedTabs()使用。

    @Component
    export default struct SelectTabsComponent {
      @State checkedChange: boolean = false;
      @Link selectTabsViewModel: SelectTabsViewModel;
    
      build() {
        Grid() {
          ForEach(this.selectTabsViewModel.allTabs, (tab: TabItemViewModel) => {
            GridItem() {
              Row() {
                Toggle({ type: ToggleType.Button, isOn: tab.isChecked }) {
                  // ...
                }
                // ...
                .onChange((isOn: boolean) => {
                  tab.isChecked = isOn;
                  this.checkedChange = !this.checkedChange;
                })
              }
            }
          }, (tab: TabItemViewModel, index: number) => index + '_' + JSON.stringify(tab))
        }
        .columnsTemplate(('1fr 1fr 1fr 1fr') as string)
        .height(Constants.FULL_HEIGHT)
      }
    }
    
Logo

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

更多推荐