HarmonyOS开发学习(3)–页面开发

组件是界面搭建与显示的最小单位,组件根据功能可以分为以下五大类:基础组件、容器组件、媒体组件、绘制组件、画布组件。

1.Text

Text组件用于在界面上展示一段文本信息,可以包含子组件Span。

名称参数类型描述
fontColorResourceColor设置文本颜色。
fontSizeLength | Resource设置文本尺寸,Length为number类型时,使用fp单位。
fontStyleFontStyle设置文本的字体样式。默认值:FontStyle.Normal。
fontWeightnumber | FontWeight | string设置文本的字体粗细,number类型取值[100, 900],取值间隔为100,默认为400,取值越大,字体越粗。string类型仅支持number类型取值的字符串形式,例如“400”,以及“bold”、“bolder”、“lighter”、“regular”、“medium”,分别对应FontWeight中相应的枚举值。默认值:FontWeight.Normal。
fontFamilystring | Resource设置文本的字体列表。使用多个字体,使用“,”进行分割,优先级按顺序生效。例如:“Arial,sans-serif”。
@Entry
@Component
struct TextDemo {
  build() {
    Row() {
      Column() {
        Text('HarmonyOS')
        Text('HarmonyOS')
          .fontColor(Color.Blue)
          .fontSize(20)
          .fontStyle(FontStyle.Italic)
          .fontWeight(FontWeight.Bold)
          .fontFamily('Arial')
      }
      .width('100%')
    }
    .backgroundColor(0xF1F3F5)
    .height('100%')
  }
}

设置文本对齐方式

使用textAlign属性可以设置文本的对齐方式:

  .textAlign(TextAlign.Start)
  • Start(默认值):水平对齐首部。
  • Center:水平居中对齐。
  • End:水平对齐尾部。

设置文本超长显示

当文本内容较多超出了Text组件范围的时候,您可以使用textOverflow设置文本截取方式,需配合maxLines使用,单独设置不生效,maxLines用于设置文本显示最大行数。下面的示例代码将textOverflow设置为Ellipsis ,它将显示不下的文本用 “…” 表示:

Text('This is the text content of Text Component This is the text content of Text Component')
  .fontSize(16)
  .maxLines(1)
  .textOverflow({overflow:TextOverflow.Ellipsis})
  .backgroundColor(0xE6F2FD) 

设置文本装饰线

使用decoration设置文本装饰线样式及其颜色,大家在浏览网页的时候经常可以看到装饰线,例如带有下划线超链接文本。decoration包含type和color两个参数,其中type用于设置装饰线样式,参数类型为TextDecorationType,color为可选参数。

Text('HarmonyOS')
  .fontSize(20)
  .decoration({ type: TextDecorationType.Underline, color: Color.Black })
  .backgroundColor(0xE6F2FD)

存在下面几个类型:

None:不使用文本装饰线。

Overline:文字上划线修饰。

LineThrough:穿过文本的修饰线。

Underline:文字下划线修饰。

2.Image

Image组件用于渲染展示图片如:

Image($r("app.media.icon"))
  .width(100)
  .height(100)

为了使图片在页面中有更好的显示效果,有时候需要对图片进行缩放处理。您可以使用objectFit属性设置图片的缩放类型,objectFit的参数类型为ImageFit。

Image($r("app.media.image2"))
  .objectFit(ImageFit.Cover)
  .backgroundColor(0xCCCCCC)
  .width(100)
  .height(100) 

ImageFit存在以下类型:

Contain:保持宽高比进行缩小或者放大,使得图片完全显示在显示边界内。

Cover(默认值):保持宽高比进行缩小或者放大,使得图片两边都大于或等于显示边界。

Auto:自适应显示。

Fill:不保持宽高比进行放大缩小,使得图片充满显示边界。

ScaleDown:保持宽高比显示,图片缩小或者保持不变。

None:保持原有尺寸显示。

加载网络图片

Image组件支持加载网络图片,将图片地址换成网络地址进行加载:

Image('https://www.example.com/xxx.png')

为了成功加载网络图片,您需要再module.json5文件中申明网络访问权限:

{
    "module" : {
        "requestPermissions":[
           {
             "name": "ohos.permission.INTERNET"
           }
        ]
    }
}

3.TextInput

TextInput组件用于输入单行文本,响应输入事件。TextInput的使用也非常广泛,例如应用登录账号密码、发送消息等。和Text组件一样,TextInput组件也支持文本样式设置,下面的示例代码实现了一个简单的输入框:

TextInput()
  .fontColor(Color.Blue)
  .fontSize(20)
  .fontStyle(FontStyle.Italic)
  .fontWeight(FontWeight.Bold)
  .fontFamily('Arial') 

设置输入提示文字

使用placeholder属性就可以实现提示文字。还可以使用placeholderColor和placeholderFont分别设置提示文本的颜色和样式:

TextInput({ placeholder: '请输入帐号' })
  .placeholderColor(0x999999)
  .placeholderFont({ size: 20, weight: FontWeight.Medium, family: 'cursive', style: FontStyle.Italic })

设置输入类型

使用type属性来设置输入框类型,如密码输入框,可以将type属性设置为InputType.Password来实现:

TextInput({ placeholder: '请输入密码' })
  .type(InputType.Password)

除此之外还有以下类型:

Normal:基本输入模式。支持输入数字、字母、下划线、空格、特殊字符。

Password:密码输入模式。

Email:e-mail地址输入模式。

Number:纯数字输入模式。

设置光标位置

可以使用TextInputController动态设置光位置,下面的示例代码使用TextInputController的caretPosition方法,将光标移动到了第二个字符后。

@Entry
@Component
struct TextInputDemo {
  controller: TextInputController = new TextInputController()
 
  build() {
    Column() {
      TextInput({ controller: this.controller })
      Button('设置光标位置')
        .onClick(() => {
          this.controller.caretPosition(2)
        })
    }
    .height('100%')
    .backgroundColor(0xE6F2FD)
  }
}

获取输入文本

我们可以给TextInput设置onChange事件,输入文本发生变化时触发回调,下面示例代码中的value为实时获取用户输入的文本信息

@Entry
@Component
struct TextInputDemo {
  @State text: string = ''
 
  build() {
    Column() {
      TextInput({ placeholder: '请输入账号' })
        .caretColor(Color.Blue)
        .onChange((value: string) => {
          this.text = value
        })
      Text(this.text)
    }
    .alignItems(HorizontalAlign.Center)
    .padding(12)
    .backgroundColor(0xE6F2FD)
  }
}

4.Button

Button组件主要用来响应点击操作,可以包含子组件。

设置按钮样式

我们可以使用type来指定按钮样式,可以使用ButtonType.Capsule来指定:

Capsule:胶囊型按钮(圆角默认为高度的一半)。

Circle:圆形按钮。

Normal:普通按钮(默认不带圆角)。

设置按钮点击事件

可以给Button绑定onClick事件,当用户点击Button的时候,就会回调onClick内方法。

包含子组件

Button组件可以包含子组件,让您可以开发出更丰富多样的Button,下面的示例代码中Button组件包含了一个Image组件:

Button({ type: ButtonType.Circle, stateEffect: true }) {
  Image($r('app.media.icon_delete'))
    .width(30)
    .height(30)
}
.width(55)
.height(55)
.backgroundColor(0x317aff)

5.LoadingProgress

LoadingProgress组件用于显示加载进展,比如应用的登录界面,当我们点击登录的时候,显示的“正在登录”的进度条状态。LoadingProgress的使用非常简单,只需要设置颜色和宽高就可以了。

LoadingProgress()
  .color(Color.Blue)
  .height(60)
  .width(60)

7.使用资源引用类型

Resource是资源引用类型,用于设置组件属性的值。推荐大家优先使用Resource类型,将资源文件(字符串、图片、音频等)统一存放于resources目录下,便于开发者统一维护。同时系统可以根据当前配置加载合适的资源,例如,开发者可以根据屏幕尺寸呈现不同的布局效果,或根据语言设置提供不同的字符串。

如以下代码所有值都是直接写在代码中:

Button('登录', { type: ButtonType.Capsule, stateEffect: true })
  .width(300)
  .height(40)
  .fontSize(16)
  .fontWeight(FontWeight.Medium)
  .backgroundColor('#007DFF')

我们可以将这些硬编码写到entry/src/main/resources下的资源文件中。

然后在Button组件通过“$r(‘app.type.name’)”的形式引用应用资源。app代表应用内resources目录中定义的资源;type代表资源类型(或资源的存放位置),可以取“color”、“float”、“string”、“plural”、“media”;name代表资源命名,由开发者定义资源时确定。

Button($r('app.string.login_text'), { type: ButtonType.Capsule })
  .width($r('app.float.button_width'))
  .height($r('app.float.button_height'))
  .fontSize($r('app.float.login_fontSize'))
  .backgroundColor($r('app.color.button_color'))

Column和Row组件

之前我们聊过Harmony的两个布局容器Column和Row,分别表示垂直布局和水平布局。

其存在两个布局属性:

属性名称描述
justifyContent设置子组件在主轴方向上的对齐格式。
alignItems设置子组件在交叉轴方向上的对齐格式。

justifyContent

其参数类型是FlexAlign。FlexAlign定义了以下几种类型:

Start:元素在主轴方向首端对齐,第一个元素与行首对齐,同时后续的元素与前一个对齐。

Center:元素在主轴方向中心对齐,第一个元素与行首的距离以及最后一个元素与行尾距离相同。

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

SpaceBetween:元素在主轴方向均匀分配弹性元素,相邻元素之间距离相同。 第一个元素与行首对齐,最后一个元素与行尾对齐。

SpaceAround:元素在主轴方向均匀分配弹性元素,相邻元素之间距离相同。 第一个元素到行首的距离和最后一个元素到行尾的距离是相邻元素之间距离的一半。

SpaceEvenly:元素在主轴方向等间距布局,无论是相邻元素还是边界元素到容器的间距都一样。

alignItems

Column容器的主轴是垂直方向,交叉轴是水平方向,其参数类型为HorizontalAlign(水平对齐),HorizontalAlign定义了以下几种类型:

Start:设置子组件在水平方向上按照起始端对齐。

Center(默认值):设置子组件在水平方向上居中对齐。

End:设置子组件在水平方向上按照末端对齐。

Row容器的主轴是水平方向,交叉轴是垂直方向,其参数类型为VerticalAlign(垂直对齐),VerticalAlign定义了以下几种类型:

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

Center(默认值):设置子组件在竖直方向上居中对齐。

Bottom:设置子组件在竖直方向上居底部对齐。

接口

Column和Row都存在以下接口:

容器组件接口
ColumnColumn(value?:{space?: string | number})
RowRow(value?:{space?: string | number})

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

List和Grid组件

List和Grid也是一种容器组件,效果如下:

List组件

List是很常用的滚动类容器组件,一般和子组件ListItem一起使用,List列表中的每一个列表项对应一个ListItem组件。

使用ForEach渲染列表

列表往往由多个列表项组成,所以我们需要在List组件中使用多个ListItem组件来构建列表,这就会导致代码的冗余。使用循环渲染(ForEach)遍历数组的方式构建列表,可以减少重复代码,示例代码如下:

@Entry
@Component
struct ListDemo {
  private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

  build() {
    Column() {
      List({ space: 10 }) {
        ForEach(this.arr, (item: number) => {
          ListItem() {
            Text(`${item}`)
              .width('100%')
              .height(100)
              .fontSize(20)
              .fontColor(Color.White)
              .textAlign(TextAlign.Center)
              .borderRadius(10)
              .backgroundColor(0x007DFF)
          }
        }, item => item)
      }
    }
    .padding(12)
    .height('100%')
    .backgroundColor(0xF1F3F5)
  }
}

设置列表分割线

List组件子组件ListItem之间默认是没有分割线的,部分场景子组件ListItem间需要设置分割线,这时候您可以使用List组件的divider属性。divider属性包含四个参数:

  • strokeWidth: 分割线的线宽。
  • color: 分割线的颜色。
  • startMargin:分割线距离列表侧边起始端的距离。
  • endMargin: 分割线距离列表侧边结束端的距离。

List列表滚动事件监听

List组件提供了一系列事件方法用来监听列表的滚动,您可以根据需要,监听这些事件来做一些操作:

  • onScroll:列表滑动时触发,返回值scrollOffset为滑动偏移量,scrollState为当前滑动状态。
  • onScrollIndex:列表滑动时触发,返回值分别为滑动起始位置索引值与滑动结束位置索引值。
  • onReachStart:列表到达起始位置时触发。
  • onReachEnd:列表到底末尾位置时触发。
  • onScrollStop:列表滑动停止时触发。
List({ space: 10 }) {
  ForEach(this.arr, (item) => {
    ListItem() {
      Text(`${item}`)
        ...
    }
  }, item => item)
}
.onScrollIndex((firstIndex: number, lastIndex: number) => {
  console.info('first' + firstIndex)
  console.info('last' + lastIndex)
})
.onScroll((scrollOffset: number, scrollState: ScrollState) => {
  console.info('scrollOffset' + scrollOffset)
  console.info('scrollState' + scrollState)
})
.onReachStart(() => {
  console.info('onReachStart')
})
.onReachEnd(() => {
  console.info('onReachEnd')
})
.onScrollStop(() => {
  console.info('onScrollStop')
})

设置List排列方向

List组件里面的列表项默认是按垂直方向排列的,如果您想让列表沿水平方向排列,您可以将List组件的listDirection属性设置为Axis.Horizontal。

