第9篇:页面跳转——多页面应用开发

本课目标:掌握页面路由和导航,能开发多页面应用
**作者:**中文编程倡导者—— 李金雨
预计课时:2课时(90分钟)
难度等级:⭐⭐⭐(进阶)


一、开篇引入

1.1 从单页面到多页面

前面的课程,我们做的应用都只有一个页面。但真正的应用通常有多个页面:

微信App:
├── 首页(聊天列表)
├── 通讯录
├── 发现
├── 我
└── 聊天详情页

淘宝App:
├── 首页
├── 分类
├── 购物车
├── 我的淘宝
└── 商品详情页

1.2 页面之间的关系

首页 ──→ 详情页 ──→ 评论页
  │
  └──→ 设置页 ──→ 关于页
  • 跳转:从A页面到B页面
  • 传参:把数据从A页面带到B页面
  • 返回:从B页面回到A页面

1.3 本课目标

今天我们要学习:

  1. 什么是页面路由
  2. 怎么跳转到新页面
  3. 怎么传递参数
  4. 怎么返回上一页
  5. 实战:实现多页面应用

1.4 预期成果

完成本课后,你能做出这样的多页面应用:

首页                      详情页                    设置页
┌─────────────┐          ┌─────────────┐          ┌─────────────┐
│ 首页        >│          │ < 详情      │          │ < 设置      │
├─────────────┤          ├─────────────┤          ├─────────────┤
│             │          │             │          │             │
│  [商品1]    │──点击──→│   商品详情   │          │  账号设置   │
│             │          │   ¥199      │          │  通知设置   │
│  [商品2]    │──点击──→│   [购买]    │          │  隐私设置   │
│             │          │             │          │             │
│  [商品3]    │          │  [去设置]   │──点击──→│  [关于我们] │──点击──→
│             │          │             │          │             │
└─────────────┘          └─────────────┘          └─────────────┘

二、概念讲解

2.1 什么是页面路由?

定义

路由(Router)就是管理页面之间跳转的系统。

就像导航仪:

  • 知道你在哪里(当前页面)
  • 知道你要去哪里(目标页面)
  • 规划路线(跳转方式)
ArkTS中的路由

ArkTS使用 @ohos.router 模块来管理页面跳转:

import router from '@ohos.router'

2.2 @Entry装饰器

什么是@Entry?

@Entry 标记一个组件是页面入口,可以被路由跳转。

@Entry                    // 标记这是页面入口
@Component
struct 首页 {
  build() {
    // 页面内容
  }
}
页面文件结构
entry/src/main/ets/pages/
├── Index.ets          // 首页(默认页面)
├── 详情页.ets         // 详情页
├── 设置页.ets         // 设置页
└── 关于页.ets         // 关于页

注意:页面文件必须放在 pages 目录下!

2.3 页面跳转

pushUrl——普通跳转
import router from '@ohos.router'

// 跳转到新页面
router.pushUrl({
  url: 'pages/详情页'    // 目标页面路径
})

特点

  • 新页面覆盖当前页面
  • 可以返回上一页
  • 页面会入栈
replaceUrl——替换跳转
router.replaceUrl({
  url: 'pages/详情页'
})

特点

  • 替换当前页面
  • 不能返回上一页(上一页被替换了)
  • 用于登录后跳转到首页等场景
跳转方式对比
方式 能否返回 使用场景
pushUrl 正常页面跳转
replaceUrl 不能 登录后跳转、不需要返回的跳转

2.4 页面传参

传递参数
// 从首页跳转到详情页,带上商品ID
router.pushUrl({
  url: 'pages/详情页',
  params: {           // 传递的参数
    商品ID: '12345',
    商品名: '手机',
    价格: 2999
  }
})
接收参数
@Entry
@Component
struct 详情页 {
  // 获取传递过来的参数
  @State 商品信息: object = router.getParams()

  build() {
    Column() {
      // 使用参数
      Text(`商品:${this.商品信息?.['商品名']}`)
      Text(`价格:¥${this.商品信息?.['价格']}`)
    }
  }
}
参数类型
// 可以传递各种类型的参数
router.pushUrl({
  url: 'pages/目标页',
  params: {
    字符串: 'Hello',
    数字: 123,
    布尔值: true,
    数组: [1, 2, 3],
    对象: { a: 1, b: 2 }
  }
})

