From a4d7674abaac9f95f91112d0611d4f94eeb543dd Mon Sep 17 00:00:00 2001 From: Martin Guillon Date: Sat, 20 Feb 2021 21:28:09 +0100 Subject: [PATCH] fix(android): rewrote the transition system to allow it work correctly with GLSurfaceView and GLTextureView --- packages/core/ui/frame/fragment.android.ts | 4 + .../ui/frame/fragment.transitions.android.ts | 74 ++++++++----------- packages/core/ui/frame/frame-interfaces.ts | 1 + packages/core/ui/frame/index.android.ts | 50 +++++++------ 4 files changed, 64 insertions(+), 65 deletions(-) diff --git a/packages/core/ui/frame/fragment.android.ts b/packages/core/ui/frame/fragment.android.ts index a7233f115..35ce1ec0f 100644 --- a/packages/core/ui/frame/fragment.android.ts +++ b/packages/core/ui/frame/fragment.android.ts @@ -11,6 +11,10 @@ const FragmentClass = (org.nativescript.widgets.FragmentBase).extend('com.t return this._callbacks.onCreateAnimator(this, transit, enter, nextAnim, superProto.onCreateAnimator); }, + onCreateAnimation(transit: number, enter: boolean, nextAnim: number): android.view.animation.Animation { + return this._callbacks.onCreateAnimation(this, transit, enter, nextAnim, superProto.onCreateAnimation); + }, + onStop(): void { this._callbacks.onStop(this, superProto.onStop); }, diff --git a/packages/core/ui/frame/fragment.transitions.android.ts b/packages/core/ui/frame/fragment.transitions.android.ts index 9dacfaa00..5558184ab 100644 --- a/packages/core/ui/frame/fragment.transitions.android.ts +++ b/packages/core/ui/frame/fragment.transitions.android.ts @@ -8,6 +8,8 @@ import { FlipTransition } from '../transition/flip-transition'; import { _resolveAnimationCurve } from '../animation'; import lazy from '../../utils/lazy'; import { Trace } from '../../trace'; +import { FadeTransition } from 'ui/transition/fade-transition.android'; +import { SlideTransition } from 'ui/transition/slide-transition.android'; interface TransitionListener { new (entry: ExpandedEntry, transition: androidx.transition.Transition): ExpandedTransitionListener; @@ -66,43 +68,39 @@ export function _setAndroidFragmentTransitions(animated: boolean, navigationTran allowTransitionOverlap(newFragment); let name = ''; - let transition: Transition; + let customTransition: Transition; if (navigationTransition) { - transition = navigationTransition.instance; + customTransition = navigationTransition.instance; name = navigationTransition.name ? navigationTransition.name.toLowerCase() : ''; } if (!animated) { name = 'none'; - } else if (transition) { + } else if (customTransition) { name = 'custom'; - } else if (name.indexOf('slide') !== 0 && name !== 'fade' && name.indexOf('flip') !== 0 && name.indexOf('explode') !== 0) { + } else if (name.indexOf('slide') !== 0 && name !== 'fade' && name.indexOf('flip') !== 0) { // If we are given name that doesn't match any of ours - fallback to default. name = 'default'; } - let currentFragmentNeedsDifferentAnimation = false; if (currentEntry) { _updateTransitions(currentEntry); - if (currentEntry.transitionName !== name || currentEntry.transition !== transition || isNestedDefaultTransition) { + if (currentEntry.transitionName !== name || currentEntry.transition !== customTransition || isNestedDefaultTransition) { clearExitAndReenterTransitions(currentEntry, true); currentFragmentNeedsDifferentAnimation = true; } } - + let transition: Transition; if (name === 'none') { const noTransition = new NoTransition(0, null); - - // Setup empty/immediate animator when transitioning to nested frame for first time. - // Also setup empty/immediate transition to be executed when navigating back to this page. - // TODO: Consider removing empty/immediate animator when migrating to official androidx.fragment.app.Fragment:1.2. if (isNestedDefaultTransition) { - fragmentTransaction.setCustomAnimations(animFadeIn, animFadeOut); - setupAllAnimation(newEntry, noTransition); - setupNewFragmentCustomTransition({ duration: 0, curve: null }, newEntry, noTransition); - } else { - setupNewFragmentCustomTransition({ duration: 0, curve: null }, newEntry, noTransition); + // with androidx.fragment 1.3.0 the first fragment animation is not triggered + // so let's simulate a transition end + setTimeout(() => { + addToWaitingQueue(newEntry); + transitionOrAnimationCompleted(newEntry, null); + }); } newEntry.isNestedDefaultTransition = isNestedDefaultTransition; @@ -110,52 +108,40 @@ export function _setAndroidFragmentTransitions(animated: boolean, navigationTran if (currentFragmentNeedsDifferentAnimation) { setupCurrentFragmentCustomTransition({ duration: 0, curve: null }, currentEntry, noTransition); } - } else if (name === 'custom') { + } else if (customTransition) { setupNewFragmentCustomTransition( { - duration: transition.getDuration(), - curve: transition.getCurve(), + duration: customTransition.getDuration(), + curve: customTransition.getCurve(), }, newEntry, - transition + customTransition ); if (currentFragmentNeedsDifferentAnimation) { setupCurrentFragmentCustomTransition( { - duration: transition.getDuration(), - curve: transition.getCurve(), + duration: customTransition.getDuration(), + curve: customTransition.getCurve(), }, currentEntry, - transition + customTransition ); } } else if (name === 'default') { - setupNewFragmentFadeTransition({ duration: 150, curve: null }, newEntry); - if (currentFragmentNeedsDifferentAnimation) { - setupCurrentFragmentFadeTransition({ duration: 150, curve: null }, currentEntry); - } + transition = new FadeTransition(150, null); } else if (name.indexOf('slide') === 0) { - setupNewFragmentSlideTransition(navigationTransition, newEntry, name); - if (currentFragmentNeedsDifferentAnimation) { - setupCurrentFragmentSlideTransition(navigationTransition, currentEntry, name); - } + const direction = name.substr('slide'.length) || 'left'; //Extract the direction from the string + transition = new SlideTransition(direction, navigationTransition.duration, navigationTransition.curve); } else if (name === 'fade') { - setupNewFragmentFadeTransition(navigationTransition, newEntry); - if (currentFragmentNeedsDifferentAnimation) { - setupCurrentFragmentFadeTransition(navigationTransition, currentEntry); - } - } else if (name === 'explode') { - setupNewFragmentExplodeTransition(navigationTransition, newEntry); - if (currentFragmentNeedsDifferentAnimation) { - setupCurrentFragmentExplodeTransition(navigationTransition, currentEntry); - } + transition = new FadeTransition(navigationTransition.duration, navigationTransition.curve); } else if (name.indexOf('flip') === 0) { const direction = name.substr('flip'.length) || 'right'; //Extract the direction from the string - const flipTransition = new FlipTransition(direction, navigationTransition.duration, navigationTransition.curve); - - setupNewFragmentCustomTransition(navigationTransition, newEntry, flipTransition); + transition = new FlipTransition(direction, navigationTransition.duration, navigationTransition.curve); + } + if (transition) { + setupNewFragmentCustomTransition(navigationTransition, newEntry, transition); if (currentFragmentNeedsDifferentAnimation) { - setupCurrentFragmentCustomTransition(navigationTransition, currentEntry, flipTransition); + setupCurrentFragmentCustomTransition(navigationTransition, currentEntry, transition); } } diff --git a/packages/core/ui/frame/frame-interfaces.ts b/packages/core/ui/frame/frame-interfaces.ts index 522fe6c36..df2360fc0 100644 --- a/packages/core/ui/frame/frame-interfaces.ts +++ b/packages/core/ui/frame/frame-interfaces.ts @@ -88,6 +88,7 @@ export interface AndroidActivityCallbacks { export interface AndroidFragmentCallbacks { onHiddenChanged(fragment: any, hidden: boolean, superFunc: Function): void; onCreateAnimator(fragment: any, transit: number, enter: boolean, nextAnim: number, superFunc: Function): any; + onCreateAnimation(fragment: any, transit: number, enter: boolean, nextAnim: number): any; onCreate(fragment: any, savedInstanceState: any, superFunc: Function): void; onCreateView(fragment: any, inflater: any, container: any, savedInstanceState: any, superFunc: Function): any; onSaveInstanceState(fragment: any, outState: any, superFunc: Function): void; diff --git a/packages/core/ui/frame/index.android.ts b/packages/core/ui/frame/index.android.ts index 891db51a0..9f0e8a9a5 100644 --- a/packages/core/ui/frame/index.android.ts +++ b/packages/core/ui/frame/index.android.ts @@ -476,10 +476,10 @@ export class Frame extends FrameBase { _reverseTransitions(backstackEntry, this._currentEntry); - const currentIndex =this.backStack.length; + const currentIndex = this.backStack.length; const goBackToIndex = this.backStack.indexOf(backstackEntry); - - // the order is important so that the transition listener called be + + // the order is important so that the transition listener called be // the one from the current entry we are going back from if (this._currentEntry !== backstackEntry) { const entry = this._currentEntry as ExpandedEntry; @@ -489,8 +489,9 @@ export class Frame extends FrameBase { if (entry.returnTransitionListener) { entry.returnTransitionListener.backEntry = backstackEntry; } - - transaction.remove((this._currentEntry).fragment); + + // we only did hide the fragment to fix some black blick issues with GLSurfaceView and GLTextureView + transaction.hide(this._currentEntry.fragment); } for (let index = goBackToIndex + 1; index < currentIndex; index++) { transaction.remove(this.backStack[index].fragment); @@ -501,8 +502,13 @@ export class Frame extends FrameBase { public _removeEntry(removed: BackstackEntry): void { super._removeEntry(removed); - if (removed.fragment) { + // we only did hide the fragment to fix some black blick issues with GLSurfaceView and GLTextureView + // so lets remove it + const manager: androidx.fragment.app.FragmentManager = this._getFragmentManager(); + const transaction = manager.beginTransaction(); + transaction.remove(removed.fragment); + transaction.commitAllowingStateLoss(); _clearEntry(removed); } @@ -788,11 +794,11 @@ function findPageForFragment(fragment: androidx.fragment.app.Fragment, frame: Fr } else if (executingContext && executingContext.entry && executingContext.entry.fragmentTag === fragmentTag) { entry = executingContext.entry; } else { - frame.backStack.forEach(e=>{ + frame.backStack.forEach((e) => { if (e && e.fragmentTag === fragmentTag) { entry = e; } - }) + }); } let page: Page; @@ -873,15 +879,24 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks { public onCreateAnimator(fragment: androidx.fragment.app.Fragment, transit: number, enter: boolean, nextAnim: number, superFunc: Function): android.animation.Animator { let animator = null; const entry = this.entry; + if (Trace.isEnabled()) { + Trace.write(`${fragment}.onCreateAnimator(${transit},${enter}, ${nextAnim})`, Trace.categories.Animation); + } // Return enterAnimator only when new (no current entry) nested transition. if (enter && entry.isNestedDefaultTransition) { animator = entry.enterAnimator; entry.isNestedDefaultTransition = false; } - return animator || superFunc.call(fragment, transit, enter, nextAnim); } + @profile + public onCreateAnimation(fragment: androidx.fragment.app.Fragment, transit: number, enter: boolean, nextAnim: number, superFunc: Function): globalAndroid.view.animation.Animation { + if (Trace.isEnabled()) { + Trace.write(`${fragment}.onCreateAnimation(${transit},${enter}, ${nextAnim})`, Trace.categories.Animation); + } + return superFunc.call(fragment, transit, enter, nextAnim); + } @profile public onCreate(fragment: androidx.fragment.app.Fragment, savedInstanceState: android.os.Bundle, superFunc: Function): void { @@ -988,11 +1003,11 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks { @profile public onDestroyView(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void { - if (Trace.isEnabled()) { - Trace.write(`${fragment}.onDestroyView()`, Trace.categories.NativeLifecycle); - } - superFunc.call(fragment); + if (Trace.isEnabled()) { + Trace.write(`${fragment}.onDestroyView()`, Trace.categories.NativeLifecycle); } + superFunc.call(fragment); + } @profile public onDestroy(fragment: androidx.fragment.app.Fragment, superFunc: Function): void { @@ -1014,18 +1029,11 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks { // "IllegalStateException: Failure saving state: active fragment has cleared index: -1" // in a specific mixed parent / nested frame navigation scenario entry.fragment = null; - - const page = entry.resolvedPage; - if (!page) { - Trace.error(`${fragment}.onDestroy: entry has no resolvedPage`); - - return null; - } } @profile public onPause(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void { - superFunc.call(fragment); + superFunc.call(fragment); } @profile