mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-18 05:18:39 +08:00
fix(android): suppress reflection for default animations (#6141)
Fixes `Error: java.lang.CloneNotSupportedException: Class android.support.v4.app.FragmentManagerImpl$AnimationOrAnimator doesn't implement Cloneable` in specific projects. Related to #5785 Related to #6129 BREAKING CHANGE Before: Default fragment enter animation was Android version specific After: Default fragment enter animation is now fade animation for all Android versions You can customise the transition per navigation entry or globally via the [navigation transitions API]( https://docs.nativescript.org/core-concepts/navigation#navigation-transitions)
This commit is contained in:
@ -50,17 +50,12 @@ interface ExpandedEntry extends BackstackEntry {
|
|||||||
|
|
||||||
const sdkVersion = lazy(() => parseInt(device.sdkVersion));
|
const sdkVersion = lazy(() => parseInt(device.sdkVersion));
|
||||||
const defaultInterpolator = lazy(() => new android.view.animation.AccelerateDecelerateInterpolator());
|
const defaultInterpolator = lazy(() => new android.view.animation.AccelerateDecelerateInterpolator());
|
||||||
const isAndroidP = lazy(() => sdkVersion() > 27);
|
|
||||||
|
|
||||||
export const waitingQueue = new Map<number, Set<ExpandedEntry>>();
|
export const waitingQueue = new Map<number, Set<ExpandedEntry>>();
|
||||||
export const completedEntries = new Map<number, ExpandedEntry>();
|
export const completedEntries = new Map<number, ExpandedEntry>();
|
||||||
|
|
||||||
let TransitionListener: TransitionListener;
|
let TransitionListener: TransitionListener;
|
||||||
let AnimationListener: android.view.animation.Animation.AnimationListener;
|
let AnimationListener: android.view.animation.Animation.AnimationListener;
|
||||||
let loadAnimationMethod: java.lang.reflect.Method;
|
|
||||||
let reflectionDone: boolean;
|
|
||||||
let defaultEnterAnimationStatic: android.view.animation.Animation;
|
|
||||||
let defaultExitAnimationStatic: android.view.animation.Animation;
|
|
||||||
|
|
||||||
export function _setAndroidFragmentTransitions(
|
export function _setAndroidFragmentTransitions(
|
||||||
animated: boolean,
|
animated: boolean,
|
||||||
@ -68,7 +63,6 @@ export function _setAndroidFragmentTransitions(
|
|||||||
currentEntry: ExpandedEntry,
|
currentEntry: ExpandedEntry,
|
||||||
newEntry: ExpandedEntry,
|
newEntry: ExpandedEntry,
|
||||||
fragmentTransaction: android.support.v4.app.FragmentTransaction,
|
fragmentTransaction: android.support.v4.app.FragmentTransaction,
|
||||||
manager: android.support.v4.app.FragmentManager,
|
|
||||||
frameId: number): void {
|
frameId: number): void {
|
||||||
|
|
||||||
const currentFragment: android.support.v4.app.Fragment = currentEntry ? currentEntry.fragment : null;
|
const currentFragment: android.support.v4.app.Fragment = currentEntry ? currentEntry.fragment : null;
|
||||||
@ -78,10 +72,6 @@ export function _setAndroidFragmentTransitions(
|
|||||||
throw new Error("Calling navigation before previous navigation finish.");
|
throw new Error("Calling navigation before previous navigation finish.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isAndroidP()) {
|
|
||||||
initDefaultAnimations(manager);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sdkVersion() >= 21) {
|
if (sdkVersion() >= 21) {
|
||||||
allowTransitionOverlap(currentFragment);
|
allowTransitionOverlap(currentFragment);
|
||||||
allowTransitionOverlap(newFragment);
|
allowTransitionOverlap(newFragment);
|
||||||
@ -120,11 +110,7 @@ export function _setAndroidFragmentTransitions(
|
|||||||
if (name === "none") {
|
if (name === "none") {
|
||||||
transition = new NoTransition(0, null);
|
transition = new NoTransition(0, null);
|
||||||
} else if (name === "default") {
|
} else if (name === "default") {
|
||||||
if (isAndroidP()) {
|
transition = new FadeTransition(150, null);
|
||||||
transition = new FadeTransition(150, null);
|
|
||||||
} else {
|
|
||||||
transition = new DefaultTransition(0, null);
|
|
||||||
}
|
|
||||||
} else if (useLollipopTransition) {
|
} else if (useLollipopTransition) {
|
||||||
// setEnterTransition: Enter
|
// setEnterTransition: Enter
|
||||||
// setExitTransition: Exit
|
// setExitTransition: Exit
|
||||||
@ -178,11 +164,7 @@ export function _setAndroidFragmentTransitions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAndroidP()) {
|
setupDefaultAnimations(newEntry, new FadeTransition(150, null));
|
||||||
setupDefaultAnimations(newEntry, new FadeTransition(150, null));
|
|
||||||
} else {
|
|
||||||
setupDefaultAnimations(newEntry, new DefaultTransition(0, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
printTransitions(currentEntry);
|
printTransitions(currentEntry);
|
||||||
printTransitions(newEntry);
|
printTransitions(newEntry);
|
||||||
@ -739,46 +721,6 @@ function toShortString(nativeTransition: android.transition.Transition): string
|
|||||||
return `${nativeTransition.getClass().getSimpleName()}@${nativeTransition.hashCode().toString(16)}`;
|
return `${nativeTransition.getClass().getSimpleName()}@${nativeTransition.hashCode().toString(16)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function javaObjectArray(...params: java.lang.Object[]) {
|
|
||||||
const nativeArray = Array.create(java.lang.Object, params.length);
|
|
||||||
params.forEach((value, i) => nativeArray[i] = value);
|
|
||||||
return nativeArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
function javaClassArray(...params: java.lang.Class<any>[]) {
|
|
||||||
const nativeArray = Array.create(java.lang.Class, params.length);
|
|
||||||
params.forEach((value, i) => nativeArray[i] = value);
|
|
||||||
return nativeArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
function initDefaultAnimations(manager: android.support.v4.app.FragmentManager): void {
|
|
||||||
if (reflectionDone) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
reflectionDone = true;
|
|
||||||
|
|
||||||
loadAnimationMethod = manager.getClass().getDeclaredMethod("loadAnimation", javaClassArray(android.support.v4.app.Fragment.class, java.lang.Integer.TYPE, java.lang.Boolean.TYPE, java.lang.Integer.TYPE));
|
|
||||||
if (loadAnimationMethod != null) {
|
|
||||||
loadAnimationMethod.setAccessible(true);
|
|
||||||
|
|
||||||
const fragment_open = java.lang.Integer.valueOf(android.support.v4.app.FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
|
|
||||||
const zero = java.lang.Integer.valueOf(0);
|
|
||||||
const fragment = new android.support.v4.app.Fragment();
|
|
||||||
|
|
||||||
// Get default enter transition.
|
|
||||||
defaultEnterAnimationStatic = loadAnimationMethod.invoke(manager, javaObjectArray(fragment, fragment_open, java.lang.Boolean.TRUE, zero));
|
|
||||||
|
|
||||||
// Get default exit transition.
|
|
||||||
defaultExitAnimationStatic = loadAnimationMethod.invoke(manager, javaObjectArray(fragment, fragment_open, java.lang.Boolean.FALSE, zero));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDefaultAnimation(enter: boolean): android.view.animation.Animation {
|
|
||||||
const defaultAnimation = enter ? defaultEnterAnimationStatic : defaultExitAnimationStatic;
|
|
||||||
return defaultAnimation ? defaultAnimation.clone() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDummyZeroDurationAnimation(): android.view.animation.Animation {
|
function createDummyZeroDurationAnimation(): android.view.animation.Animation {
|
||||||
// NOTE: returning the dummy AlphaAnimation directly does not work for some reason;
|
// NOTE: returning the dummy AlphaAnimation directly does not work for some reason;
|
||||||
// animationEnd is fired first, then some animationStart (but for a different animation?)
|
// animationEnd is fired first, then some animationStart (but for a different animation?)
|
||||||
@ -817,17 +759,3 @@ class NoTransition extends Transition {
|
|||||||
return createDummyZeroDurationAnimation();
|
return createDummyZeroDurationAnimation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DefaultTransition extends Transition {
|
|
||||||
public createAndroidAnimation(transitionType: string): android.view.animation.Animation {
|
|
||||||
switch (transitionType) {
|
|
||||||
case AndroidTransitionType.enter:
|
|
||||||
case AndroidTransitionType.popEnter:
|
|
||||||
return getDefaultAnimation(true);
|
|
||||||
|
|
||||||
case AndroidTransitionType.popExit:
|
|
||||||
case AndroidTransitionType.exit:
|
|
||||||
return getDefaultAnimation(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -24,7 +24,6 @@ export function _setAndroidFragmentTransitions(
|
|||||||
currentEntry: BackstackEntry,
|
currentEntry: BackstackEntry,
|
||||||
newEntry: BackstackEntry,
|
newEntry: BackstackEntry,
|
||||||
fragmentTransaction: any,
|
fragmentTransaction: any,
|
||||||
manager: any /* android.support.v4.app.FragmentManager */,
|
|
||||||
frameId: number): void;
|
frameId: number): void;
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
|
@ -337,10 +337,7 @@ export class Frame extends FrameBase {
|
|||||||
// https://github.com/NativeScript/NativeScript/issues/4895
|
// https://github.com/NativeScript/NativeScript/issues/4895
|
||||||
const navigationTransition = this._currentEntry ? this._getNavigationTransition(newEntry.entry) : null;
|
const navigationTransition = this._currentEntry ? this._getNavigationTransition(newEntry.entry) : null;
|
||||||
|
|
||||||
_setAndroidFragmentTransitions(animated, navigationTransition, currentEntry, newEntry, transaction, manager, this._android.frameId);
|
_setAndroidFragmentTransitions(animated, navigationTransition, currentEntry, newEntry, transaction, this._android.frameId);
|
||||||
// if (clearHistory) {
|
|
||||||
// deleteEntries(this.backStack);
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (currentEntry && animated && !navigationTransition) {
|
if (currentEntry && animated && !navigationTransition) {
|
||||||
transaction.setTransition(android.support.v4.app.FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
|
transaction.setTransition(android.support.v4.app.FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
|
||||||
|
Reference in New Issue
Block a user