第3篇:界面基础——构建第一个页面

本课目标:掌握ArkUI的基础组件和布局,能制作美观的界面
**作者:**中文编程倡导者—— 李金雨
预计课时:3课时(135分钟)
难度等级:⭐⭐⭐(进阶)


一、开篇引入

1.1 界面就像搭积木

你有没有玩过乐高积木?

  • 乐高有各种形状的积木块
  • 你可以把它们拼成房子、汽车、飞船
  • 不同的拼法,能做出完全不同的东西

做应用界面就像搭积木

  • 我们有各种"界面积木"(组件)
  • 把它们组合起来,就能做出漂亮的界面
  • 文字积木、按钮积木、图片积木、输入框积木…

1.2 本课我们要做什么?

今天我们要学习:

  1. 基础组件——文字、按钮、图片、输入框
  2. 布局容器——怎么排列这些组件
  3. 样式设置——颜色、大小、间距、圆角
  4. 实战项目——制作登录页面和个人名片

1.3 预期成果

完成本课后,你能做出这样的界面:

登录页面:                    个人名片:
┌─────────────────┐          ┌─────────────────┐
│                 │          │    ┌───────┐    │
│   用户登录       │          │    │  👤   │    │
│                 │          │    └───────┘    │
│  ┌───────────┐  │          │                 │
│  │ 请输入用户名│  │          │    张三         │
│  └───────────┘  │          │   软件工程师     │
│                 │          │                 │
│  ┌───────────┐  │          │  📧 zhang@qq.com│
│  │ 请输入密码 │  │          │  📱 138****8888 │
│  └───────────┘  │          │                 │
│                 │          │  [发消息] [关注] │
│  ┌───────────┐  │          │                 │
│  │   登 录   │  │          └─────────────────┘
│  └───────────┘  │
│                 │
└─────────────────┘

二、概念讲解

2.1 声明式UI——告诉它要什么

生活例子:去餐厅点菜

命令式(传统方式):

你去厨房,拿一个锅,倒油,
加热到180度,放入鸡蛋,
煎3分钟,翻面,再煎2分钟,
装盘,撒上葱花...

声明式(ArkTS方式):

我要一份煎蛋,两面金黄,撒葱花。

看到了吗?声明式更简单!你只需要说想要什么结果,不用管具体怎么做。

ArkTS的声明式写法
// 声明:我要一个垂直排列的容器
Column() {
  // 里面放一个文字,内容是"你好"
  Text("你好")
    // 文字大小是30
    .fontSize(30)
    // 文字颜色是红色
    .fontColor("#FF0000")
}

声明了界面应该长什么样,ArkTS会自动帮你实现!

2.2 基础组件——界面积木

组件1:Text(文字)——显示文字

就像纸上的文字,用来显示信息。

Text("这是一段文字")
  .fontSize(20)                    // 字体大小
  .fontColor("#333333")            // 字体颜色
  .fontWeight(FontWeight.Bold)     // 字体粗细
  .maxLines(2)                     // 最多显示2行
  .textOverflow({ overflow: TextOverflow.Ellipsis })  // 超出显示省略号

常用属性

属性 作用 例子
.fontSize(20) 字体大小 .fontSize(30)
.fontColor("#FF0000") 字体颜色 .fontColor("#2196F3")
.fontWeight(FontWeight.Bold) 字体粗细 .fontWeight(FontWeight.Normal)
.lineHeight(30) 行高 .lineHeight(40)
.textAlign(TextAlign.Center) 文字对齐 .textAlign(TextAlign.Left)
组件2:Button(按钮)——点击触发动作

就像电梯按钮、门铃,点击后会做某事。

Button("点我")
  .width(200)                      // 宽度
  .height(50)                      // 高度
  .backgroundColor("#2196F3")      // 背景颜色
  .fontColor("#FFFFFF")            // 文字颜色
  .fontSize(20)                    // 文字大小
  .onClick(() => {                 // 点击时做什么
    console.log("按钮被点击了!")
  })

按钮的类型

// 普通按钮(默认)
Button("普通按钮")

// 胶囊按钮(圆角)
Button("胶囊按钮", { type: ButtonType.Capsule })

// 圆形按钮
Button("圆", { type: ButtonType.Circle })
  .width(60)
  .height(60)
组件3:Image(图片)——显示图片

用来显示照片、图标、背景图等。

// 显示网络图片
Image("https://example.com/photo.jpg")
  .width(200)
  .height(200)
  .borderRadius(10)                // 圆角

