系列文章目录

HarmonyOS运用开发01-ArkTS根底知识

HarmonyOS运用开发02-程序结构UIAbility、发动形式与路由跳转


气候转凉了,上海这次应该能够成功入秋了吧。

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI


前言

想要完成一个列表,在前面是运用 ForEach 循环 StudentListItem.est 构建的,不会由滑动作用,数据源添加时,会发现现在这个“列表”显现不全,而且也是无法滑动的。对于数据源数量较多、需求分页加载的列表,咱们运用 List去完成。 接下来学习运用HarmonyOS-ArkTS言语开发办法中的根本组件,这样咱们就能够运用根本组件去完成较为杂乱丰富的UI。

看下完成作用:

List线性列表组件

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

Grid网格列表组件Swiper轮播组件(对应Android中的Banner)

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

一、Column&Row组件的运用

参阅链接:

Column组件的相关API参阅:Column组件

Row组件的相关API参阅:Row组件

Column表明沿笔直方向布局的容器。Row表明沿水平方向布局的容器。

1、主轴和穿插轴概念

在布局容器中,默许存在两根轴,分别是主轴和穿插轴,这两个轴始终是相互笔直的。不同的容器中主轴的方向不相同的。

  • 主轴:在Column容器中的子组件是依照从上到下的笔直方向布局的,其主轴的方向是笔直方向;在Row容器中的组件是依照从左到右的水平方向布局的,其主轴的方向是水平方向。

  • 穿插轴:与主轴笔直相交的轴线,假如主轴是笔直方向,则穿插轴就是水平方向;假如主轴是水平方向,则穿插轴是笔直方向。

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI
HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

2、主轴方向的对齐(justifyContent)

子组件在主轴方向上的对齐运用justifyContent特点来设置,其参数类型是FlexAlign。FlexAlign界说了以下几种类型:

(1)、Start:元素在主轴方向首端对齐,第一个元素与行首对齐,一起后续的元素与前一个对齐。

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

(2)、Center:元素在主轴方向中心对齐,第一个元素与行首的间隔以及最终一个元素与行尾间隔相同。

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

(3)、End:元素在主轴方向尾部对齐,最终一个元素与行尾对齐,其他元素与后一个对齐。

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

(4)、SpaceBetween:元素在主轴方向均匀分配弹性元素,相邻元素之间间隔相同。 第一个元素与行首对齐,最终一个元素与行尾对齐。

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

(5)、SpaceAround:元素在主轴方向均匀分配弹性元素,相邻元素之间间隔相同。 第一个元素到行首的间隔和最终一个元素到行尾的间隔是相邻元素之间间隔的一半。

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

(6)、SpaceEvenly:元素在主轴方向等间隔布局,无论是相邻元素仍是边界元素到容器的间隔都相同。

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

3、穿插轴方向的对齐(alignItems)

子组件在穿插轴方向上的对齐办法运用alignItems特点来设置。

(1)、Column容器的主轴是笔直方向,穿插轴是水平方向,其参数类型为HorizontalAlign(水平对齐),HorizontalAlign界说了以下几种类型:

  • Start:设置子组件在水平方向上依照开始端对齐。

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI
  • Center(默许值):设置子组件在水平方向上居中对齐。

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI
  • End:设置子组件在水平方向上依照末端对齐。

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

(2)、Row容器的主轴是水平方向,穿插轴是笔直方向,其参数类型为VerticalAlign(笔直对齐),VerticalAlign界说了以下几种类型:

  • Top:设置子组件在笔直方向上居顶部对齐。

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI
  • Center(默许值):设置子组件在竖直方向上居中对齐。

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI
  • Bottom:设置子组件在竖直方向上居底部对齐。

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

4、主轴方向的间隔

Column和Row容器的接口都有一个可选参数space,表明子组件在主轴方向上的间隔。

容器组件 接口
Column Column(value?:{space?: string number})
Row Row(value?:{space?: string number})

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

二、列表List组件与Grid网格组件

参阅链接:

List组件的相关API参阅:List组件

Grid组件的相关API参阅:Grid组件

循环烘托(ForEach):循环烘托

在上文Demo中添加 getStudentList2 回来数据数量:

import { DataItemBean } from './DataItemBean';
export class DataModel {
  getStudentList2(): Array<DataItemBean> {
    let studentList: DataItemBean[] = [
      {
        "title": "丁程鑫",
        "image": "https://www.6hu.cc/wp-content/uploads/2023/11/219455-RyanFm.jpeg",
      },
      {
        "title": "贺峻霖",
        "image": "https://www.6hu.cc/wp-content/uploads/2023/11/219455-T8kcSV.jpg",
      },
      {
        "title": "肖战",
        "image": "https://www.6hu.cc/wp-content/uploads/2023/11/219455-fQB86P.jpeg",
      },
      ...
      {
        "title": "丁程鑫2",
        "image": "https://www.6hu.cc/wp-content/uploads/2023/11/219455-RyanFm.jpeg",
      },
      {
        "title": "贺峻霖2",
        "image": "https://www.6hu.cc/wp-content/uploads/2023/11/219455-T8kcSV.jpg",
      },
      {
        "title": "肖战2",
        "image": "https://www.6hu.cc/wp-content/uploads/2023/11/219455-fQB86P.jpeg",
      },
      ...
    ];
    return studentList;
  }
}
export default new DataModel();