2.5 返回上一页

基本返回
import router from '@ohos.router'

// 返回上一页
router.back()
返回到指定页面
// 返回到首页
router.back({
  url: 'pages/Index'
})
返回并传参
// 返回并带上结果
router.back({
  url: 'pages/首页',
  params: {
    操作结果: '成功',
    选择项: '选项A'
  }
})

2.6 获取页面信息

获取当前页面路径
let 当前页面 = router.getState().path
console.log('当前在:' + 当前页面)
获取页面栈信息
let 页面栈 = router.getState()
console.log('页面数量:' + 页面栈.length)

三、动手实践

3.1 基础练习:简单的三页面应用

创建三个页面:首页、详情页、设置页

首页(Index.ets):

import router from '@ohos.router'

// 完整可运行代码,复制到 Index.ets 即可运行
@Entry
@Component
struct Index {
  @State 商品列表: object[] = [
    { id: '1', 名称: '无线耳机', 价格: 299 },
    { id: '2', 名称: '智能手表', 价格: 599 },
    { id: '3', 名称: '便携音箱', 价格: 199 }
  ]

  build() {
    Column() {
      // 标题栏
      Row() {
        Text('🏠 首页')
          .fontSize(22)
          .fontWeight(FontWeight.Bold)
        
        Button('设置')
          .fontSize(14)
          .backgroundColor('transparent')
          .fontColor('#2196F3')
          .onClick(() => {
            this.去设置页()
          })
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)
      .padding(20)
      .backgroundColor('#FFFFFF')
      
      // 商品列表
      List({ space: 15 }) {
        ForEach(this.商品列表, (商品: object) => {
          ListItem() {
            Row({ space: 15 }) {
              Text('📦')
                .fontSize(40)
              
              Column({ space: 5 }) {
                Text(商品.名称)
                  .fontSize(18)
                  .fontWeight(FontWeight.Medium)
                Text(`¥${商品.价格}`)
                  .fontSize(16)
                  .fontColor('#F44336')
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Start)
              
              Text('>')
                .fontSize(20)
                .fontColor('#CCCCCC')
            }
            .width('100%')
            .padding(15)
            .backgroundColor('#FFFFFF')
            .borderRadius(10)
            .onClick(() => {
              this.去详情页(商品)
            })
          }
        })
      }
      .padding(15)
      .layoutWeight(1)
      .backgroundColor('#F5F5F5')
    }
    .width('100%')
    .height('100%')
  }
  
  去详情页(商品: object) {
    router.pushUrl({
      url: 'pages/详情页',
      params: {
        商品: 商品
      }
    })
  }
  
  去设置页() {
    router.pushUrl({
      url: 'pages/设置页'
    })
  }
}

详情页(详情页.ets):

import router from '@ohos.router'

@Entry// 详情页
// 完整可运行代码,复制到 Index.ets 即可运行
@Component
struct Index {
  @State 商品信息: object = router.getParams()?.['商品'] || {}

  build() {
    Column() {
      // 标题栏
      Row() {
        Text('< 返回')
          .fontSize(16)
          .fontColor('#2196F3')
          .onClick(() => {
            router.back()
          })
        
        Text('商品详情')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
        
        Blank()    // 占位,让标题居中
      }
      .width('100%')
      .padding(20)
      .backgroundColor('#FFFFFF')
      
      // 商品信息
      Column({ space: 20 }) {
        Text('📦')
          .fontSize(100)
          .margin(30)
        
        Text(this.商品信息?.['名称'] || '未知商品')
          .fontSize(28)
          .fontWeight(FontWeight.Bold)
        
        Text(`¥${this.商品信息?.['价格'] || 0}`)
          .fontSize(32)
          .fontColor('#F44336')
          .fontWeight(FontWeight.Bold)
        
        Text('这是商品的详细介绍...')
          .fontSize(14)
          .fontColor('#666666')
          .margin(20)
        
        Button('立即购买', { type: ButtonType.Capsule })
          .width('80%')
          .height(50)
          .backgroundColor('#2196F3')
          .margin({ top: 30 })
      }
      .width('100%')
      .layoutWeight(1)
      .backgroundColor('#F5F5F5')
    }
    .width('100%')
    .height('100%')
  }
}

