第二章 ArkUI基础组件与布局:常用组件与布局容器实战

大家好,我是你们的老朋友。今天咱们来聊聊ArkUI里最基础、也最常用的那些组件和布局方式。说实话,这部分内容看着简单,但我在项目里见过太多人在这上面栽跟头了。你想想看,一个App好不好看、好不好用,很大程度上就取决于这些基础组件和布局用得怎么样。

2.1 常用组件:Text、Image、Button

这三个组件,可以说是鸿蒙应用开发的"三驾马车"。几乎每个页面都离不开它们。我个人习惯先把它们玩透了,再去碰那些花里胡哨的高级组件。

2.1.1 Text组件

Text组件就是用来显示文字的。嗯,这里要注意,它不只是显示纯文本那么简单。

Text('Hello HarmonyOS')
  .fontSize(20)
  .fontColor(Color.Blue)
  .fontWeight(FontWeight.Bold)
  .textAlign(TextAlign.Center)
  .lineHeight(30)
  .maxLines(2)
  .textOverflow({ overflow: TextOverflow.Ellipsis })

我曾经在做一个新闻列表时,就遇到过文字超长把布局撑变形的问题。后来用了maxLinestextOverflow,问题就解决了。说白了,就是给文字加个"笼子",不让它乱跑。

小技巧: 如果你想让文字支持富文本样式,可以用Span组件嵌套在Text里。比如一段文字里既有红色又有蓝色,用Span就能轻松搞定。

2.1.2 Image组件

Image组件用来显示图片。我记得刚开始用的时候,总以为直接给个路径就行了。其实没那么简单。

Image($r('app.media.icon'))
  .width(100)
  .height(100)
  .borderRadius(10)
  .objectFit(ImageFit.Cover)
  .interpolation(ImageInterpolation.High)

这里有个坑,我必须要说。图片的objectFit属性,很多人搞不清楚。它决定了图片在容器里怎么缩放。我建议你记住这几个:

  • Cover:裁剪填充,保证图片填满容器,可能会被裁掉一部分
  • Contain:完整显示,保证图片全部可见,可能会有留白
  • Fill:拉伸填充,图片会变形
避坑指南: 我曾经在加载网络图片时,忘记处理加载失败的情况。结果图片显示不出来,整个页面就空了一块。记得一定要加.alt()属性设置占位图。

2.1.3 Button组件

Button组件,说白了就是用户点击交互的入口。但它的样式定制,比你想的要灵活得多。

Button('点击我')
  .type(ButtonType.Capsule)
  .backgroundColor('#007AFF')
  .fontColor(Color.White)
  .fontSize(16)
  .width(200)
  .height(48)
  .borderRadius(24)
  .onClick(() => {
    console.log('按钮被点击了')
  })

我个人习惯用ButtonType.Capsule胶囊样式,看起来更圆润、更现代。如果你想要完全自定义的按钮,可以用Button包裹其他组件,比如:

Button() {
  Row() {
    Image($r('app.media.icon'))
      .width(20)
      .height(20)
    Text('带图标的按钮')
      .fontSize(14)
  }
}

2.2 布局容器:Row、Column、Stack

布局容器,就是用来"装"组件的容器。它们决定了组件在屏幕上的排列方式。你想想看,没有布局容器,所有组件都堆在一起,那画面太美不敢看。

2.2.1 Row(水平布局)

Row让子组件水平排列。我经常用它来做导航栏、标签栏。