再次运转APP,会发现数据充满整个屏幕,而且是不能进行滑动的,数据显现不全。要想能够滑动列表显现悉数数据,这时就需求用到现行列表组件。常见的列表有线性列表(List列表)和网格布局(Grid列表)。

1、List组件

(1)、List是很常用的翻滚类容器组件,一般和子组件ListItem一起运用,List列表中的每一个列表项对应一个ListItem组件;

(2)、运用ForEeach烘托列表

列表往往由多个列表项组成,所以咱们需求在List组件中运用多个ListItem组件来构建列表,这就会导致代码的冗余。运用循环烘托(ForEach)遍历数组的办法构建列表,能够削减重复代码。

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

比照代码:

StudentListPage.ets 代码中:

ForEach(this.studentList2, (item: DataItemBean) => {
     StudentListItem({ studentData: item })
}, (item: string) => JSON.stringify(item))

List组件:

// 列表
List({ space: 16 }) {
	ForEach(this.studentList2, (item: DataItemBean) => {
          ListItem() {
            StudentListItem({ studentData: item })
    }
   }, (item: string) => JSON.stringify(item))
}
.width('100%')
.height('50%')

运转代码,即可滑动显现一切数据。

(3)、设置列表分割线

List组件子组件ListItem之间默许是没有分割线的,部分场景子组件ListItem间需求设置分割线,这时候您能够运用List组件的divider特点。divider特点包括四个参数:

  • strokeWidth: 分割线的线宽。
  • color: 分割线的色彩。
  • startMargin:分割线间隔列表侧边开始端的间隔。
  • endMargin: 分割线间隔列表侧边完毕端的间隔。
HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

代码完成:

List({ space: 16 }) {
   ForEach(this.studentList2, (item: DataItemBean) => {
       ListItem() {
          StudentListItem({ studentData: item })
       }
   }, (item: string) => JSON.stringify(item))
}
.width('100%')
.height('50%')
.divider({ strokeWidth: 3, color: Color.Gray, startMargin: 30, endMargin: 0 })
HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

(4)、设置List摆放方向

List组件里面的列表项默许是按笔直方向摆放的,假如您想让列表沿水平方向摆放,您能够将List组件的listDirection特点设置为Axis.Horizontal

listDirection参数类型是Axis,界说了以下两种类型:

  • Vertical(默许值):子组件ListItem在List容器组件中呈纵向摆放。listDirection(Axis.Vertical)
  • Horizontal:子组件ListItem在List容器组件中呈横向摆放。listDirection(Axis.Horizontal)
HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

List组件的相关API参阅:List组件

2、Grid组件

(1)、Grid组件一般和子组件GridItem一起运用,Grid列表中的每一个条目对应一个GridItem组件。

(2)、运用ForEach烘托网格布局。

运用ForEach烘托网格布局

  • columnsTemplate : 设置当时网格布局列的数量,不设置时默许1列。设置columnsTemplate的值为’1fr 1fr 1fr 1fr’,表明这个网格为4列,将Grid答应的宽分为4等分,每列占1份;
  • rowsTemplate :设置当时网格布局行的数量,不设置时默许1行。rowsTemplate的值为’1fr 1fr 1fr 1fr’,表明这个网格为4行,将Grid答应的高分为4等分,每行占1份。
  • columnsGap :设置列与列的间隔。eg:运用columnsGap设置列间隔为10vp.
  • rowsGap :设置行与行的间隔。eg:运用rowsTemplate设置行间隔也为10vp。

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

(3)、设置Grid摆放方向

Grid组件运用 layoutDirection 设置布局的主轴方向。默许值:GridDirection.Row

留意:

1、网格布局假如运用了固定的行数和列数,则构建出的网格是不行翻滚的。

2、有时候因为内容较多,需求经过翻滚的办法来显现更多的内容,就需求一个能够翻滚的网格布局。只需求设置rowsTemplate和columnsTemplate中的一个即可。

Grid组件的相关API参阅:Grid组件

代码示例

(1)、在 StudentListItem.ets 代码中修正Item布局:

