普通视图

发现新文章,点击刷新页面。
昨天以前首页

HarmonyOS 帧动画 animator

作者 IT充电站
2025年11月27日 13:25

就是类似播放电影一样,一帧一帧的进行播放,相对于属性动画,其每一帧,我们都可以进行设置相关的属性值,并且具有暂停播放,继续播放的优点,而且还具备事件的实时响应,需要说明的是,在性能上是远远不如属性动画的,所以如果能用属性动画实现的场景,还是主推属性动画

  • 需要控制动画播放暂停:音乐播放、或者运动小车
  • 翻页动画

基础使用

  • 创建帧动画 this.animator = Animator.create()

  • 每一帧回调 this.animator.onFrame=fn

  • 控制 this.animator.play/pause()

示例代码1
import { Animator, AnimatorResult } from '@kit.ArkUI'

@Entry
@Component
struct Index {
  // 1. 创建帧动画
  @State animator:AnimatorResult  = Animator.create({
    duration: 3000, //动画播放的时长
    delay: 0, //动画延时播放时长
    easing: 'linear', //动画插值曲线
    iterations: 1, //动画播放次数
    fill: "forwards", //动画执行后是否恢复到初始状态
    direction: 'normal', //动画播放模式
    begin: 0, //动画插值起点
    end: 100//动画插值终点
  })

  @State translateX: number = 0
  @State translateY: number = 0

  aboutToAppear(): void {
    // 2.每一帧回调
    this.animator.onFrame = (progress: number) => {
      this.translateX = progress
    }
  }

  build() {
    Column({space:20}) {

      Text("1").width(30).height(30).backgroundColor(Color.Red).textAlign(TextAlign.Center).margin({ top: 100 })
        .translate({ x: this.translateX, y: this.translateY })

      Button("播放").onClick(() => this.animator.play() )
      Button("暂停").onClick(() =>  this.animator.pause() )
      Button("重置").onClick(() => {
        this.translateX = 0
        this.translateY = 0
      })
    }
    .height('100%')
    .width('100%')
  }
}

示例代码2:实战音乐播放器

import { Animator, AnimatorResult } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  @State angle: number = 0

  // 1 创建帧动画对象
  private animator: AnimatorResult = Animator.create({
    duration: 10000,  //   持续时间
    delay: 0,         //   延迟时间
    easing: "linear", //   动画曲线
    iterations: -1,   //   播放次数
    fill: "none",     //   播放模式 播放之外的状态
    direction: "normal", //   播放方向
    begin: 0,   // 开始角度
    end: 360 // 结束角度
  })

  aboutToAppear() {
    //   2 监听帧变化事件
    this.animator.onFrame = (value) => {
      this.angle = value
    }
  }

  build() {
    Column() {
      Button('开始').onClick((event: ClickEvent) => {
        this.animator.play()
      })
      Button('暂停').onClick((event: ClickEvent) => {
        this.animator.pause()
      })
      Stack() {
        Image("https://p6.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/28513831157/e8e9/beeb/912c/468bcb523668065ccf6f853c4a084e31.png")
          .width(300)
          .height(300)

        Image("https://p1.music.126.net/MX4wNwR9eJNvx_Y5AKBEFw==/109951169909770184.jpg?imageView&thumbnail=360y360&quality=75&tostatic=0")
          .width(200)
          .height(200)
          .borderRadius(200)
          .rotate({
            angle: this.angle
          })

      }.backgroundColor(Color.Gray).width('100%').height('100%')
    }
  }
}

示例代码3:图片帧动画

developer.huawei.com/consumer/cn…

www.jq22.com/yanshi24013

www.jq22.com/demo/imgAni…

鸿蒙开发班级

HarmonyOS 组件导航(Navigation)

作者 IT充电站
2025年11月27日 13:22

✨家人们记得点个账号关注,会持续发布大前端领域技术文章💕 🍃

组件导航(Navigation)

Navigation是路由容器组件,一般作为首页的根容器,包括单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式。Navigation组件适用于模块内和跨模块的路由切换,一次开发,多端部署场景。通过组件级路由能力实现更加自然流畅的转场体验,并提供多种标题栏样式来呈现更好的标题和内容联动效果。在不同尺寸的设备上,Navigation组件能够自适应显示大小,自动切换分栏展示效果。

  • 模块内、和跨模块实现路由切换
  • 一次开发,多端部署场景
  • 通过组件级路由能力实现更加自然流畅的转场体验
  • 更好的标题、tabBar等等联动效果
  • 等等

2.1 Navigation属性

@Entry
@Component
struct Index {
  build() {
    Navigation() {

    }
      .title("主标题")
      .mode()
      .toolbarConfiguration()
      .menus()
  }
}

title 设置标题

显示在这个主标题的位置

titleMode 标题栏模式

标题栏在界面顶部,用于呈现界面名称和操作入口,Navigation组件通过titleMode属性设置标题栏模式

  • Mini模式:普通型标题栏,用于一级页面不需要突出标题的场景