listDirection参数类型是Axis,定义了以下两种类型:

Vertical(默认值):子组件ListItem在List容器组件中呈纵向排列。

Horizontal:子组件ListItem在List容器组件中呈横向排列。

Grid组件

Grid组件为网格容器,是一种网格列表,由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。Grid组件一般和子组件GridItem一起使用,Grid列表中的每一个条目对应一个GridItem组件。

使用ForEach渲染网格布局

@Entry
@Component
struct GridExample {
  // 定义一个长度为16的数组
  private arr: string[] = new Array(16).fill('').map((_, index) => `item ${index}`);

  build() {
    Column() {
      Grid() {
        ForEach(this.arr, (item: string) => {
          GridItem() {
            Text(item)
              .fontSize(16)
              .fontColor(Color.White)
              .backgroundColor(0x007DFF)
              .width('100%')
              .height('100%')
              .textAlign(TextAlign.Center)
          }
        }, item => item)
      }
      .columnsTemplate('1fr 1fr 1fr 1fr')
      .rowsTemplate('1fr 1fr 1fr 1fr')
      .columnsGap(10)
      .rowsGap(10)
      .height(300)
    }
    .width('100%')
    .padding(12)
    .backgroundColor(0xF1F3F5)
  }
}

示例代码中创建了16个GridItem列表项。同时设置columnsTemplate的值为’1fr 1fr 1fr 1fr’,表示这个网格为4列,将Grid允许的宽分为4等分,每列占1份;rowsTemplate的值为’1fr 1fr 1fr 1fr’,表示这个网格为4行,将Grid允许的高分为4等分,每行占1份。这样就构成了一个4行4列的网格列表,然后使用columnsGap设置列间距为10vp,使用rowsGap设置行间距也为10vp。示例代码效果图如下:

上面构建的网格布局使用了固定的行数和列数,所以构建出的网格是不可滚动的。然而有时候因为内容较多,我们通过滚动的方式来显示更多的内容,就需要一个可以滚动的网格布局。我们只需要设置rowsTemplate和columnsTemplate中的一个即可。

将示例代码中GridItem的高度设置为固定值,例如100;仅设置columnsTemplate属性,不设置rowsTemplate属性,就可以实现Grid列表的滚动:

Grid() {
  ForEach(this.arr, (item: string) => {
    GridItem() {
      Text(item)
        .height(100)
        ...
    }
  }, item => item)
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.columnsGap(10)
.rowsGap(10)
.height(300)

此外,Grid像List一样也可以使用onScrollIndex来监听列表的滚动。

Tabs组件

在我们常用的应用中,经常会有视图内容切换的场景,来展示更加丰富的内容。比如下面这个页面,点击底部的页签的选项,可以实现“首页”和“我的”

两个内容视图的切换。

ArkUI开发框架提供了一种页签容器组件Tabs,开发者通过Tabs组件可以很容易的实现内容视图的切换。页签容器Tabs的形式多种多样,不同的页面设计页签不一样,可以把页签设置在底部、顶部或者侧边。

@Entry
@Component
struct TabsExample {
  private controller: TabsController = new TabsController()

  build() {
    Column() {
      Tabs({ barPosition: BarPosition.Start, controller: this.controller }) {
        TabContent() {
          Column().width('100%').height('100%').backgroundColor(Color.Green)
        }
        .tabBar('green')

        TabContent() {
          Column().width('100%').height('100%').backgroundColor(Color.Blue)
        }
        .tabBar('blue')

        TabContent() {
          Column().width('100%').height('100%').backgroundColor(Color.Yellow)
        }
        .tabBar('yellow')

        TabContent() {
          Column().width('100%').height('100%').backgroundColor(Color.Pink)
        }
        .tabBar('pink')
      }
      .barWidth('100%') // 设置TabBar宽度
      .barHeight(60) // 设置TabBar高度
      .width('100%') // 设置Tabs组件宽度
      .height('100%') // 设置Tabs组件高度
      .backgroundColor(0xF5F5F5) // 设置Tabs组件背景颜色
    }
    .width('100%')
    .height('100%')
  }
}

Tabs组件中包含4个子组件TabContent,通过TabContent的tabBar属性设置TabBar的显示内容。使用通用属性width和height设置了Tabs组件的宽高,使用barWidth和barHeight设置了TabBar的宽度和高度。

  • TabContent组件不支持设置通用宽度属性,其宽度默认撑满Tabs父组件。
  • TabContent组件不支持设置通用高度属性,其高度由Tabs父组件高度与TabBar组件高度决定。

设置TabBar布局模式

因为Tabs的布局模式默认是Fixed的,所以Tabs的页签是不可滑动的。当页签比较多的时候,可能会导致页签显示不全,将布局模式设置为Scrollable的话,可以实现页签的滚动。

Tabs的布局模式有Fixed(默认)和Scrollable两种:

  • BarMode.Fixed:所有TabBar平均分配barWidth宽度(纵向时平均分配barHeight高度),页签不可滚动,效果图如下:
  • BarMode.Scrollable:每一个TabBar均使用实际布局宽度,超过总长度(横向Tabs的barWidth,纵向Tabs的barHeight)后可滑动。

当页签比较多的时候,可以滑动页签,下面的示例代码将barMode设置为BarMode.Scrollable,实现了可滚动的页签:

@Entry
@Component
struct TabsExample {
  private controller: TabsController = new TabsController()

  build() {
    Column() {
      Tabs({ barPosition: BarPosition.Start, controller: this.controller }) {
        TabContent() {
          Column()
            .width('100%')
            .height('100%')
            .backgroundColor(Color.Green)
        }
        .tabBar('green')

        TabContent() {
          Column()
            .width('100%')
            .height('100%')
            .backgroundColor(Color.Blue)
        }
        .tabBar('blue')

        ...

      }
      .barMode(BarMode.Scrollable)
      .barWidth('100%')
      .barHeight(60)
      .width('100%')
      .height('100%')
    }
  }
}

设置TabBar位置和排列方向

Tabs组件页签默认显示在顶部,某些场景下您可能希望Tabs页签出现在底部或者侧边,您可以使用Tabs组件接口中的参数barPosition设置页签位置。此外页签显示位置还与vertical属性相关联,vertical属性用于设置页签的排列方向,当vertical的属性值为false(默认值)时页签横向排列,为true时页签纵向排列。

barPosition的值可以设置为BarPosition.Start(默认值)和BarPosition.End:

  • BarPosition.Startvertical属性方法设置为false(默认值)时,页签位于容器顶部。
Tabs({ barPosition: BarPosition.Start }) {
  ...
}
.vertical(false) 
.barWidth('100%') 
.barHeight(60)  

vertical属性方法设置为true时,页签位于容器左侧。

Tabs({ barPosition: BarPosition.Start }) {  
...
}
.vertical(true) 
.barWidth(100) 
.barHeight(200)  

BarPosition.End

vertical属性方法设置为false时,页签位于容器底部。

Tabs({ barPosition: BarPosition.End }) {
  ...
}
.vertical(false) 
.barWidth('100%') 
.barHeight(60)

vertical属性方法设置为true时,页签位于容器右侧。

Tabs({ barPosition: BarPosition.End}) {
  ...
}
.vertical(true) 
.barWidth(100) 
.barHeight(200)

自定义TabBar样式

TabContent的tabBar属性除了支持string类型,还支持使用@Builder装饰器修饰的函数。您可以使用@Builder装饰器,构造一个生成自定义TabBar样式的函数,实现上面的底部页签效果,示例代码如下:

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

  @Builder TabBuilder(title: string, targetIndex: number, selectedImg: Resource, normalImg: Resource) {
    Column() {
      Image(this.currentIndex === targetIndex ? selectedImg : normalImg)
        .size({ width: 25, height: 25 })
      Text(title)
        .fontColor(this.currentIndex === targetIndex ? '#1698CE' : '#6B6B6B')
    }
    .width('100%')
    .height(50)
    .justifyContent(FlexAlign.Center)
    .onClick(() => {
      this.currentIndex = targetIndex;
      this.tabsController.changeIndex(this.currentIndex);
    })
  }

  build() {
    Tabs({ barPosition: BarPosition.End, controller: this.tabsController }) {
      TabContent() {
        Column().width('100%').height('100%').backgroundColor('#00CB87')
      }
      .tabBar(this.TabBuilder('首页', 0, $r('app.media.home_selected'), $r('app.media.home_normal')))

      TabContent() {
        Column().width('100%').height('100%').backgroundColor('#007DFF')
      }
      .tabBar(this.TabBuilder('我的', 1, $r('app.media.mine_selected'), $r('app.media.mine_normal')))
    }
    .barWidth('100%')
    .barHeight(50)
    .onChange((index: number) => {
      this.currentIndex = index;
    })
  }
}

这里给Tabs组件设置了TabsController控制器,当点击某个页签时,调用changeIndex方法进行页签内容切换。还给Tabs添加onChange事件,Tab页签切换后触发该事件,这样当我们左右滑动内容视图的时候,页签样式也会跟着改变。