HarmonyOS - UIObserver(无感监听)

@[TOC](HarmonyOS - UIObserver(无感监听))

概述

本篇文档主要介绍,在鸿蒙项目开发过程中,我们如何使用UIObserver对象提供的对UI组件行为变化的无感监听能力。

基于当前鸿蒙项目, 我们是把页面所有的数据都存放在一个叫UIState对象中, 并且ViewModel持有这个UIState对象,而且ViewModel中包含了所有业务的逻辑。

当我们在ViewModel中请求数据时,需要知道在什么时机去请求, 那么我们就需要再ViewModel中知道View的一些生命周期方法的变化。

使用鸿蒙提供的UIObserver即可实现该效果。

UIObserver无感监听Router

监听代码:

typescript 复制代码
// 获取UIObserver对象
const lifecycleObserver: UIObserver = uiContext.getUIObserver()
lifecycleObserver.on('routerPageUpdate', (routerPageInfo: RouterPageInfo) => {
      console.log(`[lifecycle] routerPageUpdate============: ${this.stringify(routerPageInfo)}`)
    })

通过Router的方式push一个PageThree页面, 输出结果如下:

我们可以看到在页面状态发生变化时,注册的回调将会触发,开发者可以通过回调中传入的入参拿到页面的相关信息,如:页面的名字,索引,路径,生命周期状态等

回调方法的RouterPageInfo对象包含了上述的所有信息

UIObserver无感监听Navigation

示例代码:

typescript 复制代码
const lifecycleObserver: UIObserver = uiContext.getUIObserver()

// 监听navDestination组件 切换到另外一个navDestination组件的变化
lifecycleObserver.on('navDestinationSwitch', (navDestinationSwitchInfo: uiObserver.NavDestinationSwitchInfo) => {
      console.log(`[lifecycle] navDestinationSwitch==================: ${this.stringify(navDestinationSwitchInfo)}`)
    })

// 监听navDestination组件(页面)生命周期发生变化
 lifecycleObserver.on('navDestinationUpdate', (navDestinationInfo: NavDestinationInfo) => {

      console.log(`[lifecycle] navDestinationUpdate=====================: ${this.stringify(navDestinationInfo)}`)
 })

输出结果:

根据结果可以知道, 通过上面的两个方法的监听, 我们可以知道页面的来源,当前页面的生命周期变化等信息。

UIObserver无感监听Tab

typescript 复制代码
// 获取UIObserver对象
const lifecycleObserver: UIObserver = uiContext.getUIObserver()
// 注册tabContentUpdate的监听, 当tabContent组件变化时会回调
lifecycleObserver.on('tabContentUpdate', (tabContentInfo: uiObserver.TabContentInfo) => {
      console.log(`[lifecycle] tabContentUpdate====================: ${this.stringify(tabContentInfo)}`)

    })

测试结果:

其实关于UIObserver, 这里只介绍了比较常用的三种,官网文档中其实还有很多无感监听其他事件的api, 如果想知道更多请参考官方文档UIObsever官方文档

UIObserver的工具类封装

工具类示例代码

typescript 复制代码
import { ArrayList } from '@kit.ArkTS'
import { router, uiObserver, UIObserver } from '@kit.ArkUI'

export interface ILifecycleId {
  tabId?: string
  pageId?: string
  navDestinationId?: string
}

export function getLifecycleId(component: CustomComponent, tabId?: string): ILifecycleId {
  return {
    tabId: tabId,
    pageId: component.queryRouterPageInfo()?.pageId,
    navDestinationId: component.queryNavDestinationInfo()?.navDestinationId,
  }
}

/**
 * 定义页面的个个生命周期的枚举, 具体的值参考个个监听回调方法中的状态值
 */
export enum LifecycleState {
  onCreate = 'onCreate', // 页面创建的时候
  onVisible = 'onVisible', // 页面显示
  onInvisible = 'onInvisible', // 页面隐藏
  onDestroy = 'onDestroy', // 页面