img

  • Full模式:强调型标题栏,用于一级页面需要突出标题的场景。

img

mode 设置模式

Navigation是路由容器组件,一般作为首页的根容器,包括单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式.Navigation组件适用于模块内和跨模块的路由切换,一次开发,多端部署场景

Navigation组件通过mode属性设置页面的显示模式。自适应模式下,当页面宽度大于等于一定阈值( API version 9及以前:520vp,API version 10及以后:600vp )时,Navigation组件采用分栏模式,反之采用单栏模式。

img

将mode属性设置为NavigationMode.Stack,Navigation组件即可设置为单页面显示模式。

img

将mode属性设置为NavigationMode.Split,Navigation组件即可设置为分页面显示模式。

toolbarConfiguration 底层标题栏

.toolbarConfiguration() 里面设置的是一个数组,数组里面每个元素ToolbarItem类型,数据的结构如下

{
    value: "我的",
    icon: "https://s1.aigei.com/src/img/png/e7/e79cacc5161a4a6ca589e097f87a2526.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:TLXitIEF_QaGqfeKVbpaGb8_qyQ=",
    action: () => {

    },
    activeIcon: "",
    status: ToolbarItemStatus.DISABLED,

}

value 就是显示的文字

icon 显示的图标

action 当点击时触发做某些事情,例如跳转路由

activeIcon 激活时的图标

status 当前图标的状态,例如不可用等等

img

menus 菜单栏

菜单栏位于Navigation组件的右上角,开发者可以通过menus属性进行设置。menus支持Array和CustomBuilder两种参数类型。使用Array类型时,竖屏最多支持显示3个图标,横屏最多支持显示5个图标,多余的图标会被放入自动生成的更多图标。

img

{
    value: "我的",
    icon: "https://s1.aigei.com/src/img/png/e7/e79cacc5161a4a6ca589e097f87a2526.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:TLXitIEF_QaGqfeKVbpaGb8_qyQ=",
    action: () => {

    },
   isEnabled:false

}

value 就是显示的文字

icon 显示的图标

action 当点击时触发做某些事情,例如跳转路由

isEnabled 是否可用

  • 示例代码(全部)
@Entry
@Component
struct Index {
  build() {
    Navigation() {

    }
    .title("主标题")
      .mode(NavigationMode.Stack)
      .titleMode(NavigationTitleMode.Free)
      .toolbarConfiguration([
        {
          value: "我的",
          icon: "https://s1.aigei.com/src/img/png/e7/e79cacc5161a4a6ca589e097f87a2526.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:TLXitIEF_QaGqfeKVbpaGb8_qyQ=",
          action: () => {

          },

        },
        {
          value: "购物车",
          icon: "https://s1.aigei.com/src/img/png/5d/5dc2f07ba8224cd4883648c9ea939ac5.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:Q5dD_6ZS_ry4kaVXHgv6gnKw2IQ=",
          action: () => {

          }
        },
        {
          value: "更多",
          icon: "https://s1.aigei.com/src/img/png/03/03d71ede7b404e70838f3d575b31a930.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:ABookBJ6PVBeSxtM2hEuQHPlGLE=",
          action: () => {

          }
        }
      ])
      .menus([
        {
          value: "设置",
          icon: "https://s1.4sai.com/src/img/png/6c/6ca77e26c31548e680b784ab33f72900.png?e=1735488000&token=1srnZGLKZ0Aqlz6dk7yF4SkiYf4eP-YrEOdM1sob:9x9bgI9kDs15UsWPzGN7ZuaICXM=",
          action: () => {

          },
        },
        {
          value: "小红书",
          icon: "https://s1.aigei.com/src/img/png/3e/3e3bda0ac48046b08e560e8764942bd8.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:JPuRl9bkD4UfIc8fGN4ApLuS_rE=",
          action: () => {

          }
        },
        {
          value: "微信",
          icon: "https://s1.aigei.com/src/img/png/ef/efd714270fdd44f485156053901ecb51.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:GfcdaqAj6Uie-yCw4Wt1pPqAGwg=",
          action: () => {

          }
        },
        {
          value: "抖音",
          icon: "https://s1.aigei.com/src/img/png/5b/5b26e982f0b34c47817d3b40c9bf2d1f.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:YCO6IvUtFIqf6x1hmy82VctIElo=",
          action: () => {

          }
        }
      ])
  }
}

2.2 NavRouter

导航组件,默认提供点击响应处理,不需要开发者自定义点击事件逻辑。

必须包含两个子组件,其中第二个子组件必须为NavDestination。

其中第一个组件是需要点击的元素

第二个必须是NavDestination