import router from '@ohos.router';
import CommonConstants from '../common/constants/CommonConstants';
import { DataItemBean } from '../viewmodel/DataItemBean';
@Component
export default struct StudentListItem {
  @State isChecked: boolean = false;
  private studentData?: DataItemBean;
  aboutToAppear() {
    console.log("DataItemBean", this.studentData.title)
    console.log("DataItemBean", this.studentData.image)
  }
  @Builder checkIcon(icon: Resource) {
    Image(icon)
      .objectFit(ImageFit.Contain)
      .width($r('app.float.checkbox_width'))
      .height($r('app.float.checkbox_height'))
      .margin($r('app.float.checkbox_margin'))
  }
  build() {
    Column() {
        Row() {
          Text(this.studentData.title)
            .fontColor(this.isChecked ? Color.Red : Color.Black)
            .fontSize(this.isChecked ? $r('app.float.item_checked_font_size') : $r('app.float.item_font_size'))
            .fontWeight(500)
            .opacity(this.isChecked ? 0.5 : 1.0)
            .decoration({ type: this.isChecked ? TextDecorationType.LineThrough : TextDecorationType.None })
          Blank()
          Image($r('app.media.ic_arrow_next'))
            .width('30vp')
            .height('30vp')
            .onClick(() => {
              // console.log('Next Click' + this.name);
              console.log('Next Click' + this.studentData.title);
              console.log('Next Click' + this.studentData.image);
              router.pushUrl({
                // url: 'pages/StudentDetailPage',
                url: CommonConstants.STUDENT_DETAIL_URL,
                params: {
                  // name: this.name,
                  studentData: this.studentData
                }
              }).catch((error) => {
                console.log('Next Click', 'IndexPage push error' + JSON.stringify(error));
              })
            })
        }
        .width('100%')
        .padding({ left: 10, right: 10 })
        if (this.isChecked) {
          this.checkIcon($r('app.media.ic_checked'))
        } else {
          this.checkIcon($r('app.media.ic_unchecked'))
        }
      }
      .borderRadius(10)
      .backgroundColor($r('app.color.start_window_background'))
      .width('100%')
      .height($r('app.float.grid_item_height'))
      .padding({ top: 10 })
      .justifyContent(FlexAlign.SpaceEvenly)
      .onClick(() => {
        this.isChecked = !this.isChecked;
      })
  }
}

(2)、在 StudentListPage.ets 代码中修正List组件为Grid组件:

import DataModel from '../viewmodel/DataModel';
import StudentListItem from '../view/StudentListItem';
import router from '@ohos.router';
import prompt from '@system.prompt';
import { DataItemBean } from '../viewmodel/DataItemBean';
// import DataItemBean from '../viewmodel/DataItemBean';
const TAG = '[StudentListPage]';
@Entry
@Component
export struct StudentListPage {
  // private studentList: Array<string> = [];
  private studentList2: Array<DataItemBean> = [];
  @State backMessage: string = '';
  @State isRowModel: boolean = true;
  // 调用router.back()办法,不会新建页面,回来的是本来的页面,在本来页面中@State声明的变量不会重复声明,
  // 以及也不会触发页面的aboutToAppear()生命周期回调,因此无法直接在变量声明以及页面的aboutToAppear()
  // 生命周期回调中接纳和解析router.back()传递过来的自界说参数。
  onPageShow() {
    this.backMessage = router.getParams()?.['backMessage'];
    console.log(TAG, 'StudentDetailPage回来数据:StudentListPage => ' + this.backMessage)
    if (this.backMessage != undefined && this.backMessage != "") {
      this.showToast(this.backMessage)
    }
  }
  aboutToAppear() {
    // this.studentList = DataModel.getStudentList();
    this.studentList2 = DataModel.getStudentList2();
    // this.backMessage = router.getParams()?.['backMessage'];
  }
  showToast(message: string) {
    prompt.showToast({
      message: message
    })
  }
  build() {
    Navigation() {
      Row() {
        if (this.isRowModel) {
          Grid() {
            ForEach(this.studentList2, (item: DataItemBean) => {
              GridItem() {
                StudentListItem({ studentData: item, isRowModel: false })
              }
            }, (item: string) => JSON.stringify(item))
          }
          // .width('90%')
          .columnsTemplate('1fr 1fr 1fr')
          // .rowsTemplate('1fr 1fr 1fr')
          .columnsGap(10)
          .rowsGap(10)
          // .layoutDirection(GridDirection.Row)
      }
      .width('90%')
      // .margin({ left: 10, right: 10 })
    }
    .title('学生名单')
    .size({ width: '100%', height: '100%' })
    .titleMode(NavigationTitleMode.Mini)
    .hideBackButton(true)
    .menus(this.NavigationMenus())
    .backgroundColor($r('app.color.page_background'))
  }
}

作用图:

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

三、Tabs组件

参阅链接:

Tabs组件的更多特点和参数的运用,能够参阅API:Tabs

@Builder装修器的运用,能够参阅:@Builder

运用Tabs组件来完成类似Android开发中的BottomNavigationBar组件作用、TabIndicator作用;

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

1、特点介绍

(1)、经过 TabContent 的特点设 tabBarTabBar 的显现内容。运用通用特点width和height设置了Tabs组件的宽高,运用barWidth和barHeight设置了TabBar的宽度和高度

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

(2)、经过 barMode 设置TabBar布局形式

Tabs的布局形式有Fixed(默许)和Scrollable两种:

  • BarMode.Fixed:一切TabBar平均分配barWidth宽度(纵向时平均分配barHeight高度),页签不行翻滚;

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

  • BarMode.Scrollable:每一个TabBar均运用实际布局宽度,超越总长度(横向Tabs的barWidth,纵向Tabs的barHeight)后可滑动。

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

(3)、设置TabBar方位和摆放方向

运用Tabs组件接口中的参数barPosition设置页签方位。此外页签显现方位还与vertical特点相关联,vertical特点用于设置页签的摆放方向,当vertical的特点值为false(默许值)时页签横向摆放,为true时页签纵向摆放。

barPosition的值能够设置为BarPosition.Start(默许值)和BarPosition.End

  • BarPosition.Start

    vertical特点办法设置为false(默许值)时,页签坐落容器顶部。 vertical特点办法设置为true时,页签坐落容器左面。

  • BarPosition.End

    vertical特点办法设置为false时,页签坐落容器底部。 vertical特点办法设置为true时,页签坐落容器右侧。

(4)、自界说TabBar款式

TabContent的tabBar特点除了支撑string类型,还支撑运用@Builder装修器润饰的函数。您能够运用@Builder装修器,结构一个生成自界说TabBar款式的函数,完成自界说的底部页签作用。

代码完成:

@State currentIndex: number = CommonConstants.STUDENT_LIST_TAB_INDEX
  // 设置Tabs操控器 Tabs组件的操控器,用于操控Tabs组件进行页签切换。不支撑一个TabsController操控多个Tabs组件
  private tabsController: TabsController = new TabsController();
  // TabContent的tabBar特点除了支撑string类型,还支撑运用@Builder装修器润饰的函数。
  // 能够运用@Builder装修器,结构一个生成自界说TabBar款式的函数,完成上面的底部页签作用
  @Builder TabBuilder(title: string, index: number, selectImage: Resource, normalImage: Resource) {
    Column() {
      Image(this.currentIndex === index ? selectImage : normalImage)
        .width($r('app.float.mainPage_baseTab_size'))
        .height($r('app.float.mainPage_baseTab_size'))
      Text(title)
        .margin({ top: $r('app.float.mainPage_baseTab_top') })
        .fontSize(this.currentIndex === index ? $r('app.float.main_tab_selected_fontSize')
                                              : $r('app.float.main_tab_normal_fontSize'))
        .fontColor(this.currentIndex === index ? $r('app.color.mainPage_selected_color')
                                               : $r('app.color.mainPage_normal_color'))
    }
    .justifyContent(FlexAlign.Center)
    .height($r('app.float.mainPage_barHeight'))
    .width(CommonConstants.FULL_WIDTH)
    .onClick(() => {
      this.currentIndex = index;
      // 操控Tabs切换到指定页签
      this.tabsController.changeIndex(this.currentIndex);
    })
  }

2、代码完成

新建 MainPage.ets,初始化TabsController去设置Tabs组件;

import CommonConstants from '../common/constants/CommonConstants'
import GalleryPage from '../view/GalleryPage';
import { StudentListPage } from './StudentListPage';
@Entry
@Component
struct MainPage {
  @State currentIndex: number = CommonConstants.STUDENT_LIST_TAB_INDEX
  // 设置Tabs操控器 Tabs组件的操控器,用于操控Tabs组件进行页签切换。不支撑一个TabsController操控多个Tabs组件
  private tabsController: TabsController = new TabsController();
  // TabContent的tabBar特点除了支撑string类型,还支撑运用@Builder装修器润饰的函数。
  // 能够运用@Builder装修器,结构一个生成自界说TabBar款式的函数,完成上面的底部页签作用
  @Builder TabBuilder(title: string, index: number, selectImage: Resource, normalImage: Resource) {
    Column() {
      Image(this.currentIndex === index ? selectImage : normalImage)
        .width($r('app.float.mainPage_baseTab_size'))
        .height($r('app.float.mainPage_baseTab_size'))
      Text(title)
        .margin({ top: $r('app.float.mainPage_baseTab_top') })
        .fontSize(this.currentIndex === index ? $r('app.float.main_tab_selected_fontSize')
                                              : $r('app.float.main_tab_normal_fontSize'))
        .fontColor(this.currentIndex === index ? $r('app.color.mainPage_selected_color')
                                               : $r('app.color.mainPage_normal_color'))
    }
    .justifyContent(FlexAlign.Center)
    .height($r('app.float.mainPage_barHeight'))
    .width(CommonConstants.FULL_WIDTH)
    .onClick(() => {
      this.currentIndex = index;
      // 操控Tabs切换到指定页签
      this.tabsController.changeIndex(this.currentIndex);
    })
  }
  build() {
    Tabs({
      barPosition: BarPosition.End,
      controller: this.tabsController,
    }) {
      TabContent() {
        StudentListPage()
      }
      .tabBar(
        this.TabBuilder(
          CommonConstants.STUDENT_LIST_TITLE, CommonConstants.STUDENT_LIST_TAB_INDEX,
          $r('app.media.ic_home_selected'), $r('app.media.ic_home_normal')
        ))
      TabContent() {
        GalleryPage()
      }
      .tabBar(
        this.TabBuilder(
          CommonConstants.PICTURE_TITLE, CommonConstants.PICTURE_TAB_INDEX,
          $r('app.media.ic_checked'), $r('app.media.ic_unchecked')
        ))
    }
    .vertical(true)
    .scrollable(true) // 设置为true时能够经过滑动页面进行页面切换,为false时不行滑动切换页面。默许值:true
    .width(CommonConstants.FULL_WIDTH) // 设置Tabs组件宽度
    .height(CommonConstants.FULL_HEIGHT) // 设置Tabs组件高度
    .backgroundColor(Color.White) // 设置Tabs组件布景色彩
    .barWidth(CommonConstants.FULL_WIDTH) // 设置TabBar宽度
    .barHeight($r('app.float.mainPage_barHeight')) // 设置TabBar高度
    .barMode(BarMode.Scrollable) // Tabs的布局形式有Fixed(默许)和Scrollable两种
    .onChange((index: number) => {
      // Tabs的布局形式有Fixed(默许)和Scrollable两种
      this.currentIndex = index;
    })
  }
}

其间在 GalleryPage().ets 中先放置一个Text组件显现页面;

@Component
export default struct GalleryPage {
  build() {
    Column() {
      Text('Gallery')
        .fontSize('22fp')
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor($r('app.color.page_background'))
  }
}

完成作用:

底部导航栏作用:

设置 Tabs 组件的特点 barPosition: BarPosition.End ,而且设置 .vertical(false),就将页签坐落容器底部,设置TabBar宽度为 100%,高度设置为 56vp

@Entry
@Component
struct MainPage {
  ...
  build() {
    Tabs({
      barPosition: BarPosition.End,
      controller: this.tabsController,
    }) {
      ...
    }
    .vertical(false)
    .scrollable(true) // 设置为true时能够经过滑动页面进行页面切换,为false时不行滑动切换页面。默许值:true
    .width(CommonConstants.FULL_WIDTH) // 设置Tabs组件宽度
    .height(CommonConstants.FULL_HEIGHT) // 设置Tabs组件高度
    .backgroundColor(Color.White) // 设置Tabs组件布景色彩
    .barWidth(CommonConstants.FULL_WIDTH) // 设置TabBar宽度
    .barHeight($r('app.float.mainPage_barHeight')) // 设置TabBar高度
    .barMode(BarMode.Fixed) // Tabs的布局形式有Fixed(默许)和Scrollable两种
    .onChange((index: number) => {
      // Tabs的布局形式有Fixed(默许)和Scrollable两种
      this.currentIndex = index;
    })
  }
}
HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

侧边导航栏作用:

设置 Tabs 组件的特点 barPosition: BarPosition.End ,而且设置 .vertical(true),就将页签坐落容器底部,设置TabBar宽度为 56vp,高度设置为100%

@Entry
@Component
struct MainPage {
  ...
  build() {
    Tabs({
      barPosition: BarPosition.End,
      controller: this.tabsController,
    }) {
      ...
    }
    .vertical(true)
    .scrollable(true) // 设置为true时能够经过滑动页面进行页面切换,为false时不行滑动切换页面。默许值:true
    .width(CommonConstants.FULL_WIDTH) // 设置Tabs组件宽度
    .height(CommonConstants.FULL_HEIGHT) // 设置Tabs组件高度
    .backgroundColor(Color.White) // 设置Tabs组件布景色彩
    .barWidth($r('app.float.mainPage_barHeight')) // 设置TabBar宽度 
    .barHeight(CommonConstants.FULL_WIDTH) // 设置TabBar高度
    .barMode(BarMode.Fixed) // Tabs的布局形式有Fixed(默许)和Scrollable两种
    .onChange((index: number) => {
      // Tabs的布局形式有Fixed(默许)和Scrollable两种
      this.currentIndex = index;
    })
  }
}
HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

四、Swiper组件

参阅链接:

Swiper轮播组件的相关API参阅:Swiper组件

滑块视图容器,提供子组件滑动轮播显现的才能


1、特点介绍

(1)、操控器 SwiperController

给组件绑定一个操控器,用来操控组件翻页