  onTabSelect = 'onTabSelect', // TabContent 组件显示(选中)
  onTabUnselect = 'onTabUnselect', // TabContent组件隐藏(未选中)
}

// 定义一个生命周期状态变量
export type TabLifeState = LifecycleState.onVisible | LifecycleState.onInvisible | LifecycleState.onTabSelect
  | LifecycleState.onTabUnselect


/**
 * 定义一个页面信息对象
 */
export interface ILifecycleObserver {
  tabId?: string
  pageId?: string
  navDestinationId?: string
  uiAbility?: boolean
  onReceive: (state: LifecycleState) => void
}

export class Lifecycle {
  /** 缓存监听的对象列表 */
  private observerList: ArrayList<ILifecycleObserver> = new ArrayList()
  /** 缓存Router下监听的列表 */
  private routerPageInfoMap: Map<string, uiObserver.RouterPageInfo> = new Map()
  /** 缓存navDestination监听的列表 */
  private navDestinationStateMap: Map<string, uiObserver.NavDestinationState> = new Map()

  // private mainTabSelectProvider?: IMainTabSelectProvider
  // private uiAbilityLifecycleProvider?: IUIAbilityLifecycleProvider

  init(uiContext: UIContext) {

    const lifecycleObserver: UIObserver = uiContext.getUIObserver()

    // 无感监听router页面变化回调方法
    lifecycleObserver.on('routerPageUpdate', (routerPageInfo: RouterPageInfo) => {
      console.log(`[lifecycle] routerPageUpdate============: ${this.stringify(routerPageInfo)}`)
      // 把Router栈中的页面都缓存到map中
      this.routerPageInfoMap.set(routerPageInfo.pageId, routerPageInfo)
      // 处理Router个个page的生命周期回调
      this.handlePageState(routerPageInfo.pageId, routerPageInfo.state)
      // 移除已经销毁页面的监听
      this.checkRemoveIfOnPageDestroy(routerPageInfo)

    })

    lifecycleObserver.on('tabContentUpdate', (tabContentInfo: uiObserver.TabContentInfo) => {
      console.log(`[lifecycle] tabContentUpdate====================: ${this.stringify(tabContentInfo)}`)
      // 该方法可以处理
    })

    // 监听Navigation的页面切换事件。
    lifecycleObserver.on('navDestinationSwitch', (navDestinationSwitchInfo: uiObserver.NavDestinationSwitchInfo) => {
      console.log(`[lifecycle] navDestinationSwitch==================: ${this.stringify(navDestinationSwitchInfo)}`)

      // 如果我们整个项目是直接使用Navigation, 我们可以在这个监听方法内部, 判断是否回到主页面

      let mainPageState: LifecycleState | undefined
      // 判断是否从主界面跳转来的
      if(navDestinationSwitchInfo.from === 'navBar') {
        mainPageState = LifecycleState.onInvisible
      } else if(navDestinationSwitchInfo.to === 'navBar') {
        // 判断是否从其他页面回到主页面
        mainPageState = LifecycleState.onVisible
      }

      if(!mainPageState) {
        return
      }

      // 如果你是采用Router + Navigation 实现页面跳转, 可如下判断是否回到主页 (例如: 登录,欢迎页使用router, 主页使用Navigation)
      const mainRoutePageInfoFound = Array.from(this.routerPageInfoMap.values()).find((routePageInfo) => routePageInfo.name === 'main' )
      if(!mainRoutePageInfoFound) {
        return
      }

      // 处理回到主页事件
      this.dispatchMainPageEvent(mainRoutePageInfoFound.pageId, mainPageState)

    })
    // 监听NavDestination组件的状态变化。  生命周期方法的变化
    lifecycleObserver.on('navDestinationUpdate', (navDestinationInfo: NavDestinationInfo) => {
      console.log(`[lifecycle] navDestinationUpdate=====================: ${this.stringify(navDestinationInfo)}`)

      // 缓存每个NavDestination路由的状态
      this.navDestinationStateMap.set(navDestinationInfo.navDestinationId, navDestinationInfo.state)
      // 处理NavDestination的状态
      this.handleNavDestinationState(navDestinationInfo.navDestinationId, navDestinationInfo.state)

      // 处理销毁的NavDestination
      this.checkRemoveIfOnNavDestinationDestroy(navDestinationInfo)
    })
  }

