鸿蒙开发常用点小记

775 阅读6分钟

一、UI

1、底部菜单栏

Tabs({ barPosition: BarPosition.End }){
  ForEach(this.tabbars,(item: TabbarItem,index: number) =>{
    TabContent(){
      if(index == 0){
        MainHome()
      } else {
        MineHome()
      }
    }
    .tabBar(this.TabBuilder(item,index))
  })
}
.scrollable(false)
.animationDuration(0)
.width('100%')
.height('100%')
.onChange((index: number)=>{
  this.currentIndex = index
})

Tabs 组件实现底部菜单栏切换。

TabContent 组件内搭建其具体菜单下的页面,比如:首页或者个人中心。

scrollable 属性禁止滑动切换菜单页面。

animationDuration 属性设置切换菜单动画时长,如不需要动画可设置为 0

tabBar 自定义底部菜单 item 的自定义组件,可接受 @Builder 修饰的方法实现更灵活布局,通用为上图片,下文字样式。

onChange 菜单栏切换事件监听,可用状态属性控制其他相关展示。

2、顶部导航栏

Navigation(this.pathStack){
 
}
.height('100%')
.width('100%')
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])
.hideTitleBar(true)

Navigation 导航容器

NavPathStack 负责导航控制对象,入栈、出栈行为及参数传入与接收

expandSafeArea 设置手机屏幕的安全区域延伸,一般手机都会有安全区域,比如刘海屏、灵动岛之类的机型,也就是应用内的绘制都是在屏幕的安全域内进行的,设置 SafeAreaEdge.TOP 表面导航栏(其他视图)的背景颜色可延伸至最顶部的安全区域,布局组件不会因为安全区域被延伸而发生改变。

hideTitleBar 设置导航栏状态为隐藏

主要介绍一下页面的入栈、出栈行为,实现分为几步:

(1)在工程的 pages 文件夹下创建页面。

(2)在工程的 resources/base/profile 文件夹下创建 router_map.json,里面添加对页面的注册,这里以登录页面为例,

{
  "routerMap":[
    {
      "name": "Login",
      "pageSourceFile": "src/main/ets/pages/Login/index.ets",
      "buildFunction": "LoginBuilder"
    }
  ]
}

name 导航 NavPathStack 对象需要跳转的页面的别名。

pageSourceFile 页面的路径。

buildFunction 这里需要传一个实现构建当前页面的构建方法的方法名,可以在页面内初始化一个构建方法,下面是登录的页面。

@Builder
export function LoginBuilder() {
  Login()
}

@Component
struct Login {
  build() {
  
  }
}

(3)将 router_map.json 注册在 module.json5

"routerMap":"$profile:router_map"

(4)跳转的实现

NavPathStack 实例化对象调用 pushPath 或者 pushPathByName 方法即可。

pushPath 可通过 name 参数来进行指定目标页面的跳转。param 可在跳转中传入下一个页面需要的参数。

pushPathByName 除了上面的功能外,还可以传入一个 pop 回调函数,当下一个页面的某些行为完成后需要返回上一个页面并携带参数时, pop 回调函数会得到响应并获取到参数内容。

NavPathStack 对象调用 pop 或者 popToIndex(参数传-1可返回根视图) 方法即可实现页面的后退的功能。

3、点击空白区域键盘回收

通过 FocusController 对象的 clearFocus 方法实现取消键盘第一响应者。

normalFocusController : FocusController = new FocusController()

为组件添加点击事件,实现键盘回收
.onClick(()=>{
  this.normalFocusController.clearFocus()
})

4、自定义弹窗

可对页面进行 @CustomDialog 声明,实现弹窗操作。

@CustomDialog
export default struct SystemNoticeDialog{
  controller?: CustomDialogController = new CustomDialogController({
    builder: this.dialogBuild,
    cancel: ()=>{
    },
    customStyle: true,
    alignment: DialogAlignment.Center
  })

  private dismissDialogAction(){
    this.noticeModel = null
    this.controller?.close()
  }

  @Builder dialogBuild(){
    //自定义组件内容
  }
  build() {}
}

builder 自定义弹窗的构建组件方法

customStyle 开启自定义样式

alignment 弹窗展示内容的对齐方式,通常为中心位置。

5、自定义子窗口

应用的进入与展示依赖一个窗口,这里认为它是主窗口,与此同时还可以创建一个子窗口,在子窗口下可以更为灵活的展示相关的业务逻辑,比如音乐播放器的全局的悬浮球。

WindowManager.getWindowStage().createSubWindow(subWindowName,(err,subWindow)=> {
  if(!err.code){
    const saveModel = AppStorageV2.connect(ToastModel, () => new ToastModel())!
    // 默认需要定时消失
    if(saveModel&&saveModel.toastType != ToastType.ToastProgress){
      setTimeout(()=>{
        subWindow.destroyWindow()
      },saveModel.duration)
    }
    WindowManager.getWindowStage().getMainWindow().then((mainWindow)=>{
      let screenWidth = mainWindow.getWindowProperties().windowRect.width
      let screenHeight = mainWindow.getWindowProperties().windowRect.height
      subWindow.setUIContent(page)
      subWindow.resize(screenWidth,screenHeight)
      subWindow.setWindowTouchable(saveModel.modal)
      subWindow.showWindow((err)=>{
        if(!err.code){
          try {
            subWindow.setWindowBackgroundColor('#00000000')
          } catch (e) {
          }
        }
      })
    })
  }
})