  • showNext : 翻至下一页。翻页带动效切换进程,时长经过duration指定。
  • showPrevious : 翻至上一页。翻页带动效切换进程,时长经过duration指定。
  • finishAnimation : 中止播映动画。

(2)、autoPlay

子组件是否自动播映。默许值:false。

loop为false时,自动轮播到最终一页时中止轮播。手势切换后不是最终一页时继续播映。

(3)、interval

运用自动播映时播映的时间间隔,单位为毫秒。默许值:3000

(4)、indicator

是否启用导航点指示器。默许值:true

(5)、loop

是否敞开循环。默许值:true 设置为true时表明敞开循环,在LazyForEach懒循环加载形式下,加载的组件数量主张大于5个。

(6)、duration

子组件切换的动画时长,单位为毫秒。默许值:400

(7)、vertical

是否为纵向滑动。默许值:false

(8)、itemSpace

设置子组件与子组件之间空隙。默许值:0 说明:不支撑设置百分比。

(9)、disableSwipe

禁用组件滑动切换功用。默许值:false

(10)、indicatorStyle {}

设置导航点款式:

  • left: 设置导航点间隔Swiper组件左面的间隔。

  • top: 设置导航点间隔Swiper组件顶部的间隔。

  • right: 设置导航点间隔Swiper组件右边的间隔。