  private stringify(value: object) {
    const body = JSON.stringify(value, (_, v: object) => {
      const value: string | object = typeof v === 'bigint' ? (v as bigint).toString() : v
      return value
    })

    return body
  }

  private checkRemoveIfOnNavDestinationDestroy(navDestinationInfo: uiObserver.NavDestinationInfo) {
    if (navDestinationInfo.state === uiObserver.NavDestinationState.ON_DISAPPEAR) {
      const observerMatch = this.observerList.convertToArray()
        .find((observer) => observer.navDestinationId === navDestinationInfo.navDestinationId)
      if (observerMatch) {
        this.removeObserver(observerMatch)
      }
    }
  }


  /**
   * 当前页面销毁时,我们需要删除该页面的监听
   * @param routerPageInfo
   */
  private checkRemoveIfOnPageDestroy(routerPageInfo: uiObserver.RouterPageInfo) {
    if (routerPageInfo.state === uiObserver.RouterPageState.ABOUT_TO_DISAPPEAR) {
      const observerMatch =
        this.observerList.convertToArray().find((observer) => observer.pageId === routerPageInfo.pageId)
      if (observerMatch) {
        this.removeObserver(observerMatch)
      }
    }
  }

  /**
   * 处理Router 中page的State方法
   * @param pageId 页面id
   * @param pageState 页面当前的生命周期状态
   */
  private handlePageState(pageId: string, pageState: uiObserver.RouterPageState) {
    switch (pageState) {
      case uiObserver.RouterPageState.ABOUT_TO_APPEAR:

        this.dispatchPageEvent(pageId, LifecycleState.onCreate)

        break


      case uiObserver.RouterPageState.ON_PAGE_SHOW:
        this.dispatchPageEvent(pageId, LifecycleState.onVisible)
        break

      case uiObserver.RouterPageState.ON_PAGE_HIDE:
        this.dispatchPageEvent(pageId, LifecycleState.onInvisible)
        break


      case uiObserver.RouterPageState.ABOUT_TO_DISAPPEAR:
        this.dispatchPageEvent(pageId, LifecycleState.onDestroy)

        break

    }
  }


  private handleNavDestinationState(navDestinationId: string, pageState: uiObserver.NavDestinationState) {
    switch (pageState) {
      case uiObserver.NavDestinationState.ON_APPEAR:
        this.dispatchNavDestinationEvent(navDestinationId, LifecycleState.onCreate)
        break

      case uiObserver.NavDestinationState.ON_SHOWN:
        this.dispatchNavDestinationEvent(navDestinationId, LifecycleState.onVisible)
        break

      case uiObserver.NavDestinationState.ON_HIDDEN:
        this.dispatchNavDestinationEvent(navDestinationId, LifecycleState.onInvisible)
        break

      case uiObserver.NavDestinationState.ON_DISAPPEAR:
        this.dispatchNavDestinationEvent(navDestinationId, LifecycleState.onDestroy)
        break

    }
  }

  private dispatchMainPageEvent(pageId: string, state: LifecycleState) {
    this.observerList
      .convertToArray()
      .filter((observer) => observer.pageId === pageId)
      .forEach((observer: ILifecycleObserver) => {
        this.doDispatchPageEvent(observer, state)
      })
  }


  /**
   * 处理NavDestination发生变化时的回调
   * @param navDestinationId  NavDestination组件的id
   * @param state 生命周期状态
   */
  private dispatchNavDestinationEvent(navDestinationId: string, state: LifecycleState) {
    this.observerList
      .convertToArray()
      .filter((observer) => observer.navDestinationId === navDestinationId)
      .forEach((observer: ILifecycleObserver) => {
        observer.onReceive(state)
      })
  }