// 显示本地图片(放在resources/rawfile目录)
Image($rawfile("avatar.png"))
  .width(100)
  .height(100)

// 显示资源图片(放在resources/base/media目录)
Image($r("app.media.icon"))
  .width(50)
  .height(50)

图片显示模式

Image("xxx.jpg")
  .objectFit(ImageFit.Cover)       // 裁剪填满(保持比例)
  .objectFit(ImageFit.Contain)     // 完整显示(可能有空白)
  .objectFit(ImageFit.Fill)        // 拉伸填满(可能变形)
组件4:TextInput(输入框)——输入文字

就像表格里的填空处,让用户输入信息。

TextInput({ 
  placeholder: "请输入用户名",     // 提示文字
  text: ""                         // 默认内容
})
  .width("80%")                    // 宽度
  .height(50)                      // 高度
  .backgroundColor("#F5F5F5")      // 背景色
  .borderRadius(8)                 // 圆角
  .onChange((value: string) => {   // 内容变化时
    console.log("输入了:" + value)
  })

输入框类型

// 普通文本
TextInput({ placeholder: "请输入" })

// 密码输入(显示圆点)
TextInput({ placeholder: "请输入密码" })
  .type(InputType.Password)

// 数字输入(弹出数字键盘)
TextInput({ placeholder: "请输入年龄" })
  .type(InputType.Number)

// 邮箱输入
TextInput({ placeholder: "请输入邮箱" })
  .type(InputType.Email)

2.3 布局容器——怎么排列积木

容器1:Column(垂直排列)——一列一列往下排

就像排队做操,一个接一个竖着排。

Column({ space: 20 }) {           // space: 子组件之间的间距
  Text("第一行")
  Text("第二行")
  Text("第三行")
  Button("按钮")
}
.width("100%")                    // 容器宽度
.height("100%")                   // 容器高度
.backgroundColor("#F0F0F0")       // 背景色
.padding(20)                      // 内边距

效果:

第一行

第二行

第三行

[按钮]
容器2:Row(水平排列)——一行一行横着排

就像坐成一排,一个接一个横着排。

Row({ space: 20 }) {
  Text("左边")
  Text("中间")
  Text("右边")
}
.width("100%")
.height(60)
.backgroundColor("#E3F2FD")

效果:

左边   中间   右边
容器3:Stack(层叠排列)——叠在一起

就像一叠纸,一张盖在另一张上面。

Stack({ alignContent: Alignment.Center }) {
  // 底层:背景图
  Image($r("app.media.bg"))
    .width("100%")
    .height("100%")
  
  // 上层:文字
  Text("叠在上面的文字")
    .fontSize(30)
    .fontColor("#FFFFFF")
}
.width("100%")
.height(200)

效果:

┌─────────────────┐
│   [背景图片]     │
│                 │
│ 叠在上面的文字   │
│                 │
└─────────────────┘
容器4:Flex(弹性布局)——灵活排列

更灵活的布局方式,可以自动调整子组件的大小和位置。

Flex({ 
  direction: FlexDirection.Row,      // 水平排列
  justifyContent: FlexAlign.Center,  // 水平居中
  alignItems: ItemAlign.Center       // 垂直居中
}) {
  Text("居中文字")
  Button("居中按钮")
}
.width("100%")
.height(100)

2.4 样式设置——让界面变漂亮

尺寸设置
组件()
  .width(200)           // 固定宽度200像素
  .width("50%")         // 占父容器宽度的50%
  .width("100%")        // 填满父容器
  
  .height(100)          // 固定高度
  .height("100%")       // 填满父容器
  
  .size({ width: 200, height: 100 })  // 同时设置宽高
边距设置
// 外边距(margin):组件与其他组件的距离
组件()
  .margin(20)                    // 四边都是20
  .margin({ top: 10 })           // 只有上边10
  .margin({ left: 10, right: 10 })  // 左右各10
  .margin({ top: 10, bottom: 10, left: 20, right: 20 })

// 内边距(padding):内容与边框的距离
组件()
  .padding(20)                   // 四边都是20
  .padding({ horizontal: 15 })   // 左右15
  .padding({ vertical: 10 })     // 上下10

比喻

  • 外边距 = 你和同桌之间的距离
  • 内边距 = 书本内容和纸张边缘的距离