  • bottom: 设置导航点间隔Swiper组件底部的间隔。

  • size: 设置导航点的直径。不支撑设置百分比。默许值:6vp。

  • mask: 设置是否显现导航点蒙层款式。

  • color: 设置导航点的色彩。

  • selectedColor: 设置选中的导航点的色彩。

2、事情 onChange

onChange(event: (index: number) => void)
  • 当时显现的子组件索引变化时触发该事情,回来值为当时显现的子组件的索引值。

  • 说明:Swiper组件结合LazyForEach运用时,不能在onChange事情里触发子页面UI的改写。

  • 回来值:index:number , 代表当时显现元素的索引。

3、代码示例

完成作用:在List组件、Grid组件上面添加Swiper轮播组件,全体滑动:

HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI
HarmonyOS运用开发03-根底组件-让咱们来码出杂乱酷炫的UI

StudentListPage.ets 中修正:

运用@Builder构建Swiper自界说组件:

@Builder SwiperBuilder(studentList: Array<DataItemBean>) {
    Swiper(this.swiperController) {
      ForEach(studentList, (item: DataItemBean) => {
        Image(item.image)
          .borderRadius($r('app.float.home_swiper_borderRadius'))
          .width('100%')
          .height('240vp')
          .objectFit(ImageFit.Fill)
          .onClick(() => {
            router.pushUrl({
              // url: 'pages/StudentDetailPage',
              url: CommonConstants.STUDENT_DETAIL_URL,
              params: {
                studentData: item
              }
            }).catch((error) => {
              console.log('Next Click', 'IndexPage push error' + JSON.stringify(error));
            })
          })
      }, (item: DataItemBean) => JSON.stringify(item))
    }
    .autoPlay(true)
    .indicatorStyle({ mask: true, bottom: '10vp' })
    .margin({ top: $r('app.float.home_swiper_margin'), bottom: $r('app.float.home_swiper_margin') })
  }

(1)、List列表中修正:

嵌入 Scroll 列表中:

import DataModel from '../viewmodel/DataModel';
import StudentListItem from '../view/StudentListItem';
import router from '@ohos.router';
import prompt from '@system.prompt';
import { DataItemBean } from '../viewmodel/DataItemBean';
import CommonConstants from '../common/constants/CommonConstants';
const TAG = '[StudentListPage]';
@Entry
@Component
export struct StudentListPage {
  private studentList2: Array<DataItemBean> = [];
 ...
  // Swiper操控器
  private swiperController: SwiperController = new SwiperController();
  ...
  @Builder NavigationMenus() {
    Row() {
      Toggle({ type: ToggleType.Switch, isOn: true })
        .selectedColor(Color.Red)
        .switchPointColor(Color.White)
        .onChange((isOn: boolean) => {
          // this.isListModel = !this.isListModel
          this.isListModel = isOn
        })
    }
  }
  @Builder SwiperBuilder(studentList: Array<DataItemBean>) {
    Swiper(this.swiperController) {
      ForEach(studentList, (item: DataItemBean) => {
        Image(item.image)
          .borderRadius($r('app.float.home_swiper_borderRadius'))
          .width('100%')
          .height('240vp')
          .objectFit(ImageFit.Fill)
          .onClick(() => {
            router.pushUrl({
              // url: 'pages/StudentDetailPage',
              url: CommonConstants.STUDENT_DETAIL_URL,
              params: {
                studentData: item
              }
            }).catch((error) => {
              console.log('Next Click', 'IndexPage push error' + JSON.stringify(error));
            })
          })
      }, (item: DataItemBean) => JSON.stringify(item))
    }
    .autoPlay(true)
    .indicatorStyle({ mask: true, bottom: '10vp' })
    .margin({ top: $r('app.float.home_swiper_margin'), bottom: $r('app.float.home_swiper_margin') })
  }
  build() {
    Navigation() {
      Row() {
        Scroll() {
            Column() {
              // Swiper组件
              this.SwiperBuilder(this.studentList2)
              // 列表
              // List组件子组件ListItem之间默许是没有分割线的,部分场景子组件ListItem间需求设置分割线,
              // 这时候能够运用List组件的divider特点。divider特点包括四个参数:
              // 1、strokeWidth: 分割线的线宽。
              // 2、color: 分割线的色彩。
              // 3、startMargin:分割线间隔列表侧边开始端的间隔。
              // 4、endMargin: 分割线间隔列表侧边完毕端的间隔
              List({ space: 16 }) {
                ForEach(this.studentList2, (item: DataItemBean) => {
                  ListItem() {
                    StudentListItem({ studentData: item, isRowModel: true })
                  }
                }, (item: string) => JSON.stringify(item))
              }
              // .width('90%')
              .divider({ strokeWidth: 1, color: Color.Gray, startMargin: 30, endMargin: 0 })
              // .listDirection(Axis.Horizontal)
              Text('---没有更多了---').fontSize('22vp').margin('30vp')
            }
          }
          .scrollBar(BarState.Off)
          .edgeEffect(EdgeEffect.Spring)
      }
      .width('90%')
    }
    .title('学生名单')
    .size({ width: '100%', height: '100%' })
    .titleMode(NavigationTitleMode.Mini)
    .hideBackButton(true)
    .menus(this.NavigationMenus())
    .backgroundColor($r('app.color.page_background'))
  }
}

(1)、Grid列表中修正:

嵌入 Scroll 列表中:

import DataModel from '../viewmodel/DataModel';
import StudentListItem from '../view/StudentListItem';
import router from '@ohos.router';
import prompt from '@system.prompt';
import { DataItemBean } from '../viewmodel/DataItemBean';
import CommonConstants from '../common/constants/CommonConstants';
const TAG = '[StudentListPage]';
@Entry
@Component
export struct StudentListPage {
  private studentList2: Array<DataItemBean> = [];
  ...
  // Swiper操控器
  private swiperController: SwiperController = new SwiperController();
  ...
  build() {
    Navigation() {
      Row() {
        Scroll() {
            Column() {
              // Swiper组件
              this.SwiperBuilder(this.studentList2)
              Grid() {
                ForEach(this.studentList2, (item: DataItemBean) => {
                  GridItem() {
                    StudentListItem({ studentData: item, isRowModel: false })
                  }
                }, (item: string) => JSON.stringify(item))
              }
              .columnsTemplate('1fr 1fr 1fr')
              .rowsTemplate('1fr 1fr 1fr 1fr 1fr')
              .columnsGap('10vp')
              .rowsGap('10vp')
              .height('640vp')
              // .layoutDirection(GridDirection.Row)
              Text('---没有更多了---').fontSize('22vp').margin('30vp')
            }
          }
          .scrollBar(BarState.Off)
          .edgeEffect(EdgeEffect.Spring)
      }
      .width('90%')
      // .margin({ left: 10, right: 10 })
    }
    .title('学生名单')
    .size({ width: '100%', height: '100%' })
    .titleMode(NavigationTitleMode.Mini)
    .hideBackButton(true)
    .menus(this.NavigationMenus())
    .backgroundColor($r('app.color.page_background'))
  }
}

StudentListPage.ets 完整代码:

import DataModel from '../viewmodel/DataModel';
import StudentListItem from '../view/StudentListItem';
import router from '@ohos.router';
import prompt from '@system.prompt';
import { DataItemBean } from '../viewmodel/DataItemBean';
import CommonConstants from '../common/constants/CommonConstants';
// import DataItemBean from '../viewmodel/DataItemBean';
const TAG = '[StudentListPage]';
@Entry
@Component
export struct StudentListPage {
  // private studentList: Array<string> = [];
  private studentList2: Array<DataItemBean> = [];
  // 回来上一层数据
  @State backMessage: string = '';
  // 是否是List组件形式
  @State isListModel: boolean = true;
  // Swiper操控器
  private swiperController: SwiperController = new SwiperController();
  // 调用router.back()办法,不会新建页面,回来的是本来的页面,在本来页面中@State声明的变量不会重复声明,
  // 以及也不会触发页面的aboutToAppear()生命周期回调,因此无法直接在变量声明以及页面的aboutToAppear()
  // 生命周期回调中接纳和解析router.back()传递过来的自界说参数。
  onPageShow() {
    this.backMessage = router.getParams()?.['backMessage'];
    console.log(TAG, 'StudentDetailPage回来数据:StudentListPage => ' + this.backMessage)
    if (this.backMessage != undefined && this.backMessage != "") {
      this.showToast(this.backMessage)
    }
  }
  aboutToAppear() {
    // this.studentList = DataModel.getStudentList();
    this.studentList2 = DataModel.getStudentList2();
    // this.backMessage = router.getParams()?.['backMessage'];
  }
  showToast(message: string) {
    prompt.showToast({
      message: message
    })
  }
  @Builder NavigationMenus() {
    Row() {
      Toggle({ type: ToggleType.Switch, isOn: true })
        .selectedColor(Color.Red)
        .switchPointColor(Color.White)
        .onChange((isOn: boolean) => {
          // this.isListModel = !this.isListModel
          this.isListModel = isOn
        })
    }
  }
  @Builder SwiperBuilder(studentList: Array<DataItemBean>) {
    Swiper(this.swiperController) {
      ForEach(studentList, (item: DataItemBean) => {
        Image(item.image)
          .borderRadius($r('app.float.home_swiper_borderRadius'))
          .width('100%')
          .height('240vp')
          .objectFit(ImageFit.Fill)
          .onClick(() => {
            router.pushUrl({
              // url: 'pages/StudentDetailPage',
              url: CommonConstants.STUDENT_DETAIL_URL,
              params: {
                studentData: item
              }
            }).catch((error) => {
              console.log('Next Click', 'IndexPage push error' + JSON.stringify(error));
            })
          })
      }, (item: DataItemBean) => JSON.stringify(item))
    }
    .autoPlay(true)
    .indicatorStyle({ mask: true, bottom: '10vp' })
    .margin({ top: $r('app.float.home_swiper_margin'), bottom: $r('app.float.home_swiper_margin') })
  }
  build() {
    Navigation() {
      Row() {
        if (this.isListModel) {
          Scroll() {
            Column() {
              // Swiper组件
              this.SwiperBuilder(this.studentList2)
              // 列表
              // List组件子组件ListItem之间默许是没有分割线的,部分场景子组件ListItem间需求设置分割线,
              // 这时候能够运用List组件的divider特点。divider特点包括四个参数:
              // 1、strokeWidth: 分割线的线宽。
              // 2、color: 分割线的色彩。
              // 3、startMargin:分割线间隔列表侧边开始端的间隔。
              // 4、endMargin: 分割线间隔列表侧边完毕端的间隔
              List({ space: 16 }) {
                ForEach(this.studentList2, (item: DataItemBean) => {
                  ListItem() {
                    StudentListItem({ studentData: item, isRowModel: true })
                  }
                }, (item: string) => JSON.stringify(item))
              }
              // .width('90%')
              .divider({ strokeWidth: 1, color: Color.Gray, startMargin: 30, endMargin: 0 })
              // .listDirection(Axis.Horizontal)
              Text('---没有更多了---').fontSize('22vp').margin('30vp')
            }
          }
          .scrollBar(BarState.Off)
          .edgeEffect(EdgeEffect.Spring)
        } else {
          Scroll() {
            Column() {
              // Swiper组件
              this.SwiperBuilder(this.studentList2)
              Grid() {
                ForEach(this.studentList2, (item: DataItemBean) => {
                  GridItem() {
                    StudentListItem({ studentData: item, isRowModel: false })
                  }
                }, (item: string) => JSON.stringify(item))
              }
              .columnsTemplate('1fr 1fr 1fr')
              .rowsTemplate('1fr 1fr 1fr 1fr 1fr')
              .columnsGap('10vp')
              .rowsGap('10vp')
              .height('640vp')
              // .layoutDirection(GridDirection.Row)
              Text('---没有更多了---').fontSize('22vp').margin('30vp')
            }
          }
          .scrollBar(BarState.Off)
          .edgeEffect(EdgeEffect.Spring)
        }
      }
      .width('90%')
      // .margin({ left: 10, right: 10 })
    }
    .title('学生名单')
    .size({ width: '100%', height: '100%' })
    .titleMode(NavigationTitleMode.Mini)
    .hideBackButton(true)
    .menus(this.NavigationMenus())
    .backgroundColor($r('app.color.page_background'))
  }
}




总结

本节梳理了HarmonyOS中运用ArkTs言语开发完成根本组件、容器组件、List组件、Grid组件、轮播组件Swiper以及页签切换组件Tabs等根本组件的运用,完成了 一个能够左右滑动切换视图的 带有顶部轮播Banner的 Star List ,而且经过 Toggle({ type: ToggleType.Switch, isOn: true }) 组件切换 List 视图与 Grid 列表视图功用。把握这些根本组件,咱们就能够依照UI规划师给咱们的UI规划稿码出炫酷杂乱的UI界面了,继续尽力。⛽