Row() {
  Text('首页')
  Text('发现')
  Text('我的')
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
.alignItems(VerticalAlign.Center)

这里justifyContent控制水平方向的对齐,alignItems控制垂直方向。我建议你多试试不同的组合,感受一下效果。

2.2.2 Column(垂直布局)

Column让子组件垂直排列。表单页面、列表页面,基本都靠它。

Column() {
  Text('用户名')
  TextInput({ placeholder: '请输入用户名' })
  Text('密码')
  TextInput({ placeholder: '请输入密码' })
  Button('登录')
}
.width('100%')
.padding(20)
.alignItems(HorizontalAlign.Start)

嗯,这里要注意,Column默认是居中对齐的。如果你想让子组件左对齐,记得设置alignItems(HorizontalAlign.Start)

2.2.3 Stack(层叠布局)

Stack允许子组件重叠。说白了,就像叠罗汉一样。我经常用它来做头像上的角标、图片上的文字水印。

Stack() {
  Image($r('app.media.avatar'))
    .width(100)
    .height(100)
    .borderRadius(50)
  Text('VIP')
    .fontSize(12)
    .fontColor(Color.White)
    .backgroundColor(Color.Red)
    .borderRadius(4)
    .padding({ left: 4, right: 4 })
    .position({ x: 70, y: 70 })
}
重点: Stack的position属性可以精确控制子组件的位置。但要注意,它是相对于Stack容器本身的。我建议你多用alignContentalignItems来做相对定位,这样适配性更好。

2.3 Flex布局实战

Flex布局,可以说是布局里的"瑞士军刀"。它比Row和Column更灵活,能应对更复杂的场景。

2.3.1 Flex基础

Flex布局的核心思想,就是让子组件"弹性"地填充空间。我刚开始学的时候,觉得它很抽象。后来做多了,发现其实就是几个关键属性的事。

Flex({
  direction: FlexDirection.Row,
  wrap: FlexWrap.Wrap,
  justifyContent: FlexAlign.SpaceBetween,
  alignItems: ItemAlign.Center
}) {
  Text('项目1').width(80).height(40).backgroundColor('#FF6B6B')
  Text('项目2').width(80).height(40).backgroundColor('#4ECDC4')
  Text('项目3').width(80).height(40).backgroundColor('#45B7D1')
  Text('项目4').width(80).height(40).backgroundColor('#96CEB4')
}

2.3.2 实战:商品列表

我记得有一次做电商项目,需要实现一个商品列表。每行显示两个商品,还要自适应屏幕宽度。用Flex布局,几行代码就搞定了。

Flex({
  direction: FlexDirection.Row,
  wrap: FlexWrap.Wrap,
  justifyContent: FlexAlign.SpaceBetween
}) {
  ForEach(this.productList, (item) => {
    Column() {
      Image(item.image)
        .width('100%')
        .height(150)
        .objectFit(ImageFit.Cover)
      Text(item.name)
        .fontSize(14)
        .margin({ top: 8 })
      Text('¥' + item.price)
        .fontSize(16)
        .fontColor(Color.Red)
        .fontWeight(FontWeight.Bold)
    }
    .width('48%')
    .backgroundColor(Color.White)
    .borderRadius(8)
    .padding(10)
    .margin({ bottom: 10 })
  })
}
实战经验: 用Flex做网格布局时,记得给每个子组件设置width为百分比。比如两列就设48%,留出间距。我曾经直接设50%,结果因为间距问题,第二行就错位了。

2.3.3 Flex的伸缩性

Flex最强大的地方,在于它的伸缩性。你可以让某个子组件"吃掉"剩余的空间。

Row() {
  Text('固定宽度').width(100).backgroundColor('#FF6B6B')
  Text('弹性伸缩').layoutWeight(1).backgroundColor('#4ECDC4')
  Text('固定宽度').width(100).backgroundColor('#45B7D1')
}
.width('100%')
.height(50)

这里layoutWeight(1)就是关键。它告诉系统:剩下的空间都给我。你想想看,这在做自适应布局时有多方便。

避坑指南: 我曾经在嵌套Flex时,忘记给父容器设置固定宽度或高度。结果子组件的layoutWeight完全不起作用。记住,Flex的伸缩性需要父容器有明确的尺寸约束。

2.4 布局实战:个人中心页面

好了,理论说完了。咱们来做个实战。一个简单的个人中心页面,把今天学的都用上。

Column() {
  // 头像区域
  Stack() {
    Image($r('app.media.avatar'))
      .width(80)
      .height(80)
      .borderRadius(40)
    Text('编辑')
      .fontSize(12)
      .fontColor(Color.White)
      .backgroundColor('rgba(0,0,0,0.5)')
      .borderRadius(4)
      .padding({ left: 8, right: 8 })
      .position({ x: 20, y: 60 })
  }
  .width(80)
  .height(80)
  .margin({ top: 30 })

  // 用户名
  Text('张三')
    .fontSize(20)
    .fontWeight(FontWeight.Bold)
    .margin({ top: 10 })

  // 统计信息
  Row() {
    Column() {
      Text('128')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
      Text('关注')
        .fontSize(12)
        .fontColor('#666')
    }
    .layoutWeight(1)
    .alignItems(HorizontalAlign.Center)

    Column() {
      Text('256')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
      Text('粉丝')
        .fontSize(12)
        .fontColor('#666')
    }
    .layoutWeight(1)
    .alignItems(HorizontalAlign.Center)

    Column() {
      Text('64')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
      Text('动态')
        .fontSize(12)
        .fontColor('#666')
    }
    .layoutWeight(1)
    .alignItems(HorizontalAlign.Center)
  }
  .width('100%')
  .padding(20)

  // 功能列表
  Column() {
    Row() {
      Text('我的订单')
      Text('>')
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)
    .padding(15)

    Row() {
      Text('我的收藏')
      Text('>')
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)
    .padding(15)

    Row() {
      Text('设置')
      Text('>')
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)
    .padding(15)
  }
  .width('100%')
  .backgroundColor(Color.White)
  .borderRadius(10)
  .padding(10)
  .margin({ top: 20 })
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
.alignItems(HorizontalAlign.Center)

这个页面用到了我们今天学的所有内容:Text、Image、Button、Row、Column、Stack、Flex。你仔细看看,是不是每个组件都各司其职?

好了,今天的课就到这里。记住,布局这东西,光看不行,一定要动手练。我建议你把这个个人中心页面自己敲一遍,然后试着改改样式、加加功能。下节课我们聊状态管理和数据绑定,到时候见。