第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开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