  /**
   * 处理Router中页面的变化时的回调
   * @param pageId router page的id
   * @param state 生命周期状态
   */
  private dispatchPageEvent(pageId: string, state: LifecycleState) {
    this.observerList
      .convertToArray()
      .filter((observer) => observer.pageId === pageId)
      .forEach((observer: ILifecycleObserver) => {
        observer.onReceive(state)
      })
  }

  private doDispatchPageEvent(observer: ILifecycleObserver, state: LifecycleState) {
    if (observer.navDestinationId) {
      return
    }

    if (!observer.tabId) {
      observer.onReceive(state)
      return
    }
    observer.onReceive(state)
  }


  private dispatchUIAbilityEvent(state: LifecycleState) {
    this.observerList
      .convertToArray()
      .filter((observer) => observer.uiAbility === true)
      .forEach((observer: ILifecycleObserver) => {
        // logger.debug(`[lifecycle][uiAbility] onReceive ${state}`)
        observer.onReceive(state)

      })
  }


  selectTab(tabId: string) {
    this.dispatchTabEvent(tabId, LifecycleState.onTabSelect)
    this.dispatchTabEvent(tabId, LifecycleState.onVisible)
  }

  unselectTab(tabId: string) {
    this.dispatchTabEvent(tabId, LifecycleState.onTabUnselect)
    this.dispatchTabEvent(tabId, LifecycleState.onInvisible)
  }

  private dispatchTabEvent(tabId: string, state: TabLifeState) {
    this.observerList
      .convertToArray()
      .filter((observer) => observer.tabId === tabId)
      .forEach((observer: ILifecycleObserver) => {
        this.doDispatchTabEvent(observer, state)
      })
  }

  private doDispatchTabEvent(observer: ILifecycleObserver, state: TabLifeState) {
    if (!observer.pageId) {
      observer.onReceive(state)
      return
    }

    const pageInfo = this.routerPageInfoMap.get(observer.pageId)
    if (pageInfo?.state === uiObserver.RouterPageState.ON_PAGE_SHOW) {
      observer.onReceive(state)
    }

  }


  // 增加一个页面监听
  addObserver(observer: ILifecycleObserver, sticky: boolean = true) {
    this.observerList.add(observer)

    if (sticky) {
      this.observerUICallbackSticky(observer)
    }
  }

  private observerUICallbackSticky(observer: ILifecycleObserver) {
    const lifecycleState = this.fetchLifecycleState(observer)

    if (!lifecycleState) {
      return
    }

    if (lifecycleState === LifecycleState.onDestroy) {
      observer.onReceive(LifecycleState.onInvisible)
      observer.onReceive(LifecycleState.onDestroy)
      return
    }


    if (!observer.tabId) {
      observer.onReceive(LifecycleState.onCreate)

      if (lifecycleState !== LifecycleState.onCreate) {

        if (observer.navDestinationId) {
          this.dispatchNavDestinationEvent(observer.navDestinationId, lifecycleState)
        } else if (observer.pageId) {
          this.doDispatchPageEvent(observer, lifecycleState)
        }


      }
      return
    }
  }


  private fetchLifecycleState(observer: ILifecycleObserver): LifecycleState | undefined {
    let lifecycleState: LifecycleState | undefined
    const pageId = observer.pageId
    const navDestinationId = observer.navDestinationId
    if (pageId) {
      const pageInfo = this.routerPageInfoMap.get(pageId)
      lifecycleState = pageInfo ? transformLifecycleStateFromPage(pageInfo.state) : undefined

    } else if (navDestinationId) {
      const destinationState = this.navDestinationStateMap.get(navDestinationId)
      lifecycleState = destinationState ? transformLifecycleStateFromNavDestination(destinationState) : undefined
    }
    return lifecycleState
  }

  /**
   * 移除一个页面监听
   * @param observer
   */
  removeObserver(observer: ILifecycleObserver) {
    this.observerList.remove(observer)
  }


  release() {
    this.observerList.clear()
  }
}

function transformLifecycleStateFromPage(pageState: uiObserver.RouterPageState): LifecycleState | undefined {
  switch (pageState) {
    case uiObserver.RouterPageState.ABOUT_TO_APPEAR:
      return LifecycleState.onCreate

    case uiObserver.RouterPageState.ABOUT_TO_DISAPPEAR:
      return LifecycleState.onDestroy

    case uiObserver.RouterPageState.ON_PAGE_SHOW:
      return LifecycleState.onVisible

    case uiObserver.RouterPageState.ON_PAGE_HIDE:
      return LifecycleState.onInvisible

    default:
      return undefined

  }

}


function transformLifecycleStateFromNavDestination(navDestinationState: uiObserver.NavDestinationState): LifecycleState | undefined {
  switch (navDestinationState) {
    case uiObserver.NavDestinationState.ON_APPEAR:
      return LifecycleState.onCreate

    case uiObserver.NavDestinationState.ON_DISAPPEAR:
      return LifecycleState.onDestroy

    case uiObserver.NavDestinationState.ON_SHOWN:
      return LifecycleState.onVisible

    case uiObserver.NavDestinationState.ON_HIDDEN:
      return LifecycleState.onInvisible

    default:
      return undefined

  }

}


export const lifecycle = new Lifecycle()

工具类使用示例

  1. 首先我们需要在EntryAbility类中,初始化这个工具类
typescript 复制代码
onWindowStageCreate(windowStage: window.WindowStage): void {

// 初始化工具类
    windowStage.getMainWindow().then((window) => {

      // 初始化生命周期的对象方法
      lifecycle.init(window.getUIContext())
    })

  }
  1. 在页面中的应用
typescript 复制代码
//TestPage.ets
import { ILifecycleId } from "./Lifecycle"
import { TestView } from "./TestView"

@Component
export struct TestPage {
  build() {
    NavDestination() {
      TestView({
        onReady: (lifecycleId: ILifecycleId) => {
          // 调用逻辑层的生命周期执行方法, 比如viewmodel中的监听方法
        }
      })
    }
  }
}

// TestView.ets
import { getLifecycleId, ILifecycleId } from './Lifecycle'

@Component
export struct TestView {
  onReady: (lifecycleId: ILifecycleId) => void = () => {}

  aboutToAppear(): void {
    this.onReady(getLifecycleId(this))
  }
  build() {
    Column() {

    }
  }
}

注意:getLifecycleId(this)这个句代码是能直接放在TestPage的aboutToAppear生命周期方法中的, 因为此时的NavDestination并没有创建,所以获取的navDestinationId都是undefined

相关推荐
行者9618 小时前
Flutter与OpenHarmony深度集成:数据导出组件的实战优化与性能提升
flutter·harmonyos·鸿蒙
小雨下雨的雨18 小时前
Flutter 框架跨平台鸿蒙开发 —— Row & Column 布局之轴线控制艺术
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨19 小时前
Flutter 框架跨平台鸿蒙开发 —— Center 控件之完美居中之道
flutter·ui·华为·harmonyos·鸿蒙
小雨下雨的雨20 小时前
Flutter 框架跨平台鸿蒙开发 —— Icon 控件之图标交互美学
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨20 小时前
Flutter 框架跨平台鸿蒙开发 —— Placeholder 控件之布局雏形美学
flutter·ui·华为·harmonyos·鸿蒙系统
行者9620 小时前
OpenHarmony Flutter弹出菜单组件深度实践:从基础到高级的完整指南
flutter·harmonyos·鸿蒙
小雨下雨的雨21 小时前
Flutter 框架跨平台鸿蒙开发 —— Padding 控件之空间呼吸艺术
flutter·ui·华为·harmonyos·鸿蒙系统
行者9621 小时前
Flutter到OpenHarmony:横竖屏自适应布局深度实践
flutter·harmonyos·鸿蒙
小雨下雨的雨1 天前
Flutter 框架跨平台鸿蒙开发 —— Align 控件之精准定位美学
flutter·ui·华为·harmonyos·鸿蒙
行者961 天前
Flutter与OpenHarmony集成:跨平台开关组件的实践与优化
flutter·harmonyos·鸿蒙