学习鸿蒙,我学到的东西与浅显理解(1-6、Web & Tabs & Navigation)

126 阅读4分钟

ArkWeb

在应用中显示 Web 网页的内容,提供:页面加载、页面交互、页面调试等能力

  1. 页面加载:可加载:网络页面、本地页面、HTML 片段
  2. 页面交互:设置页面深色模式、位置权限管理、Cookie管理等
  3. 页面调试:使用 DevTools 工具调试

可用于实现移动端的混合开发,即把页面嵌入 App 中

Web组件


Web({ src: './index.html' })

Webview

控制 Web 组件,例如:前进后退、异步执行脚本等能力

代码示例

import { Webview } from ''

const webviewController = new Webview.WebviewController()

// 加载网络
webviewController.lodaUrl('www.baidu.com')
// 加载本地
webviewController.lodaUrl($rawfile('local.html'))
// 加载代码片段
webviewController.lodaData('<html></html>', 'text/html','UTF-8')

build () {
  // 注意:
  Web({ src: '', controller: this.Controller })
}

注意

  • Web组件的 src 属性不能用 @State 的变量来响应式更改,必须使用 loadUrl
  • 需要给模块配置网络权限才能访问网络

Navigation

基础用法

@Entry
@Component
struct Index {
  // 实例化一个页面栈
  @Provide('pageStack') pageStack: NavPathStack = new NavPathStack()

  // 根据名称,判断该渲染哪个子页面,相当于路由表信息
  // 这个函数,需要传递给 Navigation.navDestination() 方法
  @Builder
  ChildPage(name: string) {
    if (name === 'Child_1') {
      pageOneTmp() // 子页面的写法 在下一小节
    }
    if (name === 'Child_2') {
      pageTwoTmp()
    }
  }

  build() {
    // 把 pageStack 传给 Navigation 组件,相当于把这俩绑定起来
    Navigation(this.pageStack) {
      // 点击按钮,调转到对应的子页面
      Button('NavRouter_1').onClick(() => {
        this.pageStack.pushPath({ name: 'Child_1' })
      })
      Button('NavRouter_2').onClick(() => {
        this.pageStack.pushPath({ name: 'Child_2' })
      })
    }
    // 页面标题
    .title('主标题')
    // 页面标题栏模式 (手机上是单页,pad上是两栏)
    .mode(NavigationMode.Auto)
    // 把路由表传进去
    .navDestination(this.ChildPage)
    // 顶部菜单
    .menus([
      { value: "", icon: "./image/ic_public_sea.svg" },
      { value: "", icon: "./image/ic_public_add.svg" },
    ])
    // 底部工具栏
    .toolbarConfiguration([
      { value: "tool1", icon: "./image/ic_public_1.svg" },
      { value: "tool2", icon: "./image/ic_public_2.svg" },
    ])
  }
}

子页面

// PageOne.ets // 普通页面
@Component
export struct pageOneTmp {
  @Consume('pageStack') pageStack: NavPathStack

  build() {
    // NavDestination 是子页面的根容器,可以设置一些属性和生命周期
    NavDestination() { 
      Text('Child_1')
    }
    .title("Child_1")
    .onBackPressed(() => {
      this.pageStack.pop()
      return true
    })
  }
}


// PageTwo.ets // 弹窗
@Component
export struct pageTwoTmp {
  @Consume('pageStack') pageStack: NavPathStack

  build() {
    NavDestination() {
      Stack({ alignContent: Alignment.Center }) {
        Column() {
          Text('Child_2')
          Button('Close').onClick(() => {
            this.pageStack.pop()
          })
        }
      }
    }
    .hideTitleBar(true) // 隐藏标题栏
    .mode(NavDestinationMode.DIALOG) // 设置为弹窗模式
    .backgroundColor('rgba(0,0,0,0.5)')
  }
}


// 子页面内部的自定义组件可以通过全局方法监听或查询到页面的一些状态信息
import { uiObserver, UIContext } from '@kit.ArkUI'

 // NavDestination 内的自定义组件
