1. 前言
本文不会介绍基础使用,若完全不了解,可先看 官方文档, 不会有比此介绍更加详细的了.
本文主要当下(beta5/Api12)使用时出现的问题以及一些实用技巧.
直接上干货
2. NavPathStack#.popToName
- beta5/api12
2.1 不能夸页面栈带参返回
在Navigation的实际功能开发的时候,会有一种页面栈操作,即:
sequenceDiagram
A页面 -> B页面: 启动 B 页面
B页面 -> C页面: 启动 C 页面
C页面 --> A页面: 返回 A 页面
但是目前(api12)不支持从C页面携带返回参数直接给到A页面
// C PAGE
// 略 ...
@Provide({ allowOverride: 'pageStack' }) pageStack: NavPathStack = new NavPathStack()
Button('RETURN A').onClick(() => {
this.pageStack.popToName('BPage', {
arg1: 'THIS IS THE RETURN ARG, ORIGIN C PAGE',
arg2: '页面回到A页面, 但是pop参数仅返回B页面'
})
})
// 略 ...
// A页面接收返回参数
this.pageStack.pushDestinationByName('BPage', {}, pop => {
console.error('PAGE(A), call pop >>>', JSON.stringify(pop))
}, true)
// B页面接收返回参数
this.pageStack.pushDestinationByName('CPage', {}, pop => {
console.error('PAGE(B), call pop >>>', JSON.stringify(pop))
}, true)
点击后Debugger
虽然页面回到了A, 但是返回参数在 B页面的pop回调中被触发了, A页面没有任何反应.
针对此问题向华为提交了 issue, 但是得到的答复令我无语..
2.2 解决方案
既然华为不认为是问题,那就一定有解决方法.直接上代码
namespace route {
export function pop(stack: NavPathStack, resultJsonStr?: string, animated?: boolean): NavPathInfo | undefined {
if (resultJsonStr && resultJsonStr.length > 0) {
try {
return stack.pop(JSON.parse(resultJsonStr) as Record<string, Object>, animated)
} catch (e) {
console.error('popToName, JSON Parse ERROR: ', JSON.stringify(e))
// Q: 为什么要传递空对象
// A: unknown时pop接收方将不会被触发pop回调方法
return stack.pop({}, animated)
}
} else {
return stack.pop({}, animated)
}
}
/**
* 跳转路由到之前的指定页面
*
* 如果多实例页面可能会有问题, 暂未验证过
*/
export function popToName(stack: NavPathStack, name: string, resultJsonStr?: string,
animated?: boolean): Promise<NavPathInfo | undefined> {
return new Promise(resolve => {
if (stack.size() == 0) {
resolve(pop(stack, resultJsonStr, animated))
return
}
const previousIndexArray = stack.getIndexByName(name)
if (!previousIndexArray || previousIndexArray.length == 0) {
// 可能传入的参数错误
resolve(pop(stack, resultJsonStr, animated))
return
}
let startIndex = previousIndexArray.length > 0 ? previousIndexArray[previousIndexArray.length - 1] : 0
// Q: 为什么 + 2
// A: 1. getIndexByName 只能获取目标页之前的 index 数组, 例如: [0, 1, 2] 下标2是目标页面, 但是获取到只会是 [0, 1];
// 2. 要想跳转并且pop带参数, 目标页面index的下一个index 携带 pop参数, 目标页才能触发 pop 回调;
startIndex += 2
const len = stack.size()
if (startIndex < len) {
let removeArray: number[] = []
for (let i = startIndex; i < len; i++) {
if (i != 0) {
removeArray.push(i)
}
}
stack.removeByIndexes(removeArray)
}
resolve(pop(stack, resultJsonStr, animated))
})
}
}
调用方法
// C页面
@Provide({ allowOverride: 'pageStack' }) pageStack: NavPathStack = new NavPathStack()
Button('RETURN TOP PAGE(RIGHT)').onClick(() => {
route.popToName(this.pageStack, 'APage', JSON.stringify({
arg1: 'THIS IS THE RETURN ARG, ORIGIN C PAGE',
arg2: '页面回到A页面, pop参数可以带回A页面'
}))
})
3. NavDestinationMode.DIALOG
- beta5/api12
3.1 下级页面无动画
当启动顺序如下时: Navigation -> NavDestination(DIALOG) -> NavDestination(STANDARD)
NavDestination(STANDARD) 的页面启动时无转场动画,直接出现在屏幕中
3.2 解决方案
这是我修改后的代码, 官方提供的运行时有一些问题
// CustomNavigationUtils.ts
import { curves } from '@kit.ArkUI';
// 自定义接口,用来保存某个页面相关的转场动画回调和参数
export interface AnimateCallback {
finish: ((isPush: boolean, isExit: boolean) => void | undefined) | undefined;
start: ((isPush: boolean, isExit: boolean) => void | undefined) | undefined;
onFinish: ((isPush: boolean, isExit: boolean) => void | undefined) | undefined;
interactive: ((operation: NavigationOperation) => void | undefined) | undefined;
timeout: (number | undefined) | undefined;
}
export interface NavParam {
name: string,
startCallback?: (operation: boolean, isExit: boolean) => void,
endCallback?: (operation: boolean, isExit: boolean) => void,
onFinish?: (operation: boolean, isExit: boolean) => void,
interactiveCallback?: (operation: NavigationOperation) => void,
timeout?: number
}
const customTransitionMap: Map<string, AnimateCallback> = new Map();
export default class CustomTransition {
static delegate = new CustomTransition();
interactive: boolean = false;
proxy: NavigationTransitionProxy | undefined = undefined;
private animationId: number = 0;
operation: NavigationOperation = NavigationOperation.PUSH
static S() {
return CustomTransition.delegate;
}
/* 注册某个页面的动画回调
* name: 注册页面的唯一id
* startCallback:用来设置动画开始时页面的状态
* endCallback:用来设置动画结束时页面的状态
* onFinish:用来执行动画结束后页面的其他操作
* interactiveCallback: 注册的可交互转场的动效
* timeout:转场结束的超时时间
*/
registerNavParam($$: NavParam): void {
if (customTransitionMap.has($$.name)) {
let param = customTransitionMap.get($$.name);
if (param != undefined) {
param.start = $$.startCallback;
param.finish = $$.endCallback;
param.timeout = $$.timeout;
param.onFinish = $$.onFinish;
param.interactive = $$.interactiveCallback;
return;
}
}
let params: AnimateCallback = {
timeout: $$.timeout,
start: $$.startCallback,
finish: $$.endCallback,
onFinish: $$.onFinish,
interactive: $$.interactiveCallback
};
customTransitionMap.set($$.name, params);
}
getAnimationId() {
return Date.now();
}
unRegisterNavParam(name: string): void {
customTransitionMap.delete(name);
}
fireInteractiveAnimation(id: string, operation: NavigationOperation) {
let animation = customTransitionMap.get(id)?.interactive;
if (!animation) {
return;
}
animation(operation);
}
updateProgress(progress: number) {
if (!this.proxy?.updateTransition) {
return;
}
progress = this.operation == NavigationOperation.PUSH ? 1 - progress : progress;
this.proxy?.updateTransition(progress);
}
cancelTransition() {
if (this.proxy?.cancelTransition) {
this.proxy.cancelTransition();
}
}
recoverState() {
if (!this.proxy?.from.navDestinationId || !this.proxy?.to.navDestinationId) {
return;
}
let fromParam = customTransitionMap.get(this.proxy.from.navDestinationId);
if (fromParam?.onFinish) {
fromParam.onFinish(false, false);
}
let toParam = customTransitionMap.get(this.proxy?.to.navDestinationId);
if (toParam?.onFinish) {
toParam.onFinish(true, true);
}
}
finishTransition() {
this.proxy?.finishTransition();
}
finishInteractiveAnimation(rate: number) {
if (this.operation == NavigationOperation.PUSH) {
if (rate > 0.5) {
if (this.proxy?.cancelTransition) {
this.proxy.cancelTransition();
}
} else {
this.proxy?.finishTransition();
}
} else {
if (rate > 0.5) {
this.proxy?.finishTransition();
} else {
if (this.proxy?.cancelTransition) {
this.proxy.cancelTransition();
}
}
}
}
getAnimateParam(name: string): AnimateCallback {
let result: AnimateCallback = {
start: customTransitionMap.get(name)?.start,
finish: customTransitionMap.get(name)?.finish,
timeout: customTransitionMap.get(name)?.timeout,
onFinish: customTransitionMap.get(name)?.onFinish,
interactive: customTransitionMap.get(name)?.interactive,
};
return result;
}
}
export function CustomNavContentTransition(from: NavContentInfo, to: NavContentInfo,
operation: NavigationOperation): NavigationAnimatedTransition | undefined {
// 首页不进行自定义动画
if (from.index === -1 || to.index === -1) {
return undefined;
}
CustomTransition.S().operation = operation;
if (CustomTransition.S().interactive) {
let customAnimation: NavigationAnimatedTransition = {
onTransitionEnd: (isSuccess: boolean) => {
console.log("===== current transition is " + isSuccess);
CustomTransition.S().recoverState();
CustomTransition.S().proxy = undefined;
},
transition: (transitionProxy: NavigationTransitionProxy) => {
CustomTransition.S().proxy = transitionProxy;
let targetIndex: string | undefined = operation == NavigationOperation.PUSH ?
(to.navDestinationId) : (from.navDestinationId);
if (targetIndex) {
CustomTransition.S().fireInteractiveAnimation(targetIndex, operation);
}
},
isInteractive: CustomTransition.S().interactive
}
return customAnimation;
}
let customAnimation: NavigationAnimatedTransition = {
onTransitionEnd: (isSuccess: boolean) => {
console.log(`current transition result is ${isSuccess}`)
},
timeout: 3000,
// 转场开始时系统调用该方法,并传入转场上下文代理对象
transition: (transitionProxy: NavigationTransitionProxy) => {
if (!from.navDestinationId || !to.navDestinationId) {
return;
}
// 从封装类CustomTransition中根据子页面的序列获取对应的转场动画回调
let fromParam: AnimateCallback = CustomTransition.S().getAnimateParam(from.navDestinationId);
let toParam: AnimateCallback = CustomTransition.S().getAnimateParam(to.navDestinationId);
if (operation == NavigationOperation.PUSH) {
if (toParam.start) {
toParam.start(true, false);
}
animateTo({
curve: curves.springMotion(),
duration: toParam.timeout || 300,
onFinish: () => {
transitionProxy.finishTransition();
}
}, () => {
if (toParam.finish) {
toParam.finish(true, false);
}
})
} else {
if (fromParam.start) {
fromParam.start(true, true);
}
animateTo({
curve: curves.springMotion(),
duration: fromParam.timeout || 300,
onFinish: () => {
transitionProxy.finishTransition();
}
}, () => {
if (fromParam.finish) {
fromParam.finish(true, true);
}
})
}
}
};
return customAnimation;
}
/*
// EXAMPLE
CustomTransition.S().registerNavParam({
name: this.navDestinationId,
startCallback: (isPush: boolean, isExit: boolean) => {
this.customTranslateX = isPush ? '100%' : 0
},
endCallback: (isPush: boolean, isExit: boolean) => {
this.customTranslateX = isPush ? 0 : '100%'
},
onFinish: (isPush: boolean, isExit: boolean) => {
this.customTranslateX = 0
},
interactiveCallback: (operation: NavigationOperation) => {
if (operation == NavigationOperation.PUSH) {
this.customTranslateX = '100%';
animateTo({
duration: 1000,
onFinish: () => {
this.customTranslateX = 0;
}
}, () => {
this.customTranslateX = 0;
})
} else {
this.customTranslateX = 0;
animateTo({
duration: 1000,
onFinish: () => {
this.customTranslateX = 0;
}
}, () => {
this.customTranslateX = '100%';
})
}
},
timeout: 200
})
if (typeof this.navDestinationId === 'string') {
CustomTransition.S().unRegisterNavParam(this.navDestinationId)
}
*/
3.3 使用方法
Navigation(this.weboxPageStack)
.customNavContentTransition(CustomNavContentTransition)
@Prop navDestinationId?: string = undefined
NavDestination()
.onReady((context: NavDestinationContext) => {
this.navDestinationId = context.navDestinationId;
if (typeof this.navDestinationId === 'string') {
CustomTransition.S().registerNavParam({
name: this.navDestinationId,
startCallback: (isPush: boolean, isExit: boolean) => {
this.customTranslateY = isPush && !isExit ? '100%' : 0
},
endCallback: (isPush: boolean, isExit: boolean) => {
this.customTranslateY = isPush && !isExit ? 0 : '100%'
},
})
})
.onDisAppear(() => {
if (typeof this.navDestinationId === 'string') {
CustomTransition.S().unRegisterNavParam(this.navDestinationId)
}
})