自定义转场动画
HMRouter支持自定义转场动画,开发者可以通过实现IHMAnimator接口来定义页面切换效果,实现丰富多样的转场体验。
接口定义
自定义转场动画需要实现IHMAnimator接口:
interface IHMAnimator {
effect(enterHandle: HMAnimatorHandle, exitHandle: HMAnimatorHandle): void;
interactive?(handle: HMAnimatorHandle): void;
}
原理介绍
开发者通过实现IHMAnimator接口,覆盖effect和interactive方法,来对动画效果进行自定义。
动画效果处理器HMAnimatorHandle在页面加载aboutToAppear时进行初始化,对动画效果进行预设,并在页面跳转时进行调用。
一次性动画的处理器会在页面跳转时进行重新覆盖。
因此需注意:
- 全局动画的变更不会影响到已经加载的页面(包括单例页面),只有页面重新加载才会生效
动画状态设置
effect(enterHandle: HMAnimatorHandle, exitHandle: HMAnimatorHandle)方法中提供了入场动画enterHandle和出场动画exitHandle2组动画状态设置。
通过对HMAnimatorHandle回调中的页面位移/缩放/透明度等状态进行设置,来实现动画效果:
- 入场动画分为主动入场
enterHandle.start/finish/onFinish和被动入场enterHandle.passiveStart/passiveFinish/passiveOnFinish - 出场动画分为主动出场
exitHandle.start/finish/onFinish和被动出场exitHandle.passiveStart/passiveFinish/passiveOnFinish
每种动画可以设置3种页面状态:
- 动画开始状态
start - 动画完成状态
finish - 动画结束状态
onFinish
通过设置动画曲线让页面在开始和完成状态之间进行过渡,默认动画曲线为Curve.EaseIn,结束状态用于将页面还原,释放资源等。
页面跳转动画流程
A页面push/replace到B页面:B页面为主动入场,A页面为被动出场

B页面pop到A页面:B页面为主动出场,A页面为被动入场

动画曲线、持续时间、超时时间等也需要通过HMAnimatorHandle进行设置。
HMAnimatorHandle也提供了自定义动画回调customAnimation和passiveCustomAnimation让开发者对动画进行完全控制。
自定义动画类型
- 全局自定义动画:通过
HMNavigation初始化时定义,并且可以通过HMRouterMgr进行修改 - 页面自定义动画:通过
@HMRouter标签进行定义,页面跳转时分别执行2个页面定义的动画,如果有页面未定义则使用全局动画 - 一次性动画:通过
HMRouterMgr进行页面跳转时定义,同时执行一次性动画中的入场动画和出场动画
一次性动画的覆盖原则
- 路由push/replace时传入一次性动画,目标页面执行一次性动画的主动入场,源页面执行一次性动画的被动离场
- push/replace时传入的一次性动画根据目标页面生存周期,当目标页面pop时未传入一次性动画,则执行push/replace时的一次性动画,pop目标页面执行一次性动画的被动入场,源页面执行主动离场
- 当pop时传入一次性动画,目标页面的被动入场和源页面的主动离场均查找一次性动画
- 上述场景描述的主动入场,被动入场,主动离场,被动离场动画,如果一次性动画内未定义,则会执行页面定义的动画或者全局动画定义
交互式事件配置
interactive?(handle: HMAnimatorHandle)方法中提供了交互式事件配置。
通过对HMAnimatorHandle回调中的手势事件回调来进行路由操作,实现动画状态的跟随手势变化:
actionStart: 手势开始事件,在事件中触发路由跳转,通过对手势位移判断来决定页面是push/replace还是popupdateProgress: 手势更新事件,在事件中通过设置转场进度百分比来触发动画曲线的跟随手势变化actionEnd: 手势结束事件,在事件中对手势位移判断来调用NavigationTransitionProxy决定转场状态是完成转场还是取消转场
交互式转场方法interactive不设置动画效果,只是通过手势事件对转场行为进行处理并控制动画曲线的进度,动画效果需要配合effect方法进行定义。
实现示例
基本转场动画
import { HMAnimator, HMAnimatorHandle, IHMAnimator } from "@hadss/hmrouter";
import { AttributeUpdater } from "@kit.ArkUI";
@HMAnimator({ animatorName: 'FadeAnimator' })
export class FadeAnimator implements IHMAnimator {
effect(enterHandle: HMAnimatorHandle, exitHandle: HMAnimatorHandle): void {
// 入场动画
enterHandle.start((modifier: AttributeUpdater<NavDestinationAttribute>) => {
modifier.attribute?.opacity(0);
}).finish((modifier: AttributeUpdater<NavDestinationAttribute>) => {
modifier.attribute?.opacity(1);
}).onFinish((modifier: AttributeUpdater<NavDestinationAttribute>) => {
modifier.attribute?.opacity(1);
});
enterHandle.duration = 300;
enterHandle.curve = Curve.EaseOut;
// 出场动画
exitHandle.start((modifier: AttributeUpdater<NavDestinationAttribute>) => {
modifier.attribute?.opacity(1);
}).finish((modifier: AttributeUpdater<NavDestinationAttribute>) => {
modifier.attribute?.opacity(0);
}).onFinish((modifier: AttributeUpdater<NavDestinationAttribute>) => {
modifier.attribute?.opacity(1);
});
exitHandle.duration = 300;
exitHandle.curve = Curve.EaseIn;
}
}
滑动转场动画
import { HMAnimator, HMAnimatorHandle, IHMAnimator } from "@hadss/hmrouter";
import { AttributeUpdater } from "@kit.ArkUI";
@HMAnimator({ animatorName: 'SlideAnimator' })
export class SlideAnimator implements IHMAnimator {
effect(enterHandle: HMAnimatorHandle, exitHandle: HMAnimatorHandle): void {
// 主动入场动画(push时目标页面)
enterHandle.start((modifier: AttributeUpdater<NavDestinationAttribute>) => {
modifier.attribute?.translate({ x: "100%" });
}).finish((modifier: AttributeUpdater<NavDestinationAttribute>) => {
modifier.attribute?.translate({ x: "0" });
}).onFinish((modifier: AttributeUpdater<NavDestinationAttribute>) => {
modifier.attribute?.translate({ x: "0" });
});
enterHandle.duration = 300;
enterHandle.curve = Curve.EaseOut;
// 被动出场动画(push时源页面)
exitHandle.passiveStart((modifier: AttributeUpdater<NavDestinationAttribute>) => {
modifier.attribute?.translate({ x: "0" });
}).passiveFinish((modifier: AttributeUpdater<NavDestinationAttribute>) => {
modifier.attribute?.translate({ x: "-30%" });
}).passiveOnFinish((modifier: AttributeUpdater<NavDestinationAttribute>) => {
modifier.attribute?.translate({ x: "0" });
});
exitHandle.duration = 300;
exitHandle.curve = Curve.EaseOut;
// 主动出场动画(pop时源页面)
exitHandle.start((modifier: AttributeUpdater<NavDestinationAttribute>) => {
modifier.attribute?.translate({ x: "0" });
}).finish((modifier: AttributeUpdater<NavDestinationAttribute>) => {
modifier.attribute?.translate({ x: "100%" });
}).onFinish((modifier: AttributeUpdater<NavDestinationAttribute>) => {
modifier.attribute?.translate({ x: "0" });
});
// 被动入场动画(pop时目标页面)
enterHandle.passiveStart((modifier: AttributeUpdater<NavDestinationAttribute>) => {
modifier.attribute?.translate({ x: "-30%" });
}).passiveFinish((modifier: AttributeUpdater<NavDestinationAttribute>) => {
modifier.attribute?.translate({ x: "0" });
}).passiveOnFinish((modifier: AttributeUpdater<NavDestinationAttribute>) => {
modifier.attribute?.translate({ x: "0" });
});
}
}
交互式转场动画
import { HMAnimator, HMAnimatorHandle, IHMAnimator } from "@hadss/hmrouter";
import { AttributeUpdater } from "@kit.ArkUI";
@HMAnimator({ animatorName: 'InteractiveAnimator' })
export class InteractiveAnimator implements IHMAnimator {
effect(enterHandle: HMAnimatorHandle, exitHandle: HMAnimatorHandle): void {
// 定义与上面SlideAnimator相同的动画效果
// 主动入场动画(push时目标页面)
enterHandle.start((modifier: AttributeUpdater<NavDestinationAttribute>) => {
modifier.attribute?.translate({ x: "100%" });
}).finish((modifier: AttributeUpdater<NavDestinationAttribute>) => {
modifier.attribute?.translate({ x: "0" });
}).onFinish((modifier: AttributeUpdater<NavDestinationAttribute>) => {
modifier.attribute?.translate({ x: "0" });
});
// 其余动画设置...
}
interactive(handle: HMAnimatorHandle): void {
// 手势开始事件
handle.actionStart((event: GestureEvent, proxy: NavigationTransitionProxy, operation: string, startOffset?: { width: number, height: number }) => {
if (!startOffset) {
return;
}
// 根据手势方向判断是push还是pop
if (event.offsetX > 0) {
// 向右滑动,执行pop操作
proxy.pop();
} else if (event.offsetX < 0 && operation === 'push') {
// 向左滑动,执行push操作
proxy.push();
}
});
// 手势更新事件
handle.updateProgress((event: GestureEvent, progress: { value: number }, startOffset?: { width: number, height: number }) => {
if (!startOffset) {
return;
}
// 计算进度百分比
let percent = Math.abs(event.offsetX) / startOffset.width;
percent = Math.min(1, percent);
// 设置动画进度
progress.value = percent;
});
// 手势结束事件
handle.actionEnd((event: GestureEvent, proxy: NavigationTransitionProxy, operation: string, startOffset?: { width: number, height: number }) => {
if (!startOffset) {
return;
}
// 判断是完成转场还是取消转场
let percent = Math.abs(event.offsetX) / startOffset.width;
if (percent > 0.3) {
// 超过30%,完成转场
proxy.finish();
} else {
// 未超过30%,取消转场
proxy.cancel();
}
});
}
}
自定义复杂动画
import { HMAnimator, HMAnimatorHandle, IHMAnimator } from "@hadss/hmrouter";
import { AttributeUpdater } from "@kit.ArkUI";
@HMAnimator({ animatorName: 'CustomComplexAnimator' })
export class CustomComplexAnimator implements IHMAnimator {
effect(enterHandle: HMAnimatorHandle, exitHandle: HMAnimatorHandle): void {
// 使用customAnimation完全控制动画
enterHandle.customAnimation((progress: number, modifier: AttributeUpdater<NavDestinationAttribute>) => {
// 根据进度计算动画参数
modifier.attribute?.translate({ y: `${(1 - progress) * 100}%` })
.scale({ x: 0.8 + 0.2 * progress, y: 0.8 + 0.2 * progress })
.opacity(progress);
});
exitHandle.passiveCustomAnimation((progress: number, modifier: AttributeUpdater<NavDestinationAttribute>) => {
// 根据进度计算动画参数
modifier.attribute?.scale({ x: 1 - 0.1 * progress, y: 1 - 0.1 * progress })
.opacity(1 - 0.5 * progress);
});
enterHandle.duration = 400;
enterHandle.curve = Curve.Friction;
}
}
使用自定义动画
全局动画设置
// 在HMNavigation中设置
HMNavigation({
navigationId: 'mainNavigation',
homePageUrl: 'HomePage',
options: {
standardAnimator: 'SlideAnimator',
dialogAnimator: 'FadeAnimator'
}
})
// 或者通过HMRouterMgr设置
HMRouterMgr.setGlobalStandardAnimator('SlideAnimator', 'mainNavigation');
HMRouterMgr.setGlobalDialogAnimator('FadeAnimator', 'mainNavigation');
页面级动画设置
@HMRouter({
pageUrl: 'DetailPage',
animator: 'SlideAnimator'
})
@Component
export struct DetailPage {
// ...
}
跳转时指定动画
// 使用动画名称
HMRouterMgr.push({
pageUrl: 'DetailPage',
animator: 'SlideAnimator'
});
// 使用动画实例
const animator = new SlideAnimator();
HMRouterMgr.push({
pageUrl: 'DetailPage',
animator: animator
});
内置动画
HMRouter提供了一些内置的动画效果,可以直接使用:
标准页面动画
// 使用内置标准动画
HMNavigation({
navigationId: 'mainNavigation',
homePageUrl: 'HomePage',
options: {
standardAnimator: HMDefaultGlobalAnimator.STANDARD_ANIMATOR
}
})
对话框动画
// 使用内置对话框动画
HMNavigation({
navigationId: 'mainNavigation',
homePageUrl: 'HomePage',
options: {
dialogAnimator: HMDefaultGlobalAnimator.DIALOG_ANIMATOR
}
})
注意事项
- 动画效果应该考虑双向性,即push和pop的动画效果应该是对称的
- 动画持续时间不宜过长,一般建议在300-500ms之间
- 交互式动画需要考虑手势取消的情况,确保用户体验的连贯性
- 复杂动画可能会影响性能,应进行充分测试
- 全局动画变更不会影响已加载的页面,只有页面重新加载才会生效