@Component
  struct MyComponent {
  navDesInfo: uiObserver.NavDestinationInfo | undefined
  
  uiContext: UIContext | null = null

  aboutToAppear(): void {
    // 获取子页面信息
    this.navDesInfo = this.queryNavDestinationInfo()

    // 监听 NavDestination 生命周期的变化
    uiObserver.on('navDestinationUpdate', info => {
      console.info('state update', JSON.stringify(info))
    })
   
    // 监听切换
    uiObserver.on(
      'navDestinationSwitch', 
      this.context, 
      (info: uiObserver.NavDestinationSwitchInfo) => {
        console.log(info)
      }
    )
      
    // 
    uiObserver.on('navDestinationSwitch', this.uiContext, callback)
  }

  build() {
   Column() {
     Text('所属页面 Name: ' + this.navDesInfo?.name)
   }
  }
}
生命周期描述用途
aboutToAppear在创建自定义组件后,执行其 build() 函数之前执行(NavDestination 创建之前)。允许在该方法中改变状态变量,更改将在后续执行 build() 函数中生效。
onWillAppearNavDestination 创建后,挂载到组件树之前执行。在该方法中更改状态变量会在当前帧显示生效。
onAppear通用生命周期事件,NavDestination 组件挂载到组件树时执行。
onWillShowNavDestination 组件布局显示之前执行,此时页面不可见。
onShownNavDestination 组件布局显示之后执行,此时页面已完成布局。
onWillHideNavDestination 组件触发隐藏之前执行。
onHiddenNavDestination 组件触发隐藏后执行。
onWillDisappearNavDestination 组件即将销毁之前执行,如果有转场动画,会在动画前触发。
onDisappear通用生命周期事件,NavDestination 组件从组件树上卸载销毁时执行。
aboutToDisappear自定义组件析构销毁之前执行。不允许在该方法中改变状态变量。

页面栈的操作

pageStack: NavPathStack = new NavPathStack()

// 普通跳转
this.pageStack.pushPath({ name: 'PageOne', param: ''PageOne Param })
this.pageStack.pushPathByName('PageOne', 'PageOne Param')

// 关闭跳转页面的动画 (第三个参数为 false)
this.pageStack.pushPathByName('PageOne', 'PageOne Param', flase)
// 也可以通过 全局关闭
this.pageStack.disableAnimation(true)

// 共享元素转场
 // 为两个页面内,要共享的元素,添加 .geometryTransition('id') 方法
  Image($r('app.media.startIcon'))
   .geometryTransition('sharedId')
 // 将跳转操作这样写,并关闭系统默认的专场
  animateTo({ duration: 1000 }, () => {
    this.pageStack.pushPath({ name: 'ToPage' }, false)
  })

