HarmonyOS 如何自定义导航转场

在官网中搜索,多个文档链接中都提示看示例3,但是示例3中并无自定义导航转场相关代码,如:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-navigation-transition-V5

HarmonyOS
2024-12-25 09:26:25
浏览
收藏 0
回答 1
回答 1
按赞同
/
按时间
aquaa

Navigation通过customNavContentTransition事件提供自定义转场动画的能力,当转场开始时,通过回调函数告知开发者,告知此次动画from(从哪来)、to(到哪去)、是Push、Pop亦或是Repalce。这里需要注意当为根视图时,NavContentInfo的name值为undefined。

开发者可以在customNavContentTransition的回调函数中进行动画处理,返回NavigationAnimatedTransition自定义转场协议已实现自定义转场。 NavigationAnimatedTransition对象中包含三个参数,timeout(动画超时结束时间),transition(自定义动画执行回调),onTransitionEnd(转场完成回调),需要在transition方法中实现具体动画逻辑。 由于自定义转场参数是在Navigation层级,但是每个页面都会有其特定的自定义转场效果,因此需要定义一套转场动画框架,已实现在Navigation层面对框架进行统一管理,各个页面通过实现框架提供的回调函数,将其特定的动画效果传递给Navigation。 Step1:构建动画框架,通过一个Map管理各个页面自定义自定义动画对象CustomTransition,CustomTransition对象提供了Push、Pop、Replace各个动画阶段的回调函数给各个页面进行补充,此处将各个阶段细分为In和Out,从而实现页面进入和退出时不同的转场效果。 自定义动画的构建需要结合in、out两个页面同时进行,因此案例针对不同路由方式均提供了in、out两个方法

// 自定义动画对象,定义了Push、Pop、Replace各个动画阶段的回调函数
export class CustomTransition {
  pageID : number = -1;
  onPushInStart: () => void = () => {};
  onPushInEnd: () => void = () => {};
  onPushInFinish: () => void = () => {};
  onPopInStart: () => void = () => {};
  onPopInEnd: () => void = () => {};
  onPopInFinish: () => void = () => {};
  onReplaceInStart: () => void = () => {};
  onReplaceInEnd: () => void = () => {};
  onReplaceInFinish: () => void = () => {};
  onPushOutStart: () => void = () => {};
  onPushOutEnd: () => void = () => {};
  onPushOutFinish: () => void = () => {};
  onPopOutStart: () => void = () => {};
  onPopOutEnd: () => void = () => {};
  onPopOutFinish: () => void = () => {};
  onReplaceOutStart: () => void = () => {};
  onReplaceOutEnd: () => void = () => {};
  onReplaceOutFinish: () => void = () => {};
  ...

  // 获取启动阶段参数回调
  public getStart(operation : NavigationOperation, isInPage : boolean) : () => void {
    if (operation == NavigationOperation.PUSH) {
      if (isInPage) {
        return this.onPushInStart;
      } else {
        return this.onPushOutStart;
      }
    } else if (operation == NavigationOperation.POP) {
      if (isInPage) {
        return this.onPopInStart;
      } else {
        return this.onPopOutStart;
      }
    } else {
      if (isInPage) {
        return this.onReplaceInStart;
      } else {
        return this.onReplaceOutStart;
      }
    }
  }
  // 获取动画结束阶段参数回调
  public getEnd(operation : NavigationOperation, isInPage : boolean) : () => void {
    ...
  }
  // 获取动画结束后参数回调
  public getFinished(operation : NavigationOperation, isInPage : boolean) : () => void {
    ...
  }
}

// 自定义动画对象框架
export class CustomTransitionFW {
  // 各个页面自定义动画对象映射表
  private customTransitionMap: Map<number, CustomTransition> = new Map<number, CustomTransition>()
  ...
  registerNavParam(ct : CustomTransition): void {
    ...
    this.customTransitionMap.set(ct.pageID, ct);
  }

  unRegisterNavParam(pageId: number): void {
    ...
    this.customTransitionMap.delete(pageId);
  }

  getAnimateParam(pageId: number): CustomTransition {
    ...
    return this.customTransitionMap.get(pageId) as CustomTransition;
  }
}