@Entry
@Component
struct Index {
  build() {
    Navigation() {

      // NavRouter() {
      //   Text('菜单1')
      //   NavDestination() {
      //     Text('内容1')
      //   }
      // }
      //
      //
      // NavRouter() {
      //   Text('菜单2')
      //   NavDestination() {
      //     Text('内容2')
      //   }
      // }

      ForEach('123'.split(''), (item: number) => {
        NavRouter() {
          // 这里是点击的内容
          Column() {
            Text("文本" + item)
              .width("85%")
              .height(60)
              .backgroundColor("#ccc")
              .borderRadius(25)
              .margin({ top: 10 })
              .textAlign(TextAlign.Center)
          }
          // 这里是跳转显示的页面
          NavDestination() {
            Text("文本" + item).fontSize(30).fontColor("red")
          }.title("标题" + item).hideTitleBar(false)
        }.mode(NavRouteMode.PUSH)
      })


    }.mode(NavigationMode.Split)
  }
}

这里有一个属性

mode(mode: NavRouteMode)

设置指定点击NavRouter跳转到NavDestination页面时,使用的路由模式

PUSH_WITH_RECREATE 跳转到新的NavDestination页面时,替换当前显示的NavDestination页面,页面销毁,但该页面信息仍保留在路由栈中。

PUSH 跳转到新的NavDestination页面时,覆盖当前显示的NavDestination页面,该页面不销毁,且页面信息保留在路由栈中。

REPLACE 跳转到新的NavDestination页面时,替换当前显示的NavDestination页面,页面销毁,且该页面信息从路由栈中清除。

还有一个事件

onStateChange(callback: (isActivated: boolean) => void)

组件激活状态切换时触发该回调。开发者点击激活NavRouter,加载对应的NavDestination子组件时,回调onStateChange(true)。NavRouter对应的NavDestination子组件不再显示时,回调onStateChange(false)。

最后效果

@Entry
  @Component
  struct Index {
    pageStack: NavPathStack = new NavPathStack();
    @State list: Array<number> = [1, 2, 3]

    build() {
      Navigation(this.pageStack) {
        ForEach(this.list, (item: number) => {
          NavRouter() {
            Column() {
              Text("文本" + item)
                .width("85%")
                .height(60)
                .backgroundColor("#ccc")
                .borderRadius(25)
                .margin({ top: 10 })
                .textAlign(TextAlign.Center)
            }

            NavDestination() {
              Text("文本" + item)
                .fontSize(30)
                .fontColor("red")
            }.title("标题" + item)

          }.mode(NavRouteMode.PUSH)
                .onStateChange((isActivated: boolean) => {
                   console.log(item + "激活:" + isActivated)
          })
        })
      }
      .title("主标题")
        .mode(NavigationMode.Stack)
        .titleMode(NavigationTitleMode.Free)
        .toolbarConfiguration([
          {
            value: "我的",
            icon: "https://s1.aigei.com/src/img/png/e7/e79cacc5161a4a6ca589e097f87a2526.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:TLXitIEF_QaGqfeKVbpaGb8_qyQ=",
            action: () => {
              this.pageStack.pushPath({ name: "pageOne", param: "aaa" })
            },

          },
          {
            value: "购物车",
            icon: "https://s1.aigei.com/src/img/png/5d/5dc2f07ba8224cd4883648c9ea939ac5.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:Q5dD_6ZS_ry4kaVXHgv6gnKw2IQ=",
            action: () => {

            }
          },
          {
            value: "更多",
            icon: "https://s1.aigei.com/src/img/png/03/03d71ede7b404e70838f3d575b31a930.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:ABookBJ6PVBeSxtM2hEuQHPlGLE=",
            action: () => {

            }
          }
        ])
        .menus([
          {
            value: "设置",
            icon: "https://s1.4sai.com/src/img/png/6c/6ca77e26c31548e680b784ab33f72900.png?e=1735488000&token=1srnZGLKZ0Aqlz6dk7yF4SkiYf4eP-YrEOdM1sob:9x9bgI9kDs15UsWPzGN7ZuaICXM=",
            action: () => {

            },
          },
          {
            value: "小红书",
            icon: "https://s1.aigei.com/src/img/png/3e/3e3bda0ac48046b08e560e8764942bd8.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:JPuRl9bkD4UfIc8fGN4ApLuS_rE=",
            action: () => {

            }
          },
          {
            value: "微信",
            icon: "https://s1.aigei.com/src/img/png/ef/efd714270fdd44f485156053901ecb51.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:GfcdaqAj6Uie-yCw4Wt1pPqAGwg=",
        action: () => {

        }
      },
      {
        value: "抖音",
        icon: "https://s1.aigei.com/src/img/png/5b/5b26e982f0b34c47817d3b40c9bf2d1f.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:YCO6IvUtFIqf6x1hmy82VctIElo=",
        action: () => {

        }
      }
    ])
  }
}

2.3 独立的NavDestination

切记独立的NavDestination不能通过NavRouter跳转 它也被弃用了,只能通过 NavPathStack创建一个路由栈对象,

然后通过路由栈对象控制页面跳转

