HarmonyOS鸿蒙Next中如何解决改变@Provide修饰的值,对应UI未刷新的问题

HarmonyOS鸿蒙Next中如何解决改变@Provide修饰的值,对应UI未刷新的问题

【问题现象】

通过@Provide修饰了一个对象,在Class里改变Provide的值,发现UI监听不到,页面上的数据未发生改变。

问题现象如下图:

点击放大

【背景知识】

在声明式UI编程框架中,UI是程序状态的运行结果,用户构建了一个UI模型,其中应用的运行时的状态是参数。当参数改变时,UI作为返回结果,也将进行对应的改变。这些运行时的状态变化所带来的UI的重新渲染,在ArkUI中统称为状态管理机制。

自定义组件拥有变量,变量必须被装饰器装饰才可以成为状态变量,状态变量的改变会引起UI的渲染刷新。如果不使用状态变量,UI只能在初始化时渲染,后续将不会再刷新。下图展示了State和View(UI)之间的关系。

点击放大

  • View(UI):UI渲染,是指把build方法内的UI描述和@Builder装饰的方法内的UI描述映射到界面。
  • State:状态,指驱动UI更新的数据。用户通过触发组件的事件方法,改变状态数据。状态数据的改变,将引起UI的重新渲染。
  • 状态变量:被状态装饰器装饰的变量,状态变量值的改变会引起UI的渲染更新。示例:@State num: number = 1,其中@State是状态装饰器,num是状态变量。
  • 常规变量:没有被状态装饰器装饰的变量,通常应用于辅助计算。它的改变永远不会引起UI的刷新。

【定位思路】

相关问题代码如下:

// Index.ets
@Entry
@Component
struct Index {
  [@Provide](/user/Provide)('pageStack') pageStack: NavPathStack = new NavPathStack()
  [@Provide](/user/Provide)('playInfo') playInfo: PlayInfo = currentPlayInfo
  build() {
    Navigation(this.pageStack) {
      Column() {
        if (this.playInfo.isPlaying) {
          Text("正在播放" + this.playInfo.title)
            .fontSize(50)
            .fontWeight(FontWeight.Bold)
            .alignRules({
              center: { anchor: '__container__', align: VerticalAlign.Center },
              middle: { anchor: '__container__', align: HorizontalAlign.Center }
            })
        } else {
          Text("没有播放")
            .fontSize(30)
        }
        Text("Change PlayInfo")
          .fontSize(30)
          .fontWeight(FontWeight.Bold)
          .alignRules({
            center: { anchor: '__container__', align: VerticalAlign.Center },
            middle: { anchor: '__container__', align: HorizontalAlign.Center }
          })
          .onClick(() => {
            player.play();
          })
      }
      .height('100%')
      .width('100%')
    }
    .hideTitleBar(true)
  }
}

// PlayInfo.ets
export class PlayInfo {
  isPlaying: boolean = false
  title: string = ""
}
let currentPlayInfo = new PlayInfo();
export default currentPlayInfo as PlayInfo;

// AVPlayer.ets
import currentPlayInfo from './PlayInfo'

class AVPlayer {
  play(){
    currentPlayInfo.isPlaying  =  !currentPlayInfo.isPlaying
    currentPlayInfo.title = "第" + Math.round(Math.random()*100) + "首"
  }
}
let player = new AVPlayer();
export default player as AVPlayer;

如上代码,定义了一个播放类PlayInfo,同时new出了一个单例对象currentPlayInfo并导出给Index.ets和AVPlayer.ets使用,在Index.ets中通过@Provide声明了一个状态变量PlayInfo并把currentPlayInfo赋值给PlayInfo,点击文本时通过触发AVPlayer的play方法改变单例对象currentPlayInfo,预期能触发页面的刷新,实际上页面内容未发生改变。

如“背景知识”一章所述,ArkUI编程框架中,页面刷新是需要通过状态装饰器装饰的状态变量,由状态变量值的改变引起UI的渲染更新。实际上,对于@Provide装饰的状态变量,会被代理变成一个Proxy对象,并不是原先的currentPlayInfo对象,因此在AVPlayer类中修改的对象,与Index页面中的对象不一致,play函数虽然修改了变量但是修改的是未被代理前的对象,@Provide修饰的playInfo状态变量无法监听到变化,因而无法驱动UI的改变。

【解决方案】

对于状态变量,如果需要驱动UI的刷新,只需要修改该状态变量即可。如上代码可以做下述修改,即可完成数据驱动UI的改变:

// Index.ets
Text("Change PlayInfo")
.fontSize(30)
.fontWeight(FontWeight.Bold)
.alignRules({
  center: { anchor: '__container__', align: VerticalAlign.Center },
  middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
.onClick(() => {
  // 需要修改的是状态变量,以实现驱动UI更新的效果
  player.playNew(this.playInfo as PlayInfo);
})

// AVPlayer.ets
class AVPlayer {
  playNew(playInfo: PlayInfo) {
    playInfo.isPlaying  =  !playInfo.isPlaying
    playInfo.title = "第" + Math.round(Math.random()*100) + "首"
  }
}

修改后的效果图如下:

点击放大

【总结】

  • ArkUI是基于MVVM模式的声明式UI的编程框架,通过状态变量的变化驱动UI的更新,因此在需要更新UI时,需要修改对应的状态变量。
  • 对于未被装饰符修饰的变量或者对象等,主要是用于类型声明以及初始化,修改该变量无法直接引起UI数据的刷新。

更多关于HarmonyOS鸿蒙Next中如何解决改变@Provide修饰的值,对应UI未刷新的问题的实战教程也可以访问 https://www.itying.com/category-93-b0.html

1 回复

更多关于HarmonyOS鸿蒙Next中如何解决改变@Provide修饰的值,对应UI未刷新的问题的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS鸿蒙Next中,@Provide修饰的状态变量会被代理为Proxy对象。直接修改未被代理的对象(如currentPlayInfo)不会触发UI刷新。要解决此问题,需直接修改@Provide修饰的状态变量。例如,在Index.ets中,通过player.playNew(this.playInfo)修改playInfo,确保状态变量变化驱动UI更新。

回到顶部