Step2:配置Navigation的customNavContentTransition属性,当返回undefined时,使用系统默认动画。
build() {
  Navigation(this.pageStack){
    ...
  }.hideTitleBar(true)
  .hideToolBar(true)
  .navDestination(this.pageMap)
  .customNavContentTransition((from: NavContentInfo, to: NavContentInfo, operation: NavigationOperation) => {
    // 对于Dialog型的页面,此处统一做了自定义动画的屏蔽,若需要动画,可以不做此判断。
    if (from.mode == NavDestinationMode.DIALOG || to.mode == NavDestinationMode.DIALOG) {
      console.error(`==== no transition because Dialog`);
      return undefined;
    }
    let pageIn : CustomTransition | undefined;
    let pageOut : CustomTransition | undefined;
    pageIn = CustomTransitionFW.getInstance().getAnimateParam(to.index)
    pageOut = CustomTransitionFW.getInstance().getAnimateParam(from.index)
    // 业务首页跳转时若没有自定义动画诉求,此处可以通过判断页面id是否为-1(-1表示Navigation根视图)进行跳出。
    if (from.index === -1 || to.index === -1) {
      return undefined;
    }
    // 创建自定义转场协议,各个页面都会根据协议中的配置进行转场,当返回undefined时,使用系统默认动画。
    let customAnimation: NavigationAnimatedTransition = {
      onTransitionEnd: (isSuccess: boolean)=>{
        ...
      },
      transition: (transitionProxy: NavigationTransitionProxy)=>{
        ...
      },
      timeout: 100,
    };
    return customAnimation;
  })
}


Step3:customNavContentTransition事件需要返回NavigationAnimatedTransition对象,具体的动画实现需要在NavigationAnimatedTransition的transition属性中实现。transition中通过各个页面在框架中注册的回调函数,配置框架需要的动画属性。案例中各个页面注册了PUSH\POP\REPLACE的各个阶段动画参数。此处需要注意由于Navigation根页面不在栈中,因此无法与NavDestination无法产生跳转联动,因此如果第一个入栈的页面也需要自定义动画,那么就需要判断pageId是否为-1(-1及表示为根视图),如果是-1则不就行动画设置。
let customAnimation: NavigationAnimatedTransition = {
  ...
  transition: (transitionProxy: NavigationTransitionProxy)=>{
  // 配置起始参数
  if (pageOut != undefined && pageOut.pageID != -1) {
  pageOut.getStart(operation, false)();
}
if (pageIn != undefined && pageIn.pageID != -1) {
  pageIn.getStart(operation, true)();
}
// 执行动画
animateTo({
  duration: 1000,
  curve: Curve.EaseInOut,
  onFinish: ()=>{
    if (pageOut != undefined && pageOut.pageID != -1) {
      pageOut.getFinished(operation, false)();
    }
    if (pageIn != undefined && pageIn.pageID != -1) {
      pageIn.getFinished(operation, true)();
    }
    transitionProxy.finishTransition();
  }}, ()=>{
  if (pageOut != undefined && pageOut.pageID != -1) {
    pageOut.getEnd(operation, false)();
  }
  if (pageIn != undefined && pageIn.pageID != -1) {
    pageIn.getEnd(operation, true)();
  }
})
}
}

Step4:在各个页面中定义动画回调,并往自定义动画框架中注册。并在组件onDisAppear生命周期中注销框架中的页面动画回调。
Step5:定义NavDestination的translate属性,已实现动画效果。
@Component
export struct loginPageView {
  ...
  private pageId: number = 0;
  @State transX: number = 0;
  @State transY: number = 0;

  aboutToAppear(): void {
    this.pageId = this.pageStack.getAllPathName().length - 1;
    let ct : CustomTransition = new CustomTransition();
    ct.pageID = this.pageId;
    ct.onPushInStart = ct.onPushOutEnd = ct.onPopInStart = ct.onPopOutEnd = ct.onReplaceInStart = ct.onReplaceOutEnd = () => {
      this.transX = -300;
    }
    ct.onPushInEnd = ct.onPushOutStart = ct.onPopInEnd = ct.onPopOutStart = ct.onReplaceInEnd = ct.onReplaceOutStart = () => {
      this.transX = 0;
    }
    ct.onPushInFinish = ct.onPopInFinish  = ct.onReplaceInFinish = () => {
      this.transX = 0;
    }
    ct.onPushOutFinish = ct.onPopOutFinish  = ct.onReplaceOutFinish = () => {
      this.transX = -300;
    }
    // 将页面的动画效果注册到动画框架中
    CustomTransitionFW.getInstance().registerNavParam(ct)
  }

  build() {
    NavDestination(){
      ...
    }.hideTitleBar(true)
    .onDisAppear(()=>{
      // 组件销毁的时候,需要将页面的动画效果从框架中删除
      CustomTransitionFW.getInstance().unRegisterNavParam(this.pageId)
    })
    // 定义translate,已实现动画
    .translate({x: this.transX, y: this.transY, z: 0})
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
分享
微博
QQ
微信
回复
2024-12-25 10:34:38
相关问题
自定义弹窗自定义转场动画
1945浏览 • 1回复 待解决
Navigation如何自定义立体转场动画?
305浏览 • 1回复 待解决
Tabs组件自定义导航栏UI问题
1466浏览 • 1回复 待解决
HarmonyOS 如何自定义BuildMode?
1419浏览 • 1回复 待解决
HarmonyOS 如何自定义注解?
1258浏览 • 1回复 待解决
HarmonyOS 如何自定义toast
939浏览 • 1回复 待解决
HarmonyOS 如何自定义tab
1451浏览 • 2回复 待解决