具体实现

1、Profile.ets 把页面内容NavDestination独立出去

@Component
export struct Profile {
  build() {
    NavDestination() { // 根元素必须是NavDestination
      Column() {
        Text("实际的内容")
      }
    }
    .title("标题")
  }
}

2、Navigation通过属性关联Profile页面

import {Profile} from './Profile'

@Entry
@Component
export struct Index {

  @Builder PagesMap(name: string) {
    if (name === "Profile") {
      Profile()
    }
  }

  pageStack: NavPathStack = new NavPathStack()  // 页面栈对象 管理页面栈里面的网页

  build() {
    Navigation(this.pageStack) {
      Button('跳转到Cart页面').onClick(() => {
        this.pageStack.pushPathByName('Profile', null)
        // this.pageStack.pushPath({ name: "Profile", param: "实际需要显示的内容" })
      })
    }
    .title("主标题")
    .mode(NavigationMode.Stack)
    .navDestination(this.PagesMap) // 通过属性的方式动态设置
  }
}

3、控制跳转(之前没有独立NavRouter 独立之后必须通过Navigation提供的页面栈对象来操作

3.1 创建页面栈

pageStack: NavPathStack = new NavPathStack(); // 创建页面栈实例化对象  控制页面栈路由增删改等
 
3.2 注册关联
Navigation(this.pageStack) {

3.3  跳转
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-navigation-navigation-V5#%E8%B7%AF%E7%94%B1%E6%93%8D%E4%BD%9C

NavDestination的模式

NavDestination的mode属性有两种值

标准类型 NavDestinationMode.STANDARD

标准类型的NavDestination的生命周期跟随其在NavPathStack页面栈中的位置变化而改变。

弹窗类型 NavDestinationMode.DIALOG

整个NavDestination默认透明显示。弹窗类型的NavDestination显示和消失时不会影响下层标准类型的NavDestination的显示和生命周期,两者可以同时显示。

下面处理一个弹窗问题

@Component
export  struct DialogPage {
  @Consume pageStack: NavPathStack;
  param: string = ""

  build() {
    NavDestination() {
      Column() {
        Stack({ alignContent: Alignment.TopEnd }) {
          Text(this.param)
            .fontSize(30)
            .fontColor("red")
            .textAlign(TextAlign.Center)
            .width("100%")
            .margin({ top: 80 })
          Button("×").onClick(() => {
            this.pageStack.pop();
          })
        }.width("70%")
          .height("30%")
          .backgroundColor("white")
          .borderRadius(30)
      }.justifyContent(FlexAlign.Center)
        .alignItems(HorizontalAlign.Center)
        .width("100%")
        .height("100%")
    }
    .backgroundColor("rgba(0,0,0,0.5)")
      .mode(NavDestinationMode.DIALOG)
      .title("标题")
      .hideTitleBar(true)
  }
}
import { DialogPage } from '../components/DialogPage';


@Entry
  @Component
  struct Index {
    @Provide pageStack: NavPathStack = new NavPathStack();

    @Builder
    PagesMap(name: string, param: string) {
      if (name === "dialogPage") {
        DialogPage({ param: param })
      }
    }

    build() {
      Navigation(this.pageStack) {

      }
      .title("主标题")
        .mode(NavigationMode.Stack)
        .titleMode(NavigationTitleMode.Free)
        .navDestination(this.PagesMap)
        .toolbarConfiguration([
          {
            value: "我的",
            icon: "https://s1.aigei.com/src/img/png/e7/e79cacc5161a4a6ca589e097f87a2526.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:TLXitIEF_QaGqfeKVbpaGb8_qyQ=",
            action: () => {
              this.pageStack.pushPath({ name: "dialogPage", param: "aaa" })
            },

          },
          {
            value: "购物车",
            icon: "https://s1.aigei.com/src/img/png/5d/5dc2f07ba8224cd4883648c9ea939ac5.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:Q5dD_6ZS_ry4kaVXHgv6gnKw2IQ=",
            action: () => {

            }
          },
          {
            value: "更多",
            icon: "https://s1.aigei.com/src/img/png/03/03d71ede7b404e70838f3d575b31a930.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:ABookBJ6PVBeSxtM2hEuQHPlGLE=",
            action: () => {

            }
          }
        ])
    }
  }

img

2.4 NavPathStack

NavPathStrack是页面栈为路由跳转提供的方法对象,每个Navigation都需要创建并传入一个NavPathStack对象,用于管理页面。主要涉及页面跳转、页面返回、页面替换、页面删除、参数获取、路由拦截等功能。

pageStack: NavPathStack = new NavPathStack()

普通跳转

this.pageStack.pushPath({ name: "DialogPage", param: "aaa" })
this.pageStack.pushPathByName("DialogPage", "aaa")

这两种方法都可以使用

带返回回调的跳转,跳转时添加onPop回调,能在页面出栈时获取返回信息

this.pageStack.pushPath({
  name: "DialogPage", param: "aaa", onPop: (popInfo) => {
    Logger.log(popInfo.result)
  }
})

关闭退回时通过pop传回的数据

Button("×").onClick(() => {
  this.pageStack.pop("返回的内容")
})

页面返回

// 返回到上一页
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")

路由拦截

在页面的初始生命周期中设置

aboutToAppear(): void {
  this.pageStack.setInterception({
  willShow: (from: NavDestinationContext | NavBar, to: NavDestinationContext | NavBar,
       operation: NavigationOperation, isAnimated: boolean) => {
         if (typeof to === "string") return; 
         if (to.pathInfo.name === "dialogPage") {
           to.pathStack.pop();
           to.pathStack.pushPath({ name: "page1", param: "bbbb" })
         }
       }
  })
}

2.5 跨包动态路由-系统路由表

准备生成Navigation的route_map路由

a)给模块创建系统路由表 (也就是针对于Navigation以后还是通过配置文件生成路由)

创建resources/base/profile/route_map.json(切记route不加r)路由配置文件,填写下述代码

{
  "routerMap": [
    {
      # 路由名  也就是跳转名
      "name": "Home或者Profile",    
      # 改名字对应的页面 
      "pageSourceFile": "src/main/ets/pages/Profile.ets",     
      
      # 切记MainPage.ets这个文件必须用MainPageBuilder当做入口(比较抽象跟着写  写完反过来看才能理解)
      "buildFunction": "MainPageBuilder",    
      "data": {
        "description" : "this is PageOne"
      }
    }
  ]
}

b)让创建route_map.json生效 也就是跟模块绑定