边框设置
组件()
  .border({
    width: 2,                     // 边框宽度
    color: "#2196F3",            // 边框颜色
    style: BorderStyle.Solid     // 边框样式(实线)
  })
  .borderRadius(10)              // 圆角半径
  .borderRadius({
    topLeft: 10,                  // 左上圆角
    topRight: 20,                 // 右上圆角
    bottomLeft: 10,               // 左下圆角
    bottomRight: 20               // 右下圆角
  })
背景设置
组件()
  .backgroundColor("#F5F5F5")           // 纯色背景
  .backgroundColor("#2196F3")           // 蓝色背景
  .backgroundColor(0x2196F3)            // 另一种写法
  
  .backgroundImage($r("app.media.bg"))  // 图片背景
  .backgroundImageSize(ImageSize.Cover) // 背景图大小
对齐方式
Column() {
  Text("居中")
}
.justifyContent(FlexAlign.Center)      // 垂直居中
.alignItems(HorizontalAlign.Center)    // 水平居中

Row() {
  Text("居中")
}
.justifyContent(FlexAlign.Center)      // 水平居中
.alignItems(VerticalAlign.Center)      // 垂直居中

三、动手实践

3.1 基础练习:制作登录页面

让我们做一个漂亮的登录页面:

// 完整可运行代码,复制到 Index.ets 即可运行
@Entry
@Component
struct Index {
  @State 用户名: string = ""
  @State 密码: string = ""