// 跳转时添加 onPop 回调,能在子页面出栈时,获取返回的信息
this.pageStack.pushPathByName('PageOne', 'PageOne Param', (popInfo) => {
  console.log('弹出的页面为:' + popInfo.info.name + ', popInfo.result)
})

// 带错误码的跳转,跳转结束会触发异步回调,返回错误码信息
this.pageStack.pushDestinationByName('PageOne', 'PageOne Param')
.catch((error: BusinessError) => {
  console.error('跳转出错', JSON.stringify(error))
}).then(() => {
  console.error('跳转成功')
})

---

// 返回到上一页
this.pageStack.pop()
// 返回到上一个PageOne页面
this.pageStack.popToName('PageOne')
// 返回到索引为1的页面
this.pageStack.popToIndex(1)
// 返回到根首页(清除栈中所有页面)
this.pageStack.clear()

---

// 将栈顶页面替换为PageOne
this.pageStack.replacePath({ name: 'PageOne', param: 'PageOne Param' })
this.pageStack.replacePathByName('PageOne', 'PageOne Param')

---

// 删除栈中name为PageOne的所有页面
this.pageStack.removeByName('PageOne')
// 删除指定索引的页面
this.pageStack.removeByIndexes([1,3,5])

---

// 获取栈中所有页面name集合
this.pageStack.getAllPathName()
// 获取索引为1的页面参数
this.pageStack.getParamByIndex(1)
// 获取PageOne页面的参数
this.pageStack.getParamByName('PageOne')
// 获取PageOne页面的索引集合
this.pageStack.getIndexByName('PageOne')

---

// 路由拦截
// 注意:无论是哪个回调,在进入回调时,页面栈都已经发生了变化
this.pageStack.setInterception({
  // 页面跳转前回调,允许操作栈,在当前跳转生效
  willShow: (
    from: NavDestinationContext | 'navBar', 
    to: NavDestinationContext | 'navBar',
    operation: NavigationOperation, 
    animated: boolean
  ) => {
    if (typeof to === 'string') {
      console.log('目标页面是导航主页');
      return;
    }
    // 将即将跳转到 PageTwo 的路由重定向到 PageOne
    let target: NavDestinationContext = to as NavDestinationContext;
    if (target.pathInfo.name === 'PageTwo') {
      target.pathStack.pop();
      target.pathStack.pushPathByName('PageOne', null);
    }
  },
  
  // 页面跳转后回调,在该回调中操作栈会在下一次跳转生效
  // didShow:() => void
  
  // Navigation单双栏显示状态发生变更时触发该回调
  // modeChange:() => void
})

跨包动态路由

通过动态 Import 的方式,让各个模块解耦,并提升首页加载时间长的问题 有系统路由表和自定义路由表两种方式

系统路由表
// 在跳转目标模块的配置文件module.json5添加路由表配置
{
  "module" : {
    "routerMap": "$profile:route_map"
  }
}

// 在工程 resources/base/profile 中创建route_map.json文件
{
  "routerMap": [
    {
      // 跳转页面名称
      "name": "PageOne",
      // 跳转目标页在包内的路径,相对src目录的相对路径
      "pageSourceFile": "src/main/ets/pages/PageOne.ets",
      // 跳转目标页的入口函数名称,必须以@Builder修饰
      "buildFunction": "PageOneBuilder",
      // 应用自定义字段。可以通过配置项读取接口getConfigInRouteMap获取
      "data": {
        "description" : "this is PageOne"
      }
   }
  ]
}

// 在跳转目标页面中,需要配置入口 Builder 函数
// 函数名称需要和 router_map.json 配置文件中的 buildFunction 保持一致
// 跳转页面入口函数
@Builder
export function PageOneBuilder() {
  PageOne()
}

@Component
struct PageOne {
  pathStack: NavPathStack = new NavPathStack()

  build() {
    NavDestination() {}
    .title('PageOne')
    .onReady((context: NavDestinationContext) => {
       this.pathStack = context.pathStack
    })
  }
}


Tabs

基础用法

Tabs({
  barPosition: BarPosition.Start
}){
  TabContent() {
    Text('首页的内容').fontSize(30)
  }
  .tabBar('首页')

  TabContent() {
    Text('我的内容').fontSize(30)
  }
  .tabBar('我的')
}
.vertical(true) // 垂直导航
.scrollable(false) // 关闭切换滑动效果
.animationDuration(0) // 动画效果时长为 0
.barMode(BarMode.Scrollable) // 开启导航栏滑动 (tab很多时使用)

自定义导航按钮

@Entry
@Preview
@Component
struct Index {
  @State currentIndex: number = 0

  // 自定义构建函数
  @Builder tabBuilder(title: string, targetIndex: number) {
    Column() {
      Text(title).fontColor(this.currentIndex === targetIndex ? '#1698CE' : '#6B6B6B')
    }
    .width('100%')
    .height(50)
    .justifyContent(FlexAlign.Center)
  }

  build() {
    Tabs({
      barPosition: BarPosition.End,
    }){
      TabContent() {
        Text('首页的内容').fontSize(30)
      }
      // 可以不传字符串,而是传 自定义构建函数
      .tabBar(this.tabBuilder('首页', 0)) 

      TabContent() {
        Text('我的的内容').fontSize(30)
      }
      // 可以不传字符串,而是传 自定义构建函数
      .tabBar(this.tabBuilder('我的', 1))
    }
    .onChange((index: number) => {
      this.currentIndex = index
    })
  }
}

外部按钮 切换到指定页签

@Entry
@Preview
@Component
struct Index {
  @State currentIndex: number = 0

  @Builder tabBuilder(title: string, targetIndex: number) {
    Column() {
      Text(title).fontColor(this.currentIndex === targetIndex ? '#1698CE' : '#6B6B6B')
    }
    .width('100%')
    .height(50)
    .justifyContent(FlexAlign.Center)
  }

  build() {
    Tabs({
      barPosition: BarPosition.End,
      index: this.currentIndex,
    }){
      TabContent() {
        Column(){
          Text('首页的内容').fontSize(30)
          Button('跳到我的').onClick(()=>{
            this.currentIndex = 1
          })
        }
      }
      .tabBar(this.tabBuilder('首页', 0))

      TabContent() {
        Text('我的的内容').fontSize(30)
      }
      .tabBar(this.tabBuilder('我的', 1))
    }
    .onChange((index: number) => {
      this.currentIndex = index
    })
  }
}