WindowManager.getWindowStage().createSubWindow 创建子窗口

WindowManager.getWindowStage().getMainWindow() 获取主窗口信息,这里主要是用来为设置子窗口建立坐标参考

subWindow.setUIContent 设置子窗口展示页面的内容,这里的参数是一个页面路径。

subWindow.resize 设置子窗口的尺寸。

subWindow.setWindowTouchable 设置子窗口是否允许手势穿透,把事件传给下面的主窗口。

subWindow.destroyWindow 销毁子窗口

6、Web组件 与 JS 交互

class LocalWebBridgeManager{
  normalDataModel: NormalDataModel<string> | null = null
  constructor() {
  }

  setNormalDataModel(normalDataModel: NormalDataModel<string> | null) {
    this.normalDataModel = normalDataModel
  }

  getHtmlBody(): string{
    return this.normalDataModel?.data ?? ''
  }
}

@Component
struct NormalWebView{
  @State navigationModel: WebNavigationModel | null = null
  @State normalDataModel: NormalDataModel<string> | null = null
  localWebBridgeManager: LocalWebBridgeManager = new LocalWebBridgeManager()
  webController: WebviewController = new webview.WebviewController()

  build() {
    NavDestination(){
      Web({src: this.navigationModel?.src,controller: this.webController})
        .javaScriptAccess(true)
        .javaScriptProxy({
          object: this.localWebBridgeManager,
          name: "localWebBridgeManager",
          methodList: ["getHtmlBody"],
          controller: this.webController,
        })
        .width('100%')
        .height('100%')
    }
    .title(this.navigationModel?.title)
    .onReady(async (c: NavDestinationContext)=>{

    })
    .onShown(()=>{

    })
    .onHidden(()=>{
    
    })
  }
}

重点代码 Web 组件的 javaScriptProxy 桥接绑定参数

object 传入的是鸿蒙原生的实例对象。

name html页面的 js 环境中关联的 对象名称。

methodList 映射的方法列表。

controller webview.WebviewController 类型的web控制器管理对象。

写一个本地网页 local.html 放在 resources/rawfile 下,注意:这里的文件必须按照要求进行放置,否则工程内无法访问到。

<script>
    document.getElementById('contain').innerHTML = localWebBridgeManager.getHtmlBody()
</script>

localWebBridgeManager.getHtmlBody() 即为鸿蒙原生对象进行的映射方法执行,可以通过原生进行其他的操作,然后把结果返回给 html 页面,这种方式相对于 iOS / android 的 js 交互简洁了不少。

7、Scroller 滚动监听

scroller: Scroller = new Scroller()

获取 Scroll 组件的滚动位置,用来处理一些UI变化。

Scroll(this.scroller){
 
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width('100%')
.onScrollStop(()=>{
  this.currentShowIndicatorIndex = this.scroller.currentOffset().xOffset > 70 ? 1 : 0
})

onScrollStop 监听滚动停止事件,通过 scroller 拿到滚动的位置信息。

8、主动退出应用

应用需要在同意用户协议的时候才能进入使用,如果拒绝这个时候可以用下面的方法进行主动退出

onWindowStageCreate(windowStage: window.WindowStage): void {
  //注册退出APP事件回调
  this.context.terminateSelf()
}

二、状态

1、页面状态

@State 修饰属性,状态改变后相关绑定的页面会进行刷新。像 Vue 的 ref 和 React 的 setState 用法一致,关注处理数据即可。

@prop 父给子传值。

@Link 父子组件间的双向绑定,场景:封装自定义输入框,编辑的后的内容统一由父组件处理。

2、应用状态

AppStorageV2 应用内存数据存储。将需要同步的数据放在内存中,修改属性后,绑定数据的组件刷新,类似于 sessionStorage 的存储方式。

以前面说到的 NavPathStack 页面导航对象为例,将它缓存到应用缓存里,其他地方均可用下面的方法获取,执行 push/pop等页面操作。

AppStorageV2.connect(NavPathStack,()=>new NavPathStack())!.pop(true)

PersistenceV2 本地持久化存储,应用运行期间,储存的数据变化后,绑定数据的组件刷新,类似于 localStorage 的存储方式。

PersistenceV2.connect(SystemDataModel,()=>new SystemDataModel())

上面的两种状态存储方式的存值和取值相同。

三、常用第三方

鸿蒙常用第三方库汇总地址

Finclip 鸿蒙嵌入教程

总结部分开发用到的小点,希望能帮助到大家[抱拳][抱拳][抱拳]。