  build() {
    Column({ space: 20 }) {
      // 顶部标题区域
      Column({ space: 10 }) {
        Text("欢迎回来")
          .fontSize(35)
          .fontWeight(FontWeight.Bold)
          .fontColor("#333333")
        
        Text("请登录您的账号")
          .fontSize(16)
          .fontColor("#999999")
      }
      .margin({ top: 80, bottom: 40 })
      
      // 输入区域
      Column({ space: 15 }) {
        // 用户名输入框
        TextInput({ 
          placeholder: "请输入用户名",
          text: this.用户名 
        })
          .width("85%")
          .height(55)
          .backgroundColor("#F5F5F5")
          .borderRadius(12)
          .padding({ left: 20 })
          .onChange((value: string) => {
            this.用户名 = value
          })
        
        // 密码输入框
        TextInput({ 
          placeholder: "请输入密码",
          text: this.密码 
        })
          .width("85%")
          .height(55)
          .backgroundColor("#F5F5F5")
          .borderRadius(12)
          .padding({ left: 20 })
          .type(InputType.Password)
          .onChange((value: string) => {
            this.密码 = value
          })
      }
      
      // 登录按钮
      Button("登 录", { type: ButtonType.Capsule })
        .width("85%")
        .height(55)
        .backgroundColor("#2196F3")
        .fontSize(18)
        .fontWeight(FontWeight.Medium)
        .margin({ top: 30 })
        .onClick(() => {
          // 这里写登录逻辑
          console.log("用户名:" + this.用户名)
          console.log("密码:" + this.密码)
        })
      
      // 底部链接
      Row({ space: 5 }) {
        Text("还没有账号?")
          .fontSize(14)
          .fontColor("#999999")
        
        Text("立即注册")
          .fontSize(14)
          .fontColor("#2196F3")
          .fontWeight(FontWeight.Bold)
      }
      .margin({ top: 20 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
  }
}

3.2 进阶练习:制作个人名片

做一个精美的个人名片:

// 完整可运行代码,复制到 Index.ets 即可运行
@Entry
@Component
struct Index {
  @State 姓名: string = "张三"
  @State 职位: string = "高级前端工程师"
  @State 公司: string = "华为技术有限公司"
  @State 邮箱: string = "zhangsan@huawei.com"
  @State 电话: string = "138****8888"

  build() {
    Column() {
      // 顶部装饰条
      Row()
        .width('100%')
        .height(120)
        .backgroundColor('#2196F3')
      
      // 头像(叠在装饰条下方)
      Stack({ alignContent: Alignment.Bottom }) {
        Column()
          .width(120)
          .height(120)
          .backgroundColor('#FFFFFF')
          .borderRadius(60)
          .shadow({ radius: 10, color: '#20000000' })
        
        Text("👤")
          .fontSize(60)
          .margin({ bottom: 25 })
      }
      .margin({ top: -60 })
      
      // 姓名和职位
      Column({ space: 8 }) {
        Text(this.姓名)
          .fontSize(28)
          .fontWeight(FontWeight.Bold)
          .fontColor('#333333')
        
        Text(this.职位)
          .fontSize(16)
          .fontColor('#666666')
        
        Text(this.公司)
          .fontSize(14)
          .fontColor('#999999')
      }
      .margin({ top: 20 })
      
      // 分隔线
      Row()
        .width('80%')
        .height(1)
        .backgroundColor('#EEEEEE')
        .margin({ top: 30, bottom: 30 })
      
      // 联系信息
      Column({ space: 20 }) {
        this.信息项("📧", "邮箱", this.邮箱)
        this.信息项("📱", "电话", this.电话)
        this.信息项("📍", "地址", "深圳市南山区")
      }
      .width('85%')
      
      // 操作按钮
      Row({ space: 20 }) {
        Button("发消息", { type: ButtonType.Capsule })
          .width(120)
          .height(45)
          .backgroundColor('#2196F3')
        
        Button("关注", { type: ButtonType.Capsule })
          .width(120)
          .height(45)
          .backgroundColor('#FFFFFF')
          .fontColor('#2196F3')
          .border({ width: 1, color: '#2196F3' })
      }
      .margin({ top: 40 })
      
      // 底部留白
      Blank()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
  
  // 自定义信息项组件
  @Builder
  信息项(图标: string, 标签: string, 内容: string) {
    Row() {
      Text(图标)
        .fontSize(20)
        .width(30)
      
      Text(标签)
        .fontSize(14)
        .fontColor('#999999')
        .width(50)
      
      Text(内容)
        .fontSize(16)
        .fontColor('#333333')
    }
    .width('100%')
    .height(40)
  }
}

3.3 挑战练习:商品展示卡片

做一个电商App的商品卡片:

// 完整可运行代码,复制到 Index.ets 即可运行
@Entry
@Component
struct Index {
  @State 商品列表: object[] = [
    { 
      名称: "无线蓝牙耳机", 
      价格: 299, 
      原价: 399, 
      销量: 12580,
      标签: ["热销", "包邮"]
    },
    { 
      名称: "智能手环 Pro", 
      价格: 199, 
      原价: 259, 
      销量: 8560,
      标签: ["新品"]
    },
    { 
      名称: "便携充电宝", 
      价格: 89, 
      原价: 129, 
      销量: 23150,
      标签: ["爆款", "限时"]
    }
  ]

  build() {
    Column({ space: 15 }) {
      // 标题
      Text("热门商品")
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 20, left: 20 })
        .alignSelf(ItemAlign.Start)
      
      // 商品列表
      List({ space: 15 }) {
        ForEach(this.商品列表, (商品: object) => {
          ListItem() {
            this.商品卡片(商品)
          }
        })
      }
      .padding(20)
      .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
  
  @Builder
  商品卡片(商品: object) {
    Column() {
      // 商品图片区域(用颜色块代替)
      Stack({ alignContent: Alignment.TopStart }) {
        Row()
          .width('100%')
          .height(180)
          .backgroundColor('#E3F2FD')
        
        Text("📷")
          .fontSize(60)
          .width('100%')
          .textAlign(TextAlign.Center)
          .margin({ top: 50 })
        
        // 标签
        Row({ space: 5 }) {
          ForEach(商品.标签, (标签: string) => {
            Text(标签)
              .fontSize(12)
              .fontColor('#FFFFFF')
              .padding({ left: 8, right: 8, top: 3, bottom: 3 })
              .backgroundColor('#FF5722')
              .borderRadius(4)
          })
        }
        .margin(10)
      }
      
      // 商品信息
      Column({ space: 8 }) {
        Text(商品.名称)
          .fontSize(16)
          .fontColor('#333333')
          .maxLines(2)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
        
        Row() {
          Text(`¥${商品.价格}`)
            .fontSize(22)
            .fontColor('#F44336')
            .fontWeight(FontWeight.Bold)
          
          Text(`¥${商品.原价}`)
            .fontSize(14)
            .fontColor('#999999')
            .decoration({ type: TextDecorationType.LineThrough })
            .margin({ left: 10 })
        }
        
        Text(`${商品.销量}人已购`)
          .fontSize(12)
          .fontColor('#999999')
      }
      .width('100%')
      .padding(12)
      .alignItems(HorizontalAlign.Start)
    }
    .width('100%')
    .backgroundColor('#FFFFFF')
    .borderRadius(12)
    .shadow({ radius: 6, color: '#10000000' })
  }
}

四、知识总结

4.1 核心概念回顾

  1. 声明式UI:告诉系统"要什么",而不是"怎么做"
  2. 组件:界面的基本 building blocks(积木块)
  3. 容器:用来排列组件的"盒子"
  4. 样式:让界面变漂亮的各种设置

4.2 组件速查表

组件 作用 常用属性
Text 显示文字 .fontSize(), .fontColor(), .fontWeight()
Button 按钮 .backgroundColor(), .onClick()
Image 显示图片 .width(), .height(), .borderRadius()
TextInput 输入框 .placeholder, .type(), .onChange()
Column 垂直排列 .space, .justifyContent(), .alignItems()
Row 水平排列 .space, .justifyContent(), .alignItems()
Stack 层叠排列 .alignContent

4.3 样式速查表

样式 代码 例子
尺寸 .width(), .height() .width("100%")
边距 .margin(), .padding() .margin(20), .padding({ top: 10 })
边框 .border(), .borderRadius() .borderRadius(10)
背景 .backgroundColor() .backgroundColor("#F5F5F5")
对齐 .justifyContent(), .alignItems() .justifyContent(FlexAlign.Center)
阴影 .shadow() .shadow({ radius: 10, color: '#20000000' })

4.4 常见错误提醒

错误现象 原因 解决方法
组件不显示 没设置宽高 给组件设置.width().height()
文字被截断 容器太小 增大容器或使用.maxLines()
布局错乱 百分比计算错误 检查父容器是否有固定尺寸
按钮点不了 被其他组件覆盖 检查Stack中的层级顺序
样式不生效 写在了错误的位置 确保样式跟在组件后面

五、课后作业

5.1 巩固练习(必做)

练习1:完善登录页面

给登录页面添加:

  • 记住密码复选框
  • 第三方登录按钮(微信、QQ图标)
  • 忘记密码链接

练习2:制作设置页面

做一个手机设置页面,包含:

  • 头像和用户名
  • 多个设置项(账号、通知、隐私、关于)
  • 每个设置项右边有箭头

练习3:制作计算器界面

只做界面,不用实现功能:

  • 显示区域(显示数字)
  • 数字按钮(0-9)
  • 运算符按钮(+、-、×、÷)
  • 等号按钮

5.2 创意编程(选做)

创意1:制作音乐播放器界面

包含:

  • 专辑封面(大图)
  • 歌曲名和歌手名
  • 进度条
  • 播放控制按钮(上一首、播放/暂停、下一首)

创意2:制作天气卡片

包含:

  • 城市名称
  • 当前温度和天气图标
  • 未来几天预报
  • 空气质量指数

创意3:制作聊天界面

包含:

  • 消息列表(左右气泡)
  • 输入框和发送按钮
  • 对方头像和名字

5.3 下篇预习

下一篇,我们将学习状态管理,让界面能够响应用户的操作。预习问题:

  1. 怎么让按钮点击后改变文字内容?
  2. 怎么实现计数器功能(点一下数字加1)?
  3. 怎么让输入框的内容实时显示在界面上?

附录:更多布局技巧

技巧1:等分布局

Row() {
  Text("左")
    .layoutWeight(1)    // 占1份
    .backgroundColor('#FFCDD2')
  
  Text("中")
    .layoutWeight(2)    // 占2份
    .backgroundColor('#C8E6C9')
  
  Text("右")
    .layoutWeight(1)    // 占1份
    .backgroundColor('#BBDEFB')
}
.width('100%')
.height(100)

效果:左占25%,中占50%,右占25%

技巧2:滚动列表

Scroll() {
  Column({ space: 10 }) {
    ForEach([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], (item: number) => {
      Text(`${item}`)
        .width('100%')
        .height(80)
        .backgroundColor('#F5F5F5')
    })
  }
  .width('100%')
}
.height('100%')

技巧3:网格布局

Grid() {
  ForEach([1, 2, 3, 4, 5, 6], (item: number) => {
    GridItem() {
      Text(`${item}`)
        .width('100%')
        .height(100)
        .backgroundColor('#E3F2FD')
    }
  })
}
.columnsTemplate('1fr 1fr 1fr')    // 3列等宽
.columnsGap(10)                    // 列间距
.rowsGap(10)                       // 行间距
.width('100%')

恭喜你完成了第3篇的学习! 🎉

现在你已经掌握了制作漂亮界面的基本技能。记住,好的界面设计需要多观察、多练习。平时可以多看看优秀的App,学习它们的布局和配色!

下节课,我们将学习如何让界面"动"起来,响应用户的操作!

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