修改模块的module.json5文件 "routerMap": "$profile:route_map",

{
  "module": {
  
    "routerMap": "$profile:route_map",
    
    "name": "cartHar",
    "type": "har",
    "deviceTypes": [
      "default",
      "tablet",
      "2in1"
    ]
  }
}

后续创建页面跳转

a)必须按照下述格式创建页面

按照系统路由表需要的组件格式修改组件 `src/main/ets/components/MainPage.ets`

```plain
// 跳转页面入口函数
@Builder
export function MainPageBuilder() {  // 细节2:按照这个格式配置
  MainPage()
}


@Component
struct MainPage { // 细节1.1:不用导出  但是必须按照上述配置

  build() {
    NavDestination() { // 细节1.2:必须通过NavDestination写
      Text('内容').fontSize(30)
    }
    .title('CartHar/MainPage')
    .hideTitleBar(false)
  }
}
```


b)通过NavPathStack跳转

2.6 跨包动态路由-自定义路由表 HMRouter

千锋生鲜项目

2.7 企业级面试题

  • Navigation 和 Router 区别
1 路由有闪屏的问题、Navigation更流畅
2 Navigation更适合一次开发、多端适配
3 Navigation页面转场动画更方便
4 跨模块开发Navigation更方便
等等
  • 如何用的
传统:1-创建页面、2-配置路由main_pages、3-router跳转
现在:
- 准备:Navigation动态路由-系统路由表配置:1-创建route_map.json写配置、2-注册把1的文件跟项目关联module.json5、3-入口文件Navigation配置
- 后续1:创建页面  必须按照系统路由规则去创建 (必须写NavDestination 然后不写导出但是必须被@Builder调用)
- 后续2:配置该页面路由就可以跳转

鸿蒙开发班级

✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃

✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃

✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃

    ^_^ 点关注、不迷路、主播带你学技术 (๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤

HarmonyOS 位置服务全攻略:精准定位、地理编码与后台持续定位实现

作者 IT充电站
2025年11月27日 13:21

✨家人们记得点个账号关注,会持续发布大前端领域技术文章💕 🍃

在 HarmonyOS 应用开发中,位置服务是许多场景的核心能力 —— 无论是本地生活服务的附近推荐、导航应用的实时轨迹追踪,还是出行类 App 的后台定位,都离不开稳定、精准的位置获取能力。本文将从权限配置、位置获取、地理编码转换、持续定位到后台长时定位,一步步拆解 HarmonyOS 位置服务的完整实现流程,附带可直接复用的代码示例,助力开发者快速落地功能。

获取设备的位置信息

1-配置申请位置权限 同时申请ohos.permission.APPROXIMATELY_LOCATION和ohos.permission.LOCATION 获取到精准位置,精准度在米级别。

2-检查全局位置开关是否打开 geoLocationManager.isLocationEnabled();

  • 2.1 没开 atManager.requestGlobalSwitch(getContext(this), abilityAccessCtrl.SwitchType.LOCATION) 拉起全局设置位置开关页
  • 2.2 开了 继续

3-获取当前位置 geoLocationManager.getCurrentLocation(request) 可以获取用户经度纬度

4-可以继续通过geoLocationManager.getAddressesFromLocation()地理编码转化为可读性较强地址

  • 获取设备的位置信息,需要有位置权限,位置权限申请
// 位置信息(权限组)
{
  "name": "ohos.permission.APPROXIMATELY_LOCATION",
  "reason": '$string:permission_reason_location',
  "usedScene": {}
},
{
  "name": "ohos.permission.LOCATION",
  "reason": '$string:permission_reason_location',
  "usedScene": {}
},

按照步骤申请权限 developer.huawei.com/consumer/cn…

import { geoLocationManager } from '@kit.LocationKit';
import { abilityAccessCtrl, Context, common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { permissionUtil } from '../utils/PermissionUtil';

@Entry
@Component
struct Index {
  build() {
    Button('获取位置信息').onClick(async () => {
      // 1 配置文件 申请权限   同时申请ohos.permission.APPROXIMATELY_LOCATION和ohos.permission.LOCATION 获取到精准位置,精准度在米级别。
      const state = await permissionUtil.checkPermissions(['ohos.permission.APPROXIMATELY_LOCATION', 'ohos.permission.LOCATION'], getContext());
      if (!state) return

      // 2 检查全局开关  开了-继续走,没开-弹出让他授权
      const locationEnabled = geoLocationManager.isLocationEnabled();
      console.log('查看当前全局开关状态:', locationEnabled)

      if (!locationEnabled) {  // 没开
        const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
        const state = await atManager.requestGlobalSwitch(getContext(), abilityAccessCtrl.SwitchType.LOCATION)
        if (!state) return  // 拉起全局弹出 用户没授权就终止代码执行
      }

      // 3. 获取位置信息  经度纬度
      geoLocationManager.getCurrentLocation( {
        locatingPriority: geoLocationManager.LocatingPriority.PRIORITY_LOCATING_SPEED,
        locatingTimeoutMs: 10000
      }).then((result) => { // 调用getCurrentLocation获取当前设备位置,通过promise接收上报的位置
        console.log('current location: ' + JSON.stringify(result));
      })
        .catch((error:BusinessError) => { // 接收上报的错误码
          console.error('promise, getCurrentLocation: error=' + JSON.stringify(error));
        });
      // .....
    })
  }
}

地理编码转化与逆地理编码转化

使用坐标描述一个位置,非常准确,但是并不直观,面向用户表达并不友好。系统向开发者提供了以下两种转化能力。

  • 地理编码转化:将地理描述转化为具体坐标。
  • 逆地理编码转化能力:将坐标转化为地理描述。

其中地理编码包含多个属性来描述位置,包括国家、行政区划、街道、门牌号、地址描述等等,这样的信息更便于用户理解。

开发步骤

  1. 导入geoLocationManager模块,所有与地理编码转化&逆地理编码转化能力相关的功能API,都是通过该模块提供的。
import { geoLocationManager } from '@kit.LocationKit';

2.获取转化结果。

调用getAddressesFromLocation,把坐标转化为地理位置信息。应用可以获得与此坐标匹配的GeoAddress(地理编码地址信息)列表,应用可以根据实际使用需求,读取相应的参数数据。

let reverseGeocodeRequest:geoLocationManager.ReverseGeoCodeRequest = {
  locale: 'zh',
  "latitude": 31.12, "longitude": 121.11, "maxItems": 1
};
try {
    geoLocationManager.getAddressesFromLocation(reverseGeocodeRequest, (err, data) => {
        if (err) {
            console.log('getAddressesFromLocation err: ' + JSON.stringify(err));
        } else {
            console.log('getAddressesFromLocation data: ' + JSON.stringify(data));
        }
    });
} catch (err) {
    console.error("errCode:" + JSON.stringify(err));
}

3.示例代码

import { geoLocationManager } from '@kit.LocationKit';
import { abilityAccessCtrl, Context, common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { permissionUtil } from '../utils/PermissionUtil';

@Entry
@Component
struct Index {
  build() {
    Button('获取位置信息').onClick(async () => {
      // 1 配置文件 申请权限   同时申请ohos.permission.APPROXIMATELY_LOCATION和ohos.permission.LOCATION 获取到精准位置,精准度在米级别。
      const state = await permissionUtil.checkPermissions(['ohos.permission.APPROXIMATELY_LOCATION', 'ohos.permission.LOCATION'], getContext());
      if (!state) return

      // 2 检查全局开关  开了-继续走,没开-弹出让他授权
      const locationEnabled = geoLocationManager.isLocationEnabled();
      console.log('查看当前全局开关状态:', locationEnabled)

      if (!locationEnabled) {  // 没开
        const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
        const state = await atManager.requestGlobalSwitch(getContext(), abilityAccessCtrl.SwitchType.LOCATION)
        if (!state) return  // 拉起全局弹出 用户没授权就终止代码执行
      }

      // 3. 获取位置信息  经度纬度
      geoLocationManager.getCurrentLocation( {
        locatingPriority: geoLocationManager.LocatingPriority.PRIORITY_LOCATING_SPEED,
        locatingTimeoutMs: 10000
      }).then((result) => { // 调用getCurrentLocation获取当前设备位置,通过promise接收上报的位置
        console.log('current location: ' + JSON.stringify(result))

        // 正地理编码与逆地理编码
        geoLocationManager.getAddressesFromLocation({"latitude": result.latitude, "longitude": result.longitude, "maxItems": 1}, (err, data) => {
          if (err) {
            console.log('getAddressesFromLocation err: ' + JSON.stringify(err));
          } else {
            console.log('getAddressesFromLocation data: ' + JSON.stringify(data));
          }
        });
        // 正地理编码与逆地理编码 end
      })
        .catch((error:BusinessError) => { // 接收上报的错误码
          console.error('promise, getCurrentLocation: error=' + JSON.stringify(error));
        });
      // .....
    })
  }
}

持续定位

持续定位。多用于导航、运动轨迹、出行等场景。

首先要实例化ContinuousLocationRequest对象,用于告知系统该向应用提供何种类型的位置服务,以及位置结果上报的频率。

  • 设置locationScenario:

建议locationScenario参数优先根据应用的使用场景进行设置,该参数枚举值定义参见UserActivityScenario,例如地图在导航时使用NAVIGATION参数,可以持续在室内和室外场景获取位置用于导航。

  • 设置interval:

表示上报位置信息的时间间隔,单位是秒,默认值为1秒。如果对位置上报时间间隔无特殊要求,可以不填写该字段。

以地图导航场景为例,调用方式如下:

import { geoLocationManager } from '@kit.LocationKit';
let request: geoLocationManager.ContinuousLocationRequest= {
   'interval': 1,
   'locationScenario': geoLocationManager.UserActivityScenario.NAVIGATION
}
let locationCallback = (location:geoLocationManager.Location):void => {
   console.log('locationCallback: data: ' + JSON.stringify(location));
};
try {
   geoLocationManager.on('locationChange', request, locationCallback);
} catch (err) {
   console.error("errCode:" + JSON.stringify(err));
}

如果不主动结束定位可能导致设备功耗高,耗电快;建议在不需要获取定位信息时及时结束定位。

geoLocationManager.off('locationChange', locationCallback);

完整实例代码

import { geoLocationManager } from '@kit.LocationKit';
import { abilityAccessCtrl } from '@kit.AbilityKit';
import { permissionUtil } from '../utils/PermissionUtil';

@Entry
@Component
struct Index {

  locationCallback(location:geoLocationManager.Location)  {
    console.log('locationCallback: data: ' + JSON.stringify(location));
  }

  aboutToDisappear() {
    geoLocationManager.off('locationChange', this.locationCallback);
  }

  build() {
    Button('获取位置信息').onClick(async () => {
      // 1 配置文件 申请权限   同时申请ohos.permission.APPROXIMATELY_LOCATION和ohos.permission.LOCATION 获取到精准位置,精准度在米级别。
      const state = await permissionUtil.checkPermissions(['ohos.permission.APPROXIMATELY_LOCATION', 'ohos.permission.LOCATION'], getContext());
      if (!state) return

      // 2 检查全局开关  开了-继续走,没开-弹出让他授权
      const locationEnabled = geoLocationManager.isLocationEnabled();
      console.log('查看当前全局开关状态:', locationEnabled)

      if (!locationEnabled) {  // 没开
        const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
        const state = await atManager.requestGlobalSwitch(getContext(), abilityAccessCtrl.SwitchType.LOCATION)
        if (!state) return  // 拉起全局弹出 用户没授权就终止代码执行
      }

      // 3. 获取位置信息  经度纬度 【持续定位。多用于导航、运动轨迹、出行等场景。】
      geoLocationManager.on('locationChange', {
        interval: 1,
        locationScenario: geoLocationManager.UserActivityScenario.NAVIGATION
      }, this.locationCallback);

      // .....
    })
  }
}

后台定位-长时任务

应用退至后台后,在后台需要长时间运行用户可感知的任务,如播放音乐、导航等。为防止应用进程被挂起,导致对应功能异常,可以申请长时任务,使应用在后台长时间运行。在长时任务中可以申请多种类型的任务,并对任务类型进行更新。应用退后台执行业务时,系统会做一致性校验,确保应用在执行相应的长时任务。同时,系统有与长时任务相关联的通知栏消息,用户删除通知栏消息时,系统会自动停止长时任务。

1.需要申请ohos.permission.KEEP_BACKGROUND_RUNNING权限,配置方式请参见声明权限

2.声明后台模式类型。

在module.json5配置文件中为需要使用长时任务的UIAbility声明相应的长时任务类型(配置文件中填写长时任务类型的配置项)。

 "module": {
     "abilities": [
         {
        "name": "EntryAbility",
           // ...
              // ...
              // 
              "backgroundModes": [
              // 长时任务类型的配置项
             "audioRecording"
               或者
          "location"]
         }
     ],
     ...
 }
  1. 创建BackgroundTaskUtil.ets文件封装长短时任务
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common, WantAgent, wantAgent } from '@kit.AbilityKit';

class BackgroundTaskUtil {

  // 长任务-开启
  startLongTask(list:string[], callback: () => void) {  // list任务类型, callback开启成功后走的回调
    let wantAgentInfo: wantAgent.WantAgentInfo = {
      // 点击通知后,将要执行的动作列表
      // 添加需要被拉起应用的bundleName和abilityName
      wants: [
        {
          bundleName: (getContext() as common.UIAbilityContext).abilityInfo.bundleName,
          abilityName: "EntryAbility"
        }
      ],
      // 指定点击通知栏消息后的动作是拉起ability
      actionType: wantAgent.OperationType.START_ABILITY,
      // 使用者自定义的一个私有值
      requestCode: 0,
      // 点击通知后,动作执行属性
      actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG],
      // 车钥匙长时任务子类型。只有申请bluetoothInteraction类型的长时任务,车钥匙子类型才能生效。
      // 确保extraInfo参数中的Key值为backgroundTaskManager.BackgroundModeType.SUB_MODE,否则子类型不生效。
      // extraInfo: { [backgroundTaskManager.BackgroundModeType.SUB_MODE] : backgroundTaskManager.BackgroundSubMode.CAR_KEY }
    };

    try {
      // 通过wantAgent模块下getWantAgent方法获取WantAgent对象
      wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj: WantAgent) => {
        try {
          // let list: Array<string> = ["audioRecording"];
          // let list: Array<string> = ["bluetoothInteraction"]; 长时任务类型包含bluetoothInteraction,CAR_KEY子类型合法
          backgroundTaskManager.startBackgroundRunning(getContext(), list, wantAgentObj).then((res: backgroundTaskManager.ContinuousTaskNotification) => {
            console.info("Operation startBackgroundRunning succeeded");
            // 此处执行具体的长时任务逻辑,如录音,录制等。
            // 写尝试任务
            callback()
          }).catch((error: BusinessError) => {
            console.error(`Failed to Operation startBackgroundRunning. code is ${error.code} message is ${error.message}`);
          });
        } catch (error) {
          console.error(`Failed to Operation startBackgroundRunning. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
        }
      });
    } catch (error) {
      console.error(`Failed to Operation getWantAgent. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
    }

  }
  // 长任务-关闭
  stopLongTask() {
    backgroundTaskManager.stopBackgroundRunning(getContext()).then(() => {
      console.info(`Succeeded in operationing stopBackgroundRunning.`);
    }).catch((err: BusinessError) => {
      console.error(`Failed to operation stopBackgroundRunning. Code is ${err.code}, message is ${err.message}`);
    });
  }

  // 短任务-开启
  // 短任务-关闭
}

export  const backgroundTaskUtil = new BackgroundTaskUtil()
  • 示例代码
import { geoLocationManager } from '@kit.LocationKit';
import { abilityAccessCtrl } from '@kit.AbilityKit';
import { permissionUtil } from '../utils/PermissionUtil';
import { backgroundTaskUtil } from '../utils/BackgroundTaskUtil';

@Entry
@Component
struct Index {

  locationCallback(location:geoLocationManager.Location)  {
    console.log('locationCallback: data: ' + JSON.stringify(location));
  }

  aboutToDisappear() {
    geoLocationManager.off('locationChange', this.locationCallback);
  }

  build() {
    Button('获取位置信息').onClick(async () => {
      // 1 配置文件 申请权限   同时申请ohos.permission.APPROXIMATELY_LOCATION和ohos.permission.LOCATION 获取到精准位置,精准度在米级别。
      const state = await permissionUtil.checkPermissions(['ohos.permission.APPROXIMATELY_LOCATION', 'ohos.permission.LOCATION'], getContext());
      if (!state) return

      // 2 检查全局开关  开了-继续走,没开-弹出让他授权
      const locationEnabled = geoLocationManager.isLocationEnabled();
      console.log('查看当前全局开关状态:', locationEnabled)

      if (!locationEnabled) {  // 没开
        const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
        const state = await atManager.requestGlobalSwitch(getContext(), abilityAccessCtrl.SwitchType.LOCATION)
        if (!state) return  // 拉起全局弹出 用户没授权就终止代码执行
      }

      // 3. 获取位置信息  经度纬度 【持续定位。多用于导航、运动轨迹、出行等场景。】
      backgroundTaskUtil.startLongTask(['location'], () => {
        geoLocationManager.on('locationChange', {
          interval: 1,
          locationScenario: geoLocationManager.UserActivityScenario.NAVIGATION
        }, this.locationCallback);
      })
      // .....
    })
  }
}

鸿蒙开发者班级

✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃

✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃

✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃

    ^_^ 点关注、不迷路、主播带你学技术 (๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤

❌
❌