设置页(设置页.ets):

import router from '@ohos.router'// 设置页
// 完整可运行代码,复制到 Index.ets 即可运行
@Entry
@Component
struct Index {
  build() {
    Column() {
      // 标题栏
      Row() {
        Text('< 返回')
          .fontSize(16)
          .fontColor('#2196F3')
          .onClick(() => {
            router.back()
          })
        
        Text('设置')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
        
        Blank()
      }
      .width('100%')
      .padding(20)
      .backgroundColor('#FFFFFF')
      
      // 设置列表
      Column({ space: 1 }) {
        this.设置项('账号与安全')
        this.设置项('通知设置')
        this.设置项('隐私设置')
        this.设置项('通用设置')
        this.设置项('关于我们')
        this.设置项('退出登录', '#F44336')
      }
      .margin(20)
      .backgroundColor('#FFFFFF')
      .borderRadius(12)
      .clip(true)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
  
  @Builder
  设置项(标题: string, 颜色: string = '#333333') {
    Row() {
      Text(标题)
        .fontSize(16)
        .fontColor(color)
      
      Text('>')
        .fontSize(16)
        .fontColor('#CCCCCC')
    }
    .width('100%')
    .height(55)
    .padding({ left: 20, right: 20 })
    .justifyContent(FlexAlign.SpaceBetween)
    .backgroundColor('#FFFFFF')
  }
}

3.2 进阶练习:带返回结果的页面

选择页面(选择页.ets):

import router from '@ohos.router'

@Entry
@Component
struct 选择页 {
  @State 选项列表: string[] = ['选项A', '选项B', '选项C', '选项D']

  build() {
    Column() {
      Text('请选择一个选项')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin(30)
      
      Column({ space: 15 }) {
        ForEach(this.选项列表, (选项: string) => {
          Button(选项, { type: ButtonType.Capsule })
            .width('80%')
            .height(50)
            .backgroundColor('#2196F3')
            .onClick(() => {
              this.返回结果(选项)
            })
        })
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
  
  返回结果(选择: string) {
    router.back({
      params: {
        选择结果: 选择
      }
    })
  }
}

主页面接收结果:

import router from '@ohos.router'

@Entry
@Component
struct 主页面 {
  @State 选择结果: string = '未选择'

  build() {
    Column({ space: 30 }) {
      Text(`当前选择:${this.选择结果}`)
        .fontSize(20)
        .margin(50)
      
      Button('去选择')
        .onClick(() => {
          router.pushUrl({
            url: 'pages/选择页'
          })
        })
    }
  }
  
  // 页面显示时检查是否有返回参数
  onPageShow() {
    let 参数 = router.getParams()
    if (参数?.['选择结果']) {
      this.选择结果 = 参数['选择结果']
    }
  }
}

3.3 进阶练习:底部导航栏

实现带底部导航的多页面应用:

import router from '@ohos.router'

@Entry
@Component
struct 带导航的首页 {
  @State 当前标签: number = 0
  
  标签列表: object[] = [
    { 名称: '首页', 图标: '🏠' },
    { 名称: '分类', 图标: '📂' },
    { 名称: '购物车', 图标: '🛒' },
    { 名称: '我的', 图标: '👤' }
  ]

  build() {
    Column() {
      // 内容区域
      Stack() {
        // 根据当前标签显示不同内容
        if (this.当前标签 == 0) {
          this.首页内容()
        } else if (this.当前标签 == 1) {
          this.分类内容()
        } else if (this.当前标签 == 2) {
          this.购物车内容()
        } else {
          this.我的内容()
        }
      }
      .width('100%')
      .layoutWeight(1)
      
      // 底部导航栏
      Row() {
        ForEach(this.标签列表, (标签: object, 索引: number) => {
          Column({ space: 5 }) {
            Text(标签.图标)
              .fontSize(24)
            Text(标签.名称)
              .fontSize(12)
          }
          .width('25%')
          .height(60)
          .fontColor(this.当前标签 == 索引 ? '#2196F3' : '#999999')
          .onClick(() => {
            this.当前标签 = 索引
          })
        })
      }
      .width('100%')
      .height(70)
      .backgroundColor('#FFFFFF')
      .border({ width: { top: 1 }, color: '#EEEEEE' })
    }
    .width('100%')
    .height('100%')
  }
  
  @Builder
  首页内容() {
    Column() {
      Text('首页内容')
        .fontSize(30)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
    .justifyContent(FlexAlign.Center)
  }
  
  @Builder
  分类内容() {
    Column() {
      Text('分类内容')
        .fontSize(30)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#E3F2FD')
    .justifyContent(FlexAlign.Center)
  }
  
  @Builder
  购物车内容() {
    Column() {
      Text('购物车内容')
        .fontSize(30)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFF3E0')
    .justifyContent(FlexAlign.Center)
  }
  
  @Builder
  我的内容() {
    Column() {
      Text('我的内容')
        .fontSize(30)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#E8F5E9')
    .justifyContent(FlexAlign.Center)
  }
}

四、知识总结

4.1 核心概念回顾

  1. 路由:管理页面跳转的系统
  2. @Entry:标记页面入口
  3. pushUrl:普通跳转(可返回)
  4. replaceUrl:替换跳转(不可返回)
  5. params:页面间传递参数
  6. back:返回上一页

4.2 路由API速查

import router from '@ohos.router'

// 普通跳转
router.pushUrl({ url: 'pages/目标页' })

// 带参数跳转
router.pushUrl({
  url: 'pages/目标页',
  params: {:}
})

// 替换跳转
router.replaceUrl({ url: 'pages/目标页' })

// 返回上一页
router.back()

// 返回到指定页
router.back({ url: 'pages/首页' })

// 获取参数
let 参数 = router.getParams()

// 获取当前页面信息
let 状态 = router.getState()

4.3 页面生命周期

方法 触发时机
aboutToAppear() 页面即将显示
onPageShow() 页面显示时
onPageHide() 页面隐藏时
aboutToDisappear() 页面即将销毁

4.4 常见错误提醒

错误现象 原因 解决方法
跳转失败 页面路径错误 检查pages目录下的文件名
参数为undefined 没传参数或key错误 检查params和getParams的key
返回后数据没更新 没使用onPageShow 在onPageShow中处理返回数据
无法返回 用了replaceUrl 改用pushUrl

五、课后作业

5.1 巩固练习(必做)

练习1:登录流程

实现登录流程:

  • 登录页 → 首页(replaceUrl,防止返回登录页)
  • 首页可以跳转到设置页
  • 设置页可以退出登录,返回登录页

练习2:商品浏览

实现商品浏览功能:

  • 商品列表页
  • 点击商品进入详情页(传递商品ID)
  • 详情页可以查看评论(再跳转)

练习3:表单填写

实现多步表单:

  • 第一步:填写基本信息
  • 第二步:填写详细信息
  • 第三步:确认提交
  • 每步可以返回修改

5.2 创意编程(选做)

创意1:新闻阅读App

  • 新闻列表页
  • 新闻详情页
  • 评论页面
  • 个人中心

创意2:电商购物流程

  • 商品浏览 → 商品详情 → 加入购物车
  • 购物车 → 确认订单 → 支付页面
  • 支付成功 → 订单详情

创意3:社交应用

  • 好友列表 → 聊天页面
  • 发现页面 → 动态详情
  • 个人主页 → 编辑资料

5.3 下篇预习

下一篇,我们将学习网络请求,从服务器获取数据。预习问题:

  1. 怎么从网络获取数据?
  2. 什么是HTTP请求?
  3. 怎么处理异步操作?

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

现在你已经掌握了页面跳转,可以开发多页面应用了。记住:路由让应用有结构,页面让功能更清晰

下节课,我们将学习如何从网络获取数据!

Logo

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

更多推荐