开发者可以通过设置自定义转场动画来实现NavDestination页面切换时的动效。Navigation有两种自定义动画,基于Navigation设置的自定义动画(全局控制当前Navigation中所有的NavDestination),以及基于NavDestination的自定义动画(单点控制单个NavDestination),下文会分别介绍。
1、Navigation自定义动画
1.1 准备工作
开发者需要声明自定义动画的管理类,同时创建全局单实例的CustomTransition,以便于管理自定义动画的参数注册和获取,如下是一个简单的示例,开发者也可根据自己的业务需求去实现:
// CustomNavigationUtils.ts 工具类,用来管理所有页面的自定义动画参数注册和获取等
// 自定义接口,用来保存某个页面相关的转场动画回调和参数
export interface AnimateCallback {
start: ((isPush: boolean, isExit: boolean) => void | undefined) | undefined;
finish: ((isPush: boolean, isExit: boolean) => void | undefined) | undefined;
}
const customTransitionMap: Map<string, AnimateCallback> = new Map();
export class CustomTransition {
static delegate = new CustomTransition();
static getInstance() {
return CustomTransition.delegate;
}
/* 注册某个页面的动画回调
* name: 注册页面的唯一id
* startCallback:用来设置动画开始时页面的状态
* endCallback:用来设置动画结束时页面的状态
*/
registerNavParam(name: string, startCallback: (isPush: boolean, isExit: boolean) => void,
endCallback: (isPush: boolean, isExit: boolean) => void): void {
if (customTransitionMap.has(name)) {
let param = customTransitionMap.get(name);
if (param != undefined) {
param.start = startCallback;
param.finish = endCallback;
return;
}
}
let params: AnimateCallback = { start: startCallback, finish: endCallback };
customTransitionMap.set(name, params);
}
unRegisterNavParam(name: string): void {
customTransitionMap.delete(name);
}
getAnimateParam(name: string): AnimateCallback {
let result: AnimateCallback = {
start: customTransitionMap.get(name)?.start,
finish: customTransitionMap.get(name)?.finish
};
return result;
}
}
1.2 代码实现
对于Navigation组件,需要设置属性customNavContentTransition,该属性需要传入NavigationAnimatedTransition类型的参数delegate。在路由操作发生时,系统侧会回调至delegate,并将页面切换的from、to以及操作类型信息返回给该函数作为入参。开发者可以在delete中根据系统回传的信息,来决定执行什么类型的自定义动画,如下示例:
// NavigationCustomAniMain.ets
@Component
struct NavigationCustomTransitionExample {
pageInfos: NavPathStack = new NavPathStack();
aboutToAppear() {
this.pageInfos.pushPath({ name: 'navigationAniPageOne' }, false);
}
build() {
Navigation(this.pageInfos) {
}
.hideNavBar(true)
// 实现属性customNavContentTransition,以定义动画的规则
.customNavContentTransition((from: NavContentInfo, to: NavContentInfo, operation: NavigationOperation) => {
// 首页不进行自定义动画
if (from.index === -1 || to.index === -1) {
return undefined;
}
let customAnimation: NavigationAnimatedTransition = {
timeout: 2000,
// 转场开始时系统调用该方法,并传入转场上下文代理对象
transition: (transitionProxy: NavigationTransitionProxy) => {
if (!from.navDestinationId || !to.navDestinationId) {
return;
}
// 从封装类CustomTransition中根据子页面的序列获取对应的转场动画回调
let fromParam: AnimateCallback = CustomTransition.getInstance().getAnimateParam(from.navDestinationId);
let toParam: AnimateCallback = CustomTransition.getInstance().getAnimateParam(to.navDestinationId);
// Push动画
if (operation == NavigationOperation.PUSH) {
if (fromParam.start && toParam.start) {
// 设置Push转场的两个页面的动画起点
fromParam.start(true, true);
toParam.start(true, false);
}
this.getUIContext()?.animateTo({
duration: 500, curve: Curve.Friction, onFinish: () => {
// 动画结束后需要手动调用finishTransition,否则在timeout时间后由系统调用
transitionProxy.finishTransition();
}
}, () => {
if (fromParam.finish && toParam.finish) {
// 设置Push转场的两个页面的动画终点
fromParam.finish(true, true);
toParam.finish(true, false);
}
})
} else if (operation == NavigationOperation.POP) {
// Pop动画
if (fromParam.start && toParam.start) {
// 设置Pop转场的两个页面的动画起点
fromParam.start(false, true);
toParam.start(false, false);
}
this.getUIContext()?.animateTo({
duration: 500, curve: Curve.Friction, onFinish: () => {
// 动画结束后需要手动调用finishTransition,否则在timeout时间后由系统调用
transitionProxy.finishTransition();
}
}, () => {
if (fromParam.finish && toParam.finish) {
// 设置Pop转场的两个页面的动画终点
fromParam.finish(false, true);
toParam.finish(false, false);
}
})
} else {
// Replace不做动画
}
}
};
return customAnimation;
})
}
}
同时,开发者需要为每个NavDestination注册页面自身的动画参数,以供Navigation.customNavContentTransition回调内部的逻辑调用,例如:
// NavigationCustomAniMain.ets
@Component
export struct PageContainer {
pageInfos: NavPathStack = new NavPathStack();
@State translateY: string = '0';
pageId: string = '';
title: string = ''
registerCallback() {
CustomTransition.getInstance().registerNavParam(this.pageId,
// 设置转场动画起点,根据不同的转场类型分别设置
(isPush: boolean, isExit: boolean) => {
if (isPush) {
if (isExit) {
this.translateY = '0';
} else {
this.translateY = '100%';
}
} else {
if (isExit) {
this.translateY = '0';
} else {
this.translateY = '0';
}
}
},
// 设置转场动画终点,根据不同的转场类型分别设置
(isPush: boolean, isExit: boolean) => {
if (isPush) {
if (isExit) {
this.translateY = '0';
} else {
this.translateY = '0';
}
} else {
if (isExit) {
this.translateY = '100%';
} else {
this.translateY = '0';
}
}
});
}
build() {
NavDestination() {
Column() {
Button('push next page')
.width(330)
.margin(7)
.onClick(() => {
this.pageInfos.pushPath({ name: this.title == 'PageOne' ? "navigationAniPageTwo" : "navigationAniPageOne" });
})
}
.size({ width: '100%', height: '100%' })
}
.title(this.title)
.onDisAppear(() => {
// 页面销毁时解注册自定义转场动画参数
CustomTransition.getInstance().unRegisterNavParam(this.pageId);
})
.onReady((context: NavDestinationContext) => {
this.pageInfos = context.pathStack;
if (context.navDestinationId) {
this.pageId = context.navDestinationId;
// 页面创建时注册自定义转场动画参数
this.registerCallback();
}
})
.translate({ y: this.translateY })
.backgroundColor(this.title == 'PageOne' ? '# F1F3F5' : '# ff11dee5')
}
}
2、NavDestination自定义动画
开发者如果仅想控制单个NavDestination的转场动画,而无需关心其他NavDestination的动画时,可以通过在NavDestination中设置customTransition属性来为当前页面配置自定义动画。该属性接收类型为NavDestinationTransitionDelegate的入参作为动画代理。与Navigation动画类似,动画代理会在页面切换时执行相,并发起相应的自定义动画,例如如下设置的NavDestination组件:
// CustomNavDest.ets
@Component
struct CustomAniNavDestination {
name: string = 'NA'
stack: NavPathStack = new NavPathStack()
@State translateY: string = '0';
build() {
NavDestination() {
Button('push next page')
.width(330)
.margin(7)
.onClick(() => {
this.stack.pushPath({ name: this.name == 'PageOne' ? "navDestCustomAniPageTwo" : "navDestCustomAniPageOne" });
})
}
.translate({ y: this.translateY })
.backgroundColor(this.name == 'PageOne' ? '# F1F3F5' : '# ff11dee5')
.onReady((context) => {
this.stack = context.pathStack
})
.title(this.name)
.customTransition(
(op: NavigationOperation, isEnter: boolean)
: Array<NavDestinationTransition> | undefined => {
console.log('[NavDestinationTransition]', 'reached delegate in frontend, op: ' + op + ', isEnter: ' + isEnter);
// navDestination动画的事件。navDestination自定义动画会基于当前组件的UI状态与事件执行后的UI状态来发起动画。
let transitionOneEvent = () => { console.log('[NavDestinationTransition]', 'reached transitionOne, empty now!'); }
if (op === NavigationOperation.PUSH) {
if (isEnter) {
// ENTER_PUSH
// 为入场的PUSH页面设置动画
console.log('[NavDestinationTransition]', 'push & isEnter, init y to 100%');
this.translateY = '100%';
transitionOneEvent = () => {
console.log('[NavDestinationTransition]', 'push & isEnter, finally set y to 0');
this.translateY = '0';
}
}
} else if (op === NavigationOperation.POP) {
if (!isEnter) {
// EXIT_POP
// 为离场的POP页面设置动画
console.log('[NavDestinationTransition]', 'pop & isExit, init y to 0');
this.translateY = '0';
transitionOneEvent = () => {
console.log('[NavDestinationTransition]', 'pop & isExit, finally set y to 100%');
this.translateY = '100%';
}
}
} else if (op === NavigationOperation.REPLACE) {
console.log('[NavDestinationTransition]', 'REPLACE not support');
} else {
console.log('[NavDestinationTransition]', 'invalid operation!');
}
// 根据以上逻辑,确定动画的行为,并创建类型为NavDestinationTransition的动画实例transitionOne
let transitionOne: NavDestinationTransition = {
duration: 500,
delay: 0,
curve: Curve.Friction,
event: transitionOneEvent,
onTransitionEnd: () => { console.log('[NavDestinationTransition]', 'reached transitionOneFinish, empty now!'); }
};
// 将transitionOne作为返回值传递给系统,系统侧将发起transitionOne对应的动画
return [ transitionOne ];
})
}
}
3、动画效果演示
上面章节展示的自定义动画均如下图所示

4、示例源码
(链接待补)
2058

被折叠的 条评论
为什么被折叠?



