HarmonyOS 如何自定义导航转场

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

HarmonyOS
1天前
浏览
收藏 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})
  }
}
分享
微博
QQ
微信
回复
23h前
相关问题
自定义弹窗自定义转场动画
1170浏览 • 1回复 待解决
Tabs组件自定义导航栏UI问题
842浏览 • 1回复 待解决
HarmonyOS 如何自定义tab
456浏览 • 2回复 待解决
HarmonyOS 如何自定义BuildMode?
566浏览 • 1回复 待解决
HarmonyOS 如何自定义注解?
274浏览 • 1回复 待解决
HarmonyOS 如何自定义toast
33浏览 • 1回复 待解决
HarmonyOS 如何自定义相机
22浏览 • 1回复 待解决