feat: overhaul and streamline Android page navigation transitions (#7925)

* feat: remove Animators and replace with Transitions

* fix: handle disappearing nested fragments for tabs.

Extract TabFragmentImplementation in tab-navigation base for both tabs and bottom navigation

* chore: bump webpack cycles counter

* feat(android-widgets): add androidx.transition:transition as dependency

* chore: fix typescript errors

* fix(frame-android): child already has a parent. Replace removeView with removeAllViews

* fix(tests): wait for fragment before isAdded() check

* fix(bottom-navigation): prevent changeTab logic when fragment manager is destroyed

* chore: apply PR comments changes
This commit is contained in:
Alexander Djenkov
2019-10-15 18:19:47 +03:00
committed by GitHub
parent dc6540269f
commit 08e23bcc3b
20 changed files with 834 additions and 574 deletions

View File

@ -22,7 +22,7 @@ function waitUntilTabViewReady(page: Page, action: Function) {
action(); action();
if (isAndroid) { if (isAndroid) {
TKUnit.waitUntilReady(() => page.frame._currentEntry.fragment.isAdded()); TKUnit.waitUntilReady(() => page.frame._currentEntry.fragment && page.frame._currentEntry.fragment.isAdded());
} else { } else {
TKUnit.waitUntilReady(() => page.isLoaded); TKUnit.waitUntilReady(() => page.isLoaded);
} }

View File

@ -12,7 +12,7 @@ const { NativeScriptWorkerPlugin } = require("nativescript-worker-loader/NativeS
const TerserPlugin = require("terser-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin");
const hashSalt = Date.now().toString(); const hashSalt = Date.now().toString();
const ANDROID_MAX_CYCLES = 68; const ANDROID_MAX_CYCLES = 66;
const IOS_MAX_CYCLES = 39; const IOS_MAX_CYCLES = 39;
let numCyclesDetected = 0; let numCyclesDetected = 0;

View File

@ -77,6 +77,7 @@ dependencies {
def androidxVersion = computeAndroidXVersion() def androidxVersion = computeAndroidXVersion()
implementation 'androidx.viewpager:viewpager:' + androidxVersion implementation 'androidx.viewpager:viewpager:' + androidxVersion
implementation 'androidx.fragment:fragment:' + androidxVersion implementation 'androidx.fragment:fragment:' + androidxVersion
implementation 'androidx.transition:transition:' + androidxVersion
} else { } else {
println 'Using support library' println 'Using support library'
implementation 'com.android.support:support-v4:' + computeSupportVersion() implementation 'com.android.support:support-v4:' + computeSupportVersion()

View File

@ -0,0 +1,135 @@
package org.nativescript.widgets;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.transition.Transition;
import androidx.transition.TransitionListenerAdapter;
import androidx.transition.TransitionValues;
import androidx.transition.Visibility;
import java.util.ArrayList;
public class CustomTransition extends Visibility {
private boolean resetOnTransitionEnd;
private AnimatorSet animatorSet;
private AnimatorSet immediateAnimatorSet;
private String transitionName;
public CustomTransition(AnimatorSet animatorSet, String transitionName) {
this.animatorSet = animatorSet;
this.transitionName = transitionName;
}
@Nullable
@Override
public Animator onAppear(@NonNull ViewGroup sceneRoot, @NonNull final View view, @Nullable TransitionValues startValues,
@Nullable TransitionValues endValues) {
if (endValues == null || view == null || this.animatorSet == null) {
return null;
}
return this.setAnimatorsTarget(this.animatorSet, view);
}
@Override
public Animator onDisappear(@NonNull ViewGroup sceneRoot, @NonNull final View view, @Nullable TransitionValues startValues,
@Nullable TransitionValues endValues) {
if (startValues == null || view == null || this.animatorSet == null) {
return null;
}
return this.setAnimatorsTarget(this.animatorSet, view);
}
public void setResetOnTransitionEnd(boolean resetOnTransitionEnd) {
this.resetOnTransitionEnd = resetOnTransitionEnd;
}
public String getTransitionName(){
return this.transitionName;
}
private Animator setAnimatorsTarget(AnimatorSet animatorSet, final View view) {
ArrayList<Animator> animatorsList = animatorSet.getChildAnimations();
boolean resetOnTransitionEnd = this.resetOnTransitionEnd;
for (int i = 0; i < animatorsList.size(); i++) {
animatorsList.get(i).setTarget(view);
}
// Reset animation to its initial state to prevent mirrorered effect
if (this.resetOnTransitionEnd) {
this.immediateAnimatorSet = this.animatorSet.clone();
}
// Switching to hardware layer during transition to improve animation performance
CustomAnimatorListener listener = new CustomAnimatorListener(view);
animatorSet.addListener(listener);
this.addListener(new CustomTransitionListenerAdapter(this));
return this.animatorSet;
}
private class ReverseInterpolator implements Interpolator {
@Override
public float getInterpolation(float paramFloat) {
return Math.abs(paramFloat - 1f);
}
}
private class CustomTransitionListenerAdapter extends TransitionListenerAdapter {
private CustomTransition customTransition;
CustomTransitionListenerAdapter(CustomTransition transition) {
this.customTransition = transition;
}
@Override
public void onTransitionEnd(@NonNull Transition transition) {
if (this.customTransition.resetOnTransitionEnd) {
this.customTransition.immediateAnimatorSet.setDuration(0);
this.customTransition.immediateAnimatorSet.setInterpolator(new ReverseInterpolator());
this.customTransition.immediateAnimatorSet.start();
this.customTransition.setResetOnTransitionEnd(false);
}
this.customTransition.immediateAnimatorSet = null;
this.customTransition = null;
transition.removeListener(this);
}
}
private static class CustomAnimatorListener extends AnimatorListenerAdapter {
private final View mView;
private boolean mLayerTypeChanged = false;
CustomAnimatorListener(View view) {
mView = view;
}
@Override
public void onAnimationStart(Animator animation) {
if (ViewCompat.hasOverlappingRendering(mView)
&& mView.getLayerType() == View.LAYER_TYPE_NONE) {
mLayerTypeChanged = true;
mView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
}
@Override
public void onAnimationEnd(Animator animation) {
if (mLayerTypeChanged) {
mView.setLayerType(View.LAYER_TYPE_NONE, null);
}
}
}
}

View File

@ -4,27 +4,6 @@ import android.animation.Animator;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
public abstract class FragmentBase extends Fragment { public abstract class FragmentBase extends Fragment {
@Override
public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
// [nested frames / fragments] apply dummy animator to the nested fragment with
// the same duration as the exit animator of the removing parent fragment to work around
// https://code.google.com/p/android/issues/detail?id=55228 (child fragments disappear
// when parent fragment is removed as all children are first removed from parent)
if (!enter) {
Fragment removingParentFragment = this.getRemovingParentFragment();
if (removingParentFragment != null) {
Animator parentAnimator = removingParentFragment.onCreateAnimator(transit, enter, AnimatorHelper.exitFakeResourceId);
if (parentAnimator != null) {
long duration = AnimatorHelper.getTotalDuration(parentAnimator);
return AnimatorHelper.createDummyAnimator(duration);
}
}
}
return super.onCreateAnimator(transit, enter, nextAnim);
}
public Fragment getRemovingParentFragment() { public Fragment getRemovingParentFragment() {
Fragment parentFragment = this.getParentFragment(); Fragment parentFragment = this.getParentFragment();
while (parentFragment != null && !parentFragment.isRemoving()) { while (parentFragment != null && !parentFragment.isRemoving()) {

View File

@ -34,6 +34,7 @@ const ownerSymbol = Symbol("_owner");
let TabFragment: any; let TabFragment: any;
let BottomNavigationBar: any; let BottomNavigationBar: any;
let AttachStateChangeListener: any; let AttachStateChangeListener: any;
let appResources: android.content.res.Resources;
function makeFragmentName(viewId: number, id: number): string { function makeFragmentName(viewId: number, id: number): string {
return "android:bottomnavigation:" + viewId + ":" + id; return "android:bottomnavigation:" + viewId + ":" + id;
@ -55,8 +56,9 @@ function initializeNativeClasses() {
} }
class TabFragmentImplementation extends org.nativescript.widgets.FragmentBase { class TabFragmentImplementation extends org.nativescript.widgets.FragmentBase {
private tab: BottomNavigation; private owner: BottomNavigation;
private index: number; private index: number;
private backgroundBitmap: android.graphics.Bitmap = null;
constructor() { constructor() {
super(); super();
@ -77,18 +79,61 @@ function initializeNativeClasses() {
public onCreate(savedInstanceState: android.os.Bundle): void { public onCreate(savedInstanceState: android.os.Bundle): void {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
const args = this.getArguments(); const args = this.getArguments();
this.tab = getTabById(args.getInt(TABID)); this.owner = getTabById(args.getInt(TABID));
this.index = args.getInt(INDEX); this.index = args.getInt(INDEX);
if (!this.tab) { if (!this.owner) {
throw new Error(`Cannot find BottomNavigation`); throw new Error(`Cannot find BottomNavigation`);
} }
} }
public onCreateView(inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle): android.view.View { public onCreateView(inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle): android.view.View {
const tabItem = this.tab.items[this.index]; const tabItem = this.owner.items[this.index];
return tabItem.nativeViewProtected; return tabItem.nativeViewProtected;
} }
public onDestroyView() {
const hasRemovingParent = this.getRemovingParentFragment();
// Get view as bitmap and set it as background. This is workaround for the disapearing nested fragments.
// TODO: Consider removing it when update to androidx.fragment:1.2.0
if (hasRemovingParent && this.owner.selectedIndex === this.index) {
const bitmapDrawable = new android.graphics.drawable.BitmapDrawable(appResources, this.backgroundBitmap);
this.owner._originalBackground = this.owner.backgroundColor || new Color("White");
this.owner.nativeViewProtected.setBackgroundDrawable(bitmapDrawable);
this.backgroundBitmap = null;
}
super.onDestroyView();
}
public onPause(): void {
const hasRemovingParent = this.getRemovingParentFragment();
// Get view as bitmap and set it as background. This is workaround for the disapearing nested fragments.
// TODO: Consider removing it when update to androidx.fragment:1.2.0
if (hasRemovingParent && this.owner.selectedIndex === this.index) {
this.backgroundBitmap = this.loadBitmapFromView(this.owner.nativeViewProtected);
}
super.onPause();
}
private loadBitmapFromView(view: android.view.View): android.graphics.Bitmap {
// Another way to get view bitmap. Test performance vs setDrawingCacheEnabled
// const width = view.getWidth();
// const height = view.getHeight();
// const bitmap = android.graphics.Bitmap.createBitmap(width, height, android.graphics.Bitmap.Config.ARGB_8888);
// const canvas = new android.graphics.Canvas(bitmap);
// view.layout(0, 0, width, height);
// view.draw(canvas);
view.setDrawingCacheEnabled(true);
const bitmap = android.graphics.Bitmap.createBitmap(view.getDrawingCache());
view.setDrawingCacheEnabled(false);
return bitmap;
}
} }
class BottomNavigationBarImplementation extends org.nativescript.widgets.BottomNavigationBar { class BottomNavigationBarImplementation extends org.nativescript.widgets.BottomNavigationBar {
@ -168,6 +213,7 @@ function initializeNativeClasses() {
TabFragment = TabFragmentImplementation; TabFragment = TabFragmentImplementation;
BottomNavigationBar = BottomNavigationBarImplementation; BottomNavigationBar = BottomNavigationBarImplementation;
AttachStateChangeListener = new AttachListener(); AttachStateChangeListener = new AttachListener();
appResources = application.android.context.getResources();
} }
function setElevation(bottomNavigationBar: org.nativescript.widgets.BottomNavigationBar) { function setElevation(bottomNavigationBar: org.nativescript.widgets.BottomNavigationBar) {
@ -196,6 +242,7 @@ export class BottomNavigation extends TabNavigationBase {
private _currentFragment: androidx.fragment.app.Fragment; private _currentFragment: androidx.fragment.app.Fragment;
private _currentTransaction: androidx.fragment.app.FragmentTransaction; private _currentTransaction: androidx.fragment.app.FragmentTransaction;
private _attachedToWindow = false; private _attachedToWindow = false;
public _originalBackground: any;
constructor() { constructor() {
super(); super();
@ -320,6 +367,12 @@ export class BottomNavigation extends TabNavigationBase {
public onLoaded(): void { public onLoaded(): void {
super.onLoaded(); super.onLoaded();
if (this._originalBackground) {
this.backgroundColor = null;
this.backgroundColor = this._originalBackground;
this._originalBackground = null;
}
if (this.tabStrip) { if (this.tabStrip) {
this.setTabStripItems(this.tabStrip.items); this.setTabStripItems(this.tabStrip.items);
} else { } else {
@ -334,8 +387,14 @@ export class BottomNavigation extends TabNavigationBase {
_onAttachedToWindow(): void { _onAttachedToWindow(): void {
super._onAttachedToWindow(); super._onAttachedToWindow();
this._attachedToWindow = true; this._attachedToWindow = true;
// _onAttachedToWindow called from OS again after it was detach
// TODO: Consider testing and removing it when update to androidx.fragment:1.2.0
if (this._manager && this._manager.isDestroyed()) {
return;
}
this.changeTab(this.selectedIndex); this.changeTab(this.selectedIndex);
} }

View File

@ -84,7 +84,7 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
public static showingModallyEvent = "showingModally"; public static showingModallyEvent = "showingModally";
protected _closeModalCallback: Function; protected _closeModalCallback: Function;
public _manager: any;
public _modalParent: ViewCommon; public _modalParent: ViewCommon;
private _modalContext: any; private _modalContext: any;
private _modal: ViewCommon; private _modal: ViewCommon;

View File

@ -272,12 +272,12 @@ export class View extends ViewCommon {
public static androidBackPressedEvent = androidBackPressedEvent; public static androidBackPressedEvent = androidBackPressedEvent;
public _dialogFragment: androidx.fragment.app.DialogFragment; public _dialogFragment: androidx.fragment.app.DialogFragment;
public _manager: androidx.fragment.app.FragmentManager;
private _isClickable: boolean; private _isClickable: boolean;
private touchListenerIsSet: boolean; private touchListenerIsSet: boolean;
private touchListener: android.view.View.OnTouchListener; private touchListener: android.view.View.OnTouchListener;
private layoutChangeListenerIsSet: boolean; private layoutChangeListenerIsSet: boolean;
private layoutChangeListener: android.view.View.OnLayoutChangeListener; private layoutChangeListener: android.view.View.OnLayoutChangeListener;
private _manager: androidx.fragment.app.FragmentManager;
private _rootManager: androidx.fragment.app.FragmentManager; private _rootManager: androidx.fragment.app.FragmentManager;
nativeViewProtected: android.view.View; nativeViewProtected: android.view.View;

View File

@ -643,6 +643,11 @@ export abstract class View extends ViewBase {
* @private * @private
*/ */
_gestureObservers: any; _gestureObservers: any;
/**
* @private
* androidx.fragment.app.FragmentManager
*/
_manager: any;
/** /**
* @private * @private
*/ */

View File

@ -23,6 +23,10 @@ class FragmentClass extends org.nativescript.widgets.FragmentBase {
this._callbacks.onStop(this, super.onStop); this._callbacks.onStop(this, super.onStop);
} }
public onPause(): void {
this._callbacks.onPause(this, super.onStop);
}
public onCreate(savedInstanceState: android.os.Bundle) { public onCreate(savedInstanceState: android.os.Bundle) {
if (!this._callbacks) { if (!this._callbacks) {
setFragmentCallbacks(this); setFragmentCallbacks(this);

View File

@ -3,23 +3,29 @@
// Definitions. // Definitions.
import { NavigationType } from "./frame-common"; import { NavigationType } from "./frame-common";
import { NavigationTransition, BackstackEntry } from "../frame"; import { NavigationTransition, BackstackEntry } from "../frame";
import { AnimationType } from "./fragment.transitions.types";
// Types. // Types.
import { Transition, AndroidTransitionType } from "../transition/transition"; import { Transition, AndroidTransitionType } from "../transition/transition";
import { SlideTransition } from "../transition/slide-transition";
import { FadeTransition } from "../transition/fade-transition";
import { FlipTransition } from "../transition/flip-transition"; import { FlipTransition } from "../transition/flip-transition";
import { _resolveAnimationCurve } from "../animation"; import { _resolveAnimationCurve } from "../animation";
import { device } from "../../platform";
import lazy from "../../utils/lazy"; import lazy from "../../utils/lazy";
import { isEnabled as traceEnabled, write as traceWrite, categories as traceCategories } from "../../trace"; import { isEnabled as traceEnabled, write as traceWrite, categories as traceCategories } from "../../trace";
export { AnimationType } from "./fragment.transitions.types";
interface TransitionListener { interface TransitionListener {
new(entry: ExpandedEntry, transition: android.transition.Transition): ExpandedTransitionListener; new(entry: ExpandedEntry, transition: androidx.transition.Transition): ExpandedTransitionListener;
}
const defaultInterpolator = lazy(() => new android.view.animation.AccelerateDecelerateInterpolator());
export const waitingQueue = new Map<number, Set<ExpandedEntry>>();
export const completedEntries = new Map<number, ExpandedEntry>();
let TransitionListener: TransitionListener;
let AnimationListener: android.animation.Animator.AnimatorListener;
interface ExpandedTransitionListener extends androidx.transition.Transition.TransitionListener {
entry: ExpandedEntry;
transition: androidx.transition.Transition;
} }
interface ExpandedAnimator extends android.animation.Animator { interface ExpandedAnimator extends android.animation.Animator {
@ -27,12 +33,8 @@ interface ExpandedAnimator extends android.animation.Animator {
transitionType?: string; transitionType?: string;
} }
interface ExpandedTransitionListener extends android.transition.Transition.TransitionListener {
entry: ExpandedEntry;
transition: android.transition.Transition;
}
interface ExpandedEntry extends BackstackEntry { interface ExpandedEntry extends BackstackEntry {
enterTransitionListener: ExpandedTransitionListener; enterTransitionListener: ExpandedTransitionListener;
exitTransitionListener: ExpandedTransitionListener; exitTransitionListener: ExpandedTransitionListener;
reenterTransitionListener: ExpandedTransitionListener; reenterTransitionListener: ExpandedTransitionListener;
@ -43,32 +45,21 @@ interface ExpandedEntry extends BackstackEntry {
popEnterAnimator: ExpandedAnimator; popEnterAnimator: ExpandedAnimator;
popExitAnimator: ExpandedAnimator; popExitAnimator: ExpandedAnimator;
defaultEnterAnimator: ExpandedAnimator;
defaultExitAnimator: ExpandedAnimator;
transition: Transition; transition: Transition;
transitionName: string; transitionName: string;
frameId: number; frameId: number;
useLollipopTransition: boolean;
isNestedDefaultTransition: boolean;
} }
const sdkVersion = lazy(() => parseInt(device.sdkVersion));
const intEvaluator = lazy(() => new android.animation.IntEvaluator());
const defaultInterpolator = lazy(() => new android.view.animation.AccelerateDecelerateInterpolator());
export const waitingQueue = new Map<number, Set<ExpandedEntry>>();
export const completedEntries = new Map<number, ExpandedEntry>();
let TransitionListener: TransitionListener;
let AnimationListener: android.animation.Animator.AnimatorListener;
export function _setAndroidFragmentTransitions( export function _setAndroidFragmentTransitions(
animated: boolean, animated: boolean,
navigationTransition: NavigationTransition, navigationTransition: NavigationTransition,
currentEntry: ExpandedEntry, currentEntry: ExpandedEntry,
newEntry: ExpandedEntry, newEntry: ExpandedEntry,
fragmentTransaction: androidx.fragment.app.FragmentTransaction, frameId: number,
frameId: number): void { fragmentTransaction: any,
isNestedDefaultTransition?: boolean): void {
const currentFragment: androidx.fragment.app.Fragment = currentEntry ? currentEntry.fragment : null; const currentFragment: androidx.fragment.app.Fragment = currentEntry ? currentEntry.fragment : null;
const newFragment: androidx.fragment.app.Fragment = newEntry.fragment; const newFragment: androidx.fragment.app.Fragment = newEntry.fragment;
@ -77,10 +68,8 @@ export function _setAndroidFragmentTransitions(
throw new Error("Calling navigation before previous navigation finish."); throw new Error("Calling navigation before previous navigation finish.");
} }
if (sdkVersion() >= 21) {
allowTransitionOverlap(currentFragment); allowTransitionOverlap(currentFragment);
allowTransitionOverlap(newFragment); allowTransitionOverlap(newFragment);
}
let name = ""; let name = "";
let transition: Transition; let transition: Transition;
@ -90,29 +79,11 @@ export function _setAndroidFragmentTransitions(
name = navigationTransition.name ? navigationTransition.name.toLowerCase() : ""; name = navigationTransition.name ? navigationTransition.name.toLowerCase() : "";
} }
let useLollipopTransition = !!(name && (name.indexOf("slide") === 0 || name === "fade" || name === "explode") && sdkVersion() >= 21);
// [nested frames / fragments] force disable lollipop transitions in case nested fragments
// are detected as applying dummy animator to the nested fragment with the same duration as
// the exit animator of the removing parent fragment as a workaround for
// https://code.google.com/p/android/issues/detail?id=55228 works only if custom animations are
// used
// NOTE: this effectively means you cannot use Explode transition in nested frames scenarios as
// we have implementations only for slide, fade, and flip
if (currentFragment &&
currentFragment.getChildFragmentManager() &&
currentFragment.getChildFragmentManager().getFragments().toArray().length > 0) {
useLollipopTransition = false;
}
newEntry.useLollipopTransition = useLollipopTransition;
if (!animated) { if (!animated) {
name = "none"; name = "none";
} else if (transition) { } else if (transition) {
name = "custom"; name = "custom";
// specifiying transition should override default one even if name match the lollipop transition name. } else if (name.indexOf("slide") !== 0 && name !== "fade" && name.indexOf("flip") !== 0 && name.indexOf("explode") !== 0) {
useLollipopTransition = false;
} else if (!useLollipopTransition && 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. // If we are given name that doesn't match any of ours - fallback to default.
name = "default"; name = "default";
} }
@ -121,25 +92,42 @@ export function _setAndroidFragmentTransitions(
if (currentEntry) { if (currentEntry) {
_updateTransitions(currentEntry); _updateTransitions(currentEntry);
if (currentEntry.transitionName !== name || if (currentEntry.transitionName !== name ||
currentEntry.transition !== transition || currentEntry.transition !== transition || isNestedDefaultTransition) {
!!currentEntry.useLollipopTransition !== useLollipopTransition ||
!useLollipopTransition) {
clearExitAndReenterTransitions(currentEntry, true); clearExitAndReenterTransitions(currentEntry, true);
currentFragmentNeedsDifferentAnimation = true; currentFragmentNeedsDifferentAnimation = true;
} }
} }
if (name === "none") { if (name === "none") {
transition = new NoTransition(0, null); const noTransition = new NoTransition(0, null);
} else if (name === "default") {
transition = new FadeTransition(150, null);
} else if (useLollipopTransition) {
// setEnterTransition: Enter
// setExitTransition: Exit
// setReenterTransition: Pop Enter, same as Exit if not specified
// setReturnTransition: Pop Exit, same as Enter if not specified
if (name.indexOf("slide") === 0) { // 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(android.R.anim.fade_in, android.R.anim.fade_out);
setupAllAnimation(newEntry, noTransition);
setupNewFragmentCustomTransition({ duration: 0, curve: null }, newEntry, noTransition);
} else {
setupNewFragmentCustomTransition({ duration: 0, curve: null }, newEntry, noTransition);
}
newEntry.isNestedDefaultTransition = isNestedDefaultTransition;
if (currentFragmentNeedsDifferentAnimation) {
setupCurrentFragmentCustomTransition({ duration: 0, curve: null }, currentEntry, noTransition);
}
} else if (name === "custom") {
setupNewFragmentCustomTransition({ duration: transition.getDuration(), curve: transition.getCurve() }, newEntry, transition);
if (currentFragmentNeedsDifferentAnimation) {
setupCurrentFragmentCustomTransition({ duration: transition.getDuration(), curve: transition.getCurve() }, currentEntry, transition);
}
} else if (name === "default") {
setupNewFragmentFadeTransition({ duration: 150, curve: null }, newEntry);
if (currentFragmentNeedsDifferentAnimation) {
setupCurrentFragmentFadeTransition({ duration: 150, curve: null }, currentEntry);
}
} else if (name.indexOf("slide") === 0) {
setupNewFragmentSlideTransition(navigationTransition, newEntry, name); setupNewFragmentSlideTransition(navigationTransition, newEntry, name);
if (currentFragmentNeedsDifferentAnimation) { if (currentFragmentNeedsDifferentAnimation) {
setupCurrentFragmentSlideTransition(navigationTransition, currentEntry, name); setupCurrentFragmentSlideTransition(navigationTransition, currentEntry, name);
@ -154,34 +142,17 @@ export function _setAndroidFragmentTransitions(
if (currentFragmentNeedsDifferentAnimation) { if (currentFragmentNeedsDifferentAnimation) {
setupCurrentFragmentExplodeTransition(navigationTransition, currentEntry); setupCurrentFragmentExplodeTransition(navigationTransition, currentEntry);
} }
} } else if (name === "flip") {
} else if (name.indexOf("slide") === 0) {
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") {
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 direction = name.substr("flip".length) || "right"; //Extract the direction from the string
transition = new FlipTransition(direction, navigationTransition.duration, navigationTransition.curve); const flipTransition = new FlipTransition(direction, navigationTransition.duration, navigationTransition.curve);
setupNewFragmentCustomTransition(navigationTransition, newEntry, flipTransition);
if (currentFragmentNeedsDifferentAnimation) {
setupCurrentFragmentCustomTransition(navigationTransition, currentEntry, flipTransition);
}
} }
newEntry.transitionName = name; newEntry.transitionName = name;
if (name === "custom") {
newEntry.transition = transition;
}
// Having transition means we have custom animation
if (transition) {
if (fragmentTransaction) {
// we do not use Android backstack so setting popEnter / popExit is meaningless (3rd and 4th optional args)
fragmentTransaction.setCustomAnimations(AnimationType.enterFakeResourceId, AnimationType.exitFakeResourceId);
}
setupAllAnimation(newEntry, transition);
if (currentFragmentNeedsDifferentAnimation) {
setupExitAndPopEnterAnimation(currentEntry, transition);
}
}
if (currentEntry) { if (currentEntry) {
currentEntry.transitionName = name; currentEntry.transitionName = name;
@ -190,45 +161,106 @@ export function _setAndroidFragmentTransitions(
} }
} }
setupDefaultAnimations(newEntry, new FadeTransition(150, null));
printTransitions(currentEntry); printTransitions(currentEntry);
printTransitions(newEntry); printTransitions(newEntry);
} }
export function _onFragmentCreateAnimator(entry: ExpandedEntry, fragment: androidx.fragment.app.Fragment, nextAnim: number, enter: boolean): android.animation.Animator { function setupAllAnimation(entry: ExpandedEntry, transition: Transition): void {
let animator: android.animation.Animator; setupExitAndPopEnterAnimation(entry, transition);
switch (nextAnim) { const listener = getAnimationListener();
case AnimationType.enterFakeResourceId:
animator = entry.enterAnimator || entry.defaultEnterAnimator /* HACK */;
break;
case AnimationType.exitFakeResourceId: // setupAllAnimation is called only for new fragments so we don't
animator = entry.exitAnimator || entry.defaultExitAnimator /* HACK */; // need to clearAnimationListener for enter & popExit animators.
break; const enterAnimator = <ExpandedAnimator>transition.createAndroidAnimator(AndroidTransitionType.enter);
enterAnimator.transitionType = AndroidTransitionType.enter;
enterAnimator.entry = entry;
enterAnimator.addListener(listener);
entry.enterAnimator = enterAnimator;
case AnimationType.popEnterFakeResourceId: const popExitAnimator = <ExpandedAnimator>transition.createAndroidAnimator(AndroidTransitionType.popExit);
animator = entry.popEnterAnimator; popExitAnimator.transitionType = AndroidTransitionType.popExit;
break; popExitAnimator.entry = entry;
popExitAnimator.addListener(listener);
case AnimationType.popExitFakeResourceId: entry.popExitAnimator = popExitAnimator;
animator = entry.popExitAnimator;
break;
} }
if (!animator && sdkVersion() >= 21) { function setupExitAndPopEnterAnimation(entry: ExpandedEntry, transition: Transition): void {
const view = fragment.getView(); const listener = getAnimationListener();
const jsParent = entry.resolvedPage.parent;
const parent = view.getParent() || (jsParent && jsParent.nativeViewProtected); // remove previous listener if we are changing the animator.
const animatedEntries = _getAnimatedEntries(entry.frameId); clearAnimationListener(entry.exitAnimator, listener);
if (!animatedEntries || !animatedEntries.has(entry)) { clearAnimationListener(entry.popEnterAnimator, listener);
if (parent && !(<any>parent).isLaidOut()) {
animator = enter ? entry.defaultEnterAnimator : entry.defaultExitAnimator; const exitAnimator = <ExpandedAnimator>transition.createAndroidAnimator(AndroidTransitionType.exit);
exitAnimator.transitionType = AndroidTransitionType.exit;
exitAnimator.entry = entry;
exitAnimator.addListener(listener);
entry.exitAnimator = exitAnimator;
const popEnterAnimator = <ExpandedAnimator>transition.createAndroidAnimator(AndroidTransitionType.popEnter);
popEnterAnimator.transitionType = AndroidTransitionType.popEnter;
popEnterAnimator.entry = entry;
popEnterAnimator.addListener(listener);
entry.popEnterAnimator = popEnterAnimator;
}
function getAnimationListener(): android.animation.Animator.AnimatorListener {
if (!AnimationListener) {
@Interfaces([android.animation.Animator.AnimatorListener])
class AnimationListenerImpl extends java.lang.Object implements android.animation.Animator.AnimatorListener {
constructor() {
super();
return global.__native(this);
}
onAnimationStart(animator: ExpandedAnimator): void {
const entry = animator.entry;
addToWaitingQueue(entry);
if (traceEnabled()) {
traceWrite(`START ${animator.transitionType} for ${entry.fragmentTag}`, traceCategories.Transition);
}
}
onAnimationRepeat(animator: ExpandedAnimator): void {
if (traceEnabled()) {
traceWrite(`REPEAT ${animator.transitionType} for ${animator.entry.fragmentTag}`, traceCategories.Transition);
}
}
onAnimationEnd(animator: ExpandedAnimator): void {
if (traceEnabled()) {
traceWrite(`END ${animator.transitionType} for ${animator.entry.fragmentTag}`, traceCategories.Transition);
}
transitionOrAnimationCompleted(animator.entry);
}
onAnimationCancel(animator: ExpandedAnimator): void {
if (traceEnabled()) {
traceWrite(`CANCEL ${animator.transitionType} for ${animator.entry.fragmentTag}`, traceCategories.Transition);
} }
} }
} }
return animator; AnimationListener = new AnimationListenerImpl();
}
return AnimationListener;
}
function clearAnimationListener(animator: ExpandedAnimator, listener: android.animation.Animator.AnimatorListener): void {
if (!animator) {
return;
}
animator.removeListener(listener);
if (animator.entry && traceEnabled()) {
const entry = animator.entry;
traceWrite(`Clear ${animator.transitionType} - ${entry.transition} for ${entry.fragmentTag}`, traceCategories.Transition);
}
animator.entry = null;
} }
export function _getAnimatedEntries(frameId: number): Set<BackstackEntry> { export function _getAnimatedEntries(frameId: number): Set<BackstackEntry> {
@ -262,7 +294,7 @@ export function _reverseTransitions(previousEntry: ExpandedEntry, currentEntry:
const previousFragment = previousEntry.fragment; const previousFragment = previousEntry.fragment;
const currentFragment = currentEntry.fragment; const currentFragment = currentEntry.fragment;
let transitionUsed = false; let transitionUsed = false;
if (sdkVersion() >= 21) {
const returnTransitionListener = currentEntry.returnTransitionListener; const returnTransitionListener = currentEntry.returnTransitionListener;
if (returnTransitionListener) { if (returnTransitionListener) {
transitionUsed = true; transitionUsed = true;
@ -278,24 +310,23 @@ export function _reverseTransitions(previousEntry: ExpandedEntry, currentEntry:
} else { } else {
previousFragment.setEnterTransition(null); previousFragment.setEnterTransition(null);
} }
}
return transitionUsed; return transitionUsed;
} }
// Transition listener can't be static because // Transition listener can't be static because
// android is cloning transitions and we can't expand them :( // android is cloning transitions and we can't expand them :(
function getTransitionListener(entry: ExpandedEntry, transition: android.transition.Transition): ExpandedTransitionListener { function getTransitionListener(entry: ExpandedEntry, transition: androidx.transition.Transition): ExpandedTransitionListener {
if (!TransitionListener) { if (!TransitionListener) {
@Interfaces([(<any>android).transition.Transition.TransitionListener]) @Interfaces([(<any>androidx).transition.Transition.TransitionListener])
class TransitionListenerImpl extends java.lang.Object implements android.transition.Transition.TransitionListener { class TransitionListenerImpl extends java.lang.Object implements androidx.transition.Transition.TransitionListener {
constructor(public entry: ExpandedEntry, public transition: android.transition.Transition) { constructor(public entry: ExpandedEntry, public transition: androidx.transition.Transition) {
super(); super();
return global.__native(this); return global.__native(this);
} }
public onTransitionStart(transition: android.transition.Transition): void { public onTransitionStart(transition: androidx.transition.Transition): void {
const entry = this.entry; const entry = this.entry;
addToWaitingQueue(entry); addToWaitingQueue(entry);
if (traceEnabled()) { if (traceEnabled()) {
@ -303,7 +334,7 @@ function getTransitionListener(entry: ExpandedEntry, transition: android.transit
} }
} }
onTransitionEnd(transition: android.transition.Transition): void { onTransitionEnd(transition: androidx.transition.Transition): void {
const entry = this.entry; const entry = this.entry;
if (traceEnabled()) { if (traceEnabled()) {
traceWrite(`END ${toShortString(transition)} transition for ${entry.fragmentTag}`, traceCategories.Transition); traceWrite(`END ${toShortString(transition)} transition for ${entry.fragmentTag}`, traceCategories.Transition);
@ -312,20 +343,20 @@ function getTransitionListener(entry: ExpandedEntry, transition: android.transit
transitionOrAnimationCompleted(entry); transitionOrAnimationCompleted(entry);
} }
onTransitionResume(transition: android.transition.Transition): void { onTransitionResume(transition: androidx.transition.Transition): void {
if (traceEnabled()) { if (traceEnabled()) {
const fragment = this.entry.fragmentTag; const fragment = this.entry.fragmentTag;
traceWrite(`RESUME ${toShortString(transition)} transition for ${fragment}`, traceCategories.Transition); traceWrite(`RESUME ${toShortString(transition)} transition for ${fragment}`, traceCategories.Transition);
} }
} }
onTransitionPause(transition: android.transition.Transition): void { onTransitionPause(transition: androidx.transition.Transition): void {
if (traceEnabled()) { if (traceEnabled()) {
traceWrite(`PAUSE ${toShortString(transition)} transition for ${this.entry.fragmentTag}`, traceCategories.Transition); traceWrite(`PAUSE ${toShortString(transition)} transition for ${this.entry.fragmentTag}`, traceCategories.Transition);
} }
} }
onTransitionCancel(transition: android.transition.Transition): void { onTransitionCancel(transition: androidx.transition.Transition): void {
if (traceEnabled()) { if (traceEnabled()) {
traceWrite(`CANCEL ${toShortString(transition)} transition for ${this.entry.fragmentTag}`, traceCategories.Transition); traceWrite(`CANCEL ${toShortString(transition)} transition for ${this.entry.fragmentTag}`, traceCategories.Transition);
} }
@ -338,51 +369,6 @@ function getTransitionListener(entry: ExpandedEntry, transition: android.transit
return new TransitionListener(entry, transition); return new TransitionListener(entry, transition);
} }
function getAnimationListener(): android.animation.Animator.AnimatorListener {
if (!AnimationListener) {
@Interfaces([android.animation.Animator.AnimatorListener])
class AnimationListenerImpl extends java.lang.Object implements android.animation.Animator.AnimatorListener {
constructor() {
super();
return global.__native(this);
}
onAnimationStart(animator: ExpandedAnimator): void {
const entry = animator.entry;
addToWaitingQueue(entry);
if (traceEnabled()) {
traceWrite(`START ${animator.transitionType} for ${entry.fragmentTag}`, traceCategories.Transition);
}
}
onAnimationRepeat(animator: ExpandedAnimator): void {
if (traceEnabled()) {
traceWrite(`REPEAT ${animator.transitionType} for ${animator.entry.fragmentTag}`, traceCategories.Transition);
}
}
onAnimationEnd(animator: ExpandedAnimator): void {
if (traceEnabled()) {
traceWrite(`END ${animator.transitionType} for ${animator.entry.fragmentTag}`, traceCategories.Transition);
}
transitionOrAnimationCompleted(animator.entry);
}
onAnimationCancel(animator: ExpandedAnimator): void {
if (traceEnabled()) {
traceWrite(`CANCEL ${animator.transitionType} for ${animator.entry.fragmentTag}`, traceCategories.Transition);
}
}
}
AnimationListener = new AnimationListenerImpl();
}
return AnimationListener;
}
function addToWaitingQueue(entry: ExpandedEntry): void { function addToWaitingQueue(entry: ExpandedEntry): void {
const frameId = entry.frameId; const frameId = entry.frameId;
let entries = waitingQueue.get(frameId); let entries = waitingQueue.get(frameId);
@ -394,23 +380,7 @@ function addToWaitingQueue(entry: ExpandedEntry): void {
entries.add(entry); entries.add(entry);
} }
function clearAnimationListener(animator: ExpandedAnimator, listener: android.animation.Animator.AnimatorListener): void {
if (!animator) {
return;
}
animator.removeListener(listener);
if (animator.entry && traceEnabled()) {
const entry = animator.entry;
traceWrite(`Clear ${animator.transitionType} - ${entry.transition} for ${entry.fragmentTag}`, traceCategories.Transition);
}
animator.entry = null;
}
function clearExitAndReenterTransitions(entry: ExpandedEntry, removeListener: boolean): void { function clearExitAndReenterTransitions(entry: ExpandedEntry, removeListener: boolean): void {
if (sdkVersion() >= 21) {
const fragment: androidx.fragment.app.Fragment = entry.fragment; const fragment: androidx.fragment.app.Fragment = entry.fragment;
const exitListener = entry.exitTransitionListener; const exitListener = entry.exitTransitionListener;
if (exitListener) { if (exitListener) {
@ -450,7 +420,6 @@ function clearExitAndReenterTransitions(entry: ExpandedEntry, removeListener: bo
} }
} }
} }
}
export function _clearFragment(entry: ExpandedEntry): void { export function _clearFragment(entry: ExpandedEntry): void {
clearEntry(entry, false); clearEntry(entry, false);
@ -463,7 +432,6 @@ export function _clearEntry(entry: ExpandedEntry): void {
function clearEntry(entry: ExpandedEntry, removeListener: boolean): void { function clearEntry(entry: ExpandedEntry, removeListener: boolean): void {
clearExitAndReenterTransitions(entry, removeListener); clearExitAndReenterTransitions(entry, removeListener);
if (sdkVersion() >= 21) {
const fragment: androidx.fragment.app.Fragment = entry.fragment; const fragment: androidx.fragment.app.Fragment = entry.fragment;
const enterListener = entry.enterTransitionListener; const enterListener = entry.enterTransitionListener;
if (enterListener) { if (enterListener) {
@ -504,17 +472,6 @@ function clearEntry(entry: ExpandedEntry, removeListener: boolean): void {
} }
} }
if (removeListener) {
const listener = getAnimationListener();
clearAnimationListener(entry.enterAnimator, listener);
clearAnimationListener(entry.exitAnimator, listener);
clearAnimationListener(entry.popEnterAnimator, listener);
clearAnimationListener(entry.popExitAnimator, listener);
clearAnimationListener(entry.defaultEnterAnimator, listener);
clearAnimationListener(entry.defaultExitAnimator, listener);
}
}
function allowTransitionOverlap(fragment: androidx.fragment.app.Fragment): void { function allowTransitionOverlap(fragment: androidx.fragment.app.Fragment): void {
if (fragment) { if (fragment) {
fragment.setAllowEnterTransitionOverlap(true); fragment.setAllowEnterTransitionOverlap(true);
@ -522,7 +479,7 @@ function allowTransitionOverlap(fragment: androidx.fragment.app.Fragment): void
} }
} }
function setEnterTransition(navigationTransition: NavigationTransition, entry: ExpandedEntry, transition: android.transition.Transition): void { function setEnterTransition(navigationTransition: NavigationTransition, entry: ExpandedEntry, transition: androidx.transition.Transition): void {
setUpNativeTransition(navigationTransition, transition); setUpNativeTransition(navigationTransition, transition);
const listener = addNativeTransitionListener(entry, transition); const listener = addNativeTransitionListener(entry, transition);
@ -532,7 +489,7 @@ function setEnterTransition(navigationTransition: NavigationTransition, entry: E
fragment.setEnterTransition(transition); fragment.setEnterTransition(transition);
} }
function setExitTransition(navigationTransition: NavigationTransition, entry: ExpandedEntry, transition: android.transition.Transition): void { function setExitTransition(navigationTransition: NavigationTransition, entry: ExpandedEntry, transition: androidx.transition.Transition): void {
setUpNativeTransition(navigationTransition, transition); setUpNativeTransition(navigationTransition, transition);
const listener = addNativeTransitionListener(entry, transition); const listener = addNativeTransitionListener(entry, transition);
@ -542,7 +499,7 @@ function setExitTransition(navigationTransition: NavigationTransition, entry: Ex
fragment.setExitTransition(transition); fragment.setExitTransition(transition);
} }
function setReenterTransition(navigationTransition: NavigationTransition, entry: ExpandedEntry, transition: android.transition.Transition): void { function setReenterTransition(navigationTransition: NavigationTransition, entry: ExpandedEntry, transition: androidx.transition.Transition): void {
setUpNativeTransition(navigationTransition, transition); setUpNativeTransition(navigationTransition, transition);
const listener = addNativeTransitionListener(entry, transition); const listener = addNativeTransitionListener(entry, transition);
@ -552,7 +509,7 @@ function setReenterTransition(navigationTransition: NavigationTransition, entry:
fragment.setReenterTransition(transition); fragment.setReenterTransition(transition);
} }
function setReturnTransition(navigationTransition: NavigationTransition, entry: ExpandedEntry, transition: android.transition.Transition): void { function setReturnTransition(navigationTransition: NavigationTransition, entry: ExpandedEntry, transition: androidx.transition.Transition): void {
setUpNativeTransition(navigationTransition, transition); setUpNativeTransition(navigationTransition, transition);
const listener = addNativeTransitionListener(entry, transition); const listener = addNativeTransitionListener(entry, transition);
@ -567,23 +524,23 @@ function setupNewFragmentSlideTransition(navTransition: NavigationTransition, en
const direction = name.substr("slide".length) || "left"; //Extract the direction from the string const direction = name.substr("slide".length) || "left"; //Extract the direction from the string
switch (direction) { switch (direction) {
case "left": case "left":
setEnterTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.RIGHT)); setEnterTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.RIGHT));
setReturnTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.RIGHT)); setReturnTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.RIGHT));
break; break;
case "right": case "right":
setEnterTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.LEFT)); setEnterTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.LEFT));
setReturnTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.LEFT)); setReturnTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.LEFT));
break; break;
case "top": case "top":
setEnterTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.BOTTOM)); setEnterTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.BOTTOM));
setReturnTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.BOTTOM)); setReturnTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.BOTTOM));
break; break;
case "bottom": case "bottom":
setEnterTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.TOP)); setEnterTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.TOP));
setReturnTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.TOP)); setReturnTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.TOP));
break; break;
} }
} }
@ -592,115 +549,85 @@ function setupCurrentFragmentSlideTransition(navTransition: NavigationTransition
const direction = name.substr("slide".length) || "left"; //Extract the direction from the string const direction = name.substr("slide".length) || "left"; //Extract the direction from the string
switch (direction) { switch (direction) {
case "left": case "left":
setExitTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.LEFT)); setExitTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.LEFT));
setReenterTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.LEFT)); setReenterTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.LEFT));
break; break;
case "right": case "right":
setExitTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.RIGHT)); setExitTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.RIGHT));
setReenterTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.RIGHT)); setReenterTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.RIGHT));
break; break;
case "top": case "top":
setExitTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.TOP)); setExitTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.TOP));
setReenterTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.TOP)); setReenterTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.TOP));
break; break;
case "bottom": case "bottom":
setExitTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.BOTTOM)); setExitTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.BOTTOM));
setReenterTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.BOTTOM)); setReenterTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.BOTTOM));
break; break;
} }
} }
function setupCurrentFragmentCustomTransition(navTransition: NavigationTransition, entry: ExpandedEntry, transition: Transition): void {
const exitAnimator = transition.createAndroidAnimator(AndroidTransitionType.exit);
const exitTransition = new org.nativescript.widgets.CustomTransition(exitAnimator, transition.constructor.name + AndroidTransitionType.exit.toString());
setExitTransition(navTransition, entry, exitTransition);
const reenterAnimator = transition.createAndroidAnimator(AndroidTransitionType.popEnter);
const reenterTransition = new org.nativescript.widgets.CustomTransition(reenterAnimator, transition.constructor.name + AndroidTransitionType.popEnter.toString());
setReenterTransition(navTransition, entry, reenterTransition);
}
function setupNewFragmentCustomTransition(navTransition: NavigationTransition, entry: ExpandedEntry, transition: Transition): void {
setupCurrentFragmentCustomTransition(navTransition, entry, transition);
const enterAnimator = transition.createAndroidAnimator(AndroidTransitionType.enter);
const enterTransition = new org.nativescript.widgets.CustomTransition(enterAnimator, transition.constructor.name + AndroidTransitionType.enter.toString());
setEnterTransition(navTransition, entry, enterTransition);
const returnAnimator = transition.createAndroidAnimator(AndroidTransitionType.popExit);
const returnTransition = new org.nativescript.widgets.CustomTransition(returnAnimator, transition.constructor.name + AndroidTransitionType.popExit.toString());
setReturnTransition(navTransition, entry, returnTransition);
}
function setupNewFragmentFadeTransition(navTransition: NavigationTransition, entry: ExpandedEntry): void { function setupNewFragmentFadeTransition(navTransition: NavigationTransition, entry: ExpandedEntry): void {
setupCurrentFragmentFadeTransition(navTransition, entry); setupCurrentFragmentFadeTransition(navTransition, entry);
const fadeInEnter = new android.transition.Fade(android.transition.Fade.IN); const fadeInEnter = new androidx.transition.Fade(androidx.transition.Fade.IN);
setEnterTransition(navTransition, entry, fadeInEnter); setEnterTransition(navTransition, entry, fadeInEnter);
const fadeOutReturn = new android.transition.Fade(android.transition.Fade.OUT); const fadeOutReturn = new androidx.transition.Fade(androidx.transition.Fade.OUT);
setReturnTransition(navTransition, entry, fadeOutReturn); setReturnTransition(navTransition, entry, fadeOutReturn);
} }
function setupCurrentFragmentFadeTransition(navTransition: NavigationTransition, entry: ExpandedEntry): void { function setupCurrentFragmentFadeTransition(navTransition: NavigationTransition, entry: ExpandedEntry): void {
const fadeOutExit = new android.transition.Fade(android.transition.Fade.OUT); const fadeOutExit = new androidx.transition.Fade(androidx.transition.Fade.OUT);
setExitTransition(navTransition, entry, fadeOutExit); setExitTransition(navTransition, entry, fadeOutExit);
// NOTE: There is a bug in Fade transition so we need to set all 4 // NOTE: There is a bug in Fade transition so we need to set all 4
// otherwise back navigation will complete immediately (won't run the reverse transition). // otherwise back navigation will complete immediately (won't run the reverse transition).
const fadeInReenter = new android.transition.Fade(android.transition.Fade.IN); const fadeInReenter = new androidx.transition.Fade(androidx.transition.Fade.IN);
setReenterTransition(navTransition, entry, fadeInReenter); setReenterTransition(navTransition, entry, fadeInReenter);
} }
function setupCurrentFragmentExplodeTransition(navTransition: NavigationTransition, entry: ExpandedEntry): void { function setupCurrentFragmentExplodeTransition(navTransition: NavigationTransition, entry: ExpandedEntry): void {
setExitTransition(navTransition, entry, new android.transition.Explode()); setExitTransition(navTransition, entry, new androidx.transition.Explode());
setReenterTransition(navTransition, entry, new android.transition.Explode()); setReenterTransition(navTransition, entry, new androidx.transition.Explode());
} }
function setupNewFragmentExplodeTransition(navTransition: NavigationTransition, entry: ExpandedEntry): void { function setupNewFragmentExplodeTransition(navTransition: NavigationTransition, entry: ExpandedEntry): void {
setupCurrentFragmentExplodeTransition(navTransition, entry); setupCurrentFragmentExplodeTransition(navTransition, entry);
setEnterTransition(navTransition, entry, new android.transition.Explode()); setEnterTransition(navTransition, entry, new androidx.transition.Explode());
setReturnTransition(navTransition, entry, new android.transition.Explode()); setReturnTransition(navTransition, entry, new androidx.transition.Explode());
} }
function setupExitAndPopEnterAnimation(entry: ExpandedEntry, transition: Transition): void { function setUpNativeTransition(navigationTransition: NavigationTransition, nativeTransition: androidx.transition.Transition) {
const listener = getAnimationListener();
// remove previous listener if we are changing the animator.
clearAnimationListener(entry.exitAnimator, listener);
clearAnimationListener(entry.popEnterAnimator, listener);
const exitAnimator = <ExpandedAnimator>transition.createAndroidAnimator(AndroidTransitionType.exit);
exitAnimator.transitionType = AndroidTransitionType.exit;
exitAnimator.entry = entry;
exitAnimator.addListener(listener);
entry.exitAnimator = exitAnimator;
const popEnterAnimator = <ExpandedAnimator>transition.createAndroidAnimator(AndroidTransitionType.popEnter);
popEnterAnimator.transitionType = AndroidTransitionType.popEnter;
popEnterAnimator.entry = entry;
popEnterAnimator.addListener(listener);
entry.popEnterAnimator = popEnterAnimator;
}
function setupAllAnimation(entry: ExpandedEntry, transition: Transition): void {
setupExitAndPopEnterAnimation(entry, transition);
const listener = getAnimationListener();
// setupAllAnimation is called only for new fragments so we don't
// need to clearAnimationListener for enter & popExit animators.
const enterAnimator = <ExpandedAnimator>transition.createAndroidAnimator(AndroidTransitionType.enter);
enterAnimator.transitionType = AndroidTransitionType.enter;
enterAnimator.entry = entry;
enterAnimator.addListener(listener);
entry.enterAnimator = enterAnimator;
const popExitAnimator = <ExpandedAnimator>transition.createAndroidAnimator(AndroidTransitionType.popExit);
popExitAnimator.transitionType = AndroidTransitionType.popExit;
popExitAnimator.entry = entry;
popExitAnimator.addListener(listener);
entry.popExitAnimator = popExitAnimator;
}
function setupDefaultAnimations(entry: ExpandedEntry, transition: Transition): void {
const listener = getAnimationListener();
const enterAnimator = <ExpandedAnimator>transition.createAndroidAnimator(AndroidTransitionType.enter);
enterAnimator.transitionType = AndroidTransitionType.enter;
enterAnimator.entry = entry;
enterAnimator.addListener(listener);
entry.defaultEnterAnimator = enterAnimator;
const exitAnimator = <ExpandedAnimator>transition.createAndroidAnimator(AndroidTransitionType.exit);
exitAnimator.transitionType = AndroidTransitionType.exit;
exitAnimator.entry = entry;
exitAnimator.addListener(listener);
entry.defaultExitAnimator = exitAnimator;
}
function setUpNativeTransition(navigationTransition: NavigationTransition, nativeTransition: android.transition.Transition) {
if (navigationTransition.duration) { if (navigationTransition.duration) {
nativeTransition.setDuration(navigationTransition.duration); nativeTransition.setDuration(navigationTransition.duration);
} }
@ -709,7 +636,7 @@ function setUpNativeTransition(navigationTransition: NavigationTransition, nativ
nativeTransition.setInterpolator(interpolator); nativeTransition.setInterpolator(interpolator);
} }
function addNativeTransitionListener(entry: ExpandedEntry, nativeTransition: android.transition.Transition): ExpandedTransitionListener { export function addNativeTransitionListener(entry: ExpandedEntry, nativeTransition: androidx.transition.Transition): ExpandedTransitionListener {
const listener = getTransitionListener(entry, nativeTransition); const listener = getTransitionListener(entry, nativeTransition);
nativeTransition.addListener(listener); nativeTransition.addListener(listener);
@ -750,7 +677,7 @@ function transitionOrAnimationCompleted(entry: ExpandedEntry): void {
} }
} }
function toShortString(nativeTransition: android.transition.Transition): string { function toShortString(nativeTransition: androidx.transition.Transition): string {
return `${nativeTransition.getClass().getSimpleName()}@${nativeTransition.hashCode().toString(16)}`; return `${nativeTransition.getClass().getSimpleName()}@${nativeTransition.hashCode().toString(16)}`;
} }
@ -761,19 +688,12 @@ function printTransitions(entry: ExpandedEntry) {
result += `transitionName=${entry.transitionName}, `; result += `transitionName=${entry.transitionName}, `;
} }
if (entry.transition) {
result += `enterAnimator=${entry.enterAnimator}, `;
result += `exitAnimator=${entry.exitAnimator}, `;
result += `popEnterAnimator=${entry.popEnterAnimator}, `;
result += `popExitAnimator=${entry.popExitAnimator}, `;
}
if (sdkVersion() >= 21) {
const fragment = entry.fragment; const fragment = entry.fragment;
result += `${fragment.getEnterTransition() ? " enter=" + toShortString(fragment.getEnterTransition()) : ""}`; result += `${fragment.getEnterTransition() ? " enter=" + toShortString(fragment.getEnterTransition()) : ""}`;
result += `${fragment.getExitTransition() ? " exit=" + toShortString(fragment.getExitTransition()) : ""}`; result += `${fragment.getExitTransition() ? " exit=" + toShortString(fragment.getExitTransition()) : ""}`;
result += `${fragment.getReenterTransition() ? " popEnter=" + toShortString(fragment.getReenterTransition()) : ""}`; result += `${fragment.getReenterTransition() ? " popEnter=" + toShortString(fragment.getReenterTransition()) : ""}`;
result += `${fragment.getReturnTransition() ? " popExit=" + toShortString(fragment.getReturnTransition()) : ""}`; result += `${fragment.getReturnTransition() ? " popExit=" + toShortString(fragment.getReturnTransition()) : ""}`;
}
traceWrite(result, traceCategories.Transition); traceWrite(result, traceCategories.Transition);
} }
} }
@ -785,16 +705,24 @@ function javaObjectArray(...params: java.lang.Object[]) {
return nativeArray; return nativeArray;
} }
function createDummyZeroDurationAnimator(): android.animation.Animator { function createDummyZeroDurationAnimator(duration: number): android.animation.AnimatorSet {
const animator = android.animation.ValueAnimator.ofObject(intEvaluator(), javaObjectArray(java.lang.Integer.valueOf(0), java.lang.Integer.valueOf(1))); const animatorSet = new android.animation.AnimatorSet();
// TODO: investigate why this is necessary for 3 levels of nested frames const objectAnimators = Array.create(android.animation.Animator, 1);
animator.setDuration(1);
return animator; const values = Array.create("float", 2);
values[0] = 0.0;
values[1] = 1.0;
const animator = <android.animation.Animator>android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
animator.setDuration(duration);
objectAnimators[0] = animator;
animatorSet.playTogether(objectAnimators);
return animatorSet;
} }
class NoTransition extends Transition { class NoTransition extends Transition {
public createAndroidAnimator(transitionType: string): android.animation.Animator { public createAndroidAnimator(transitionType: string): android.animation.AnimatorSet {
return createDummyZeroDurationAnimator(); return createDummyZeroDurationAnimator(this.getDuration());
} }
} }

View File

@ -3,11 +3,8 @@
*/ /** */ */ /** */
import { NavigationTransition, BackstackEntry } from "../frame"; import { NavigationTransition, BackstackEntry } from "../frame";
// Types.
/** import { Transition, AndroidTransitionType } from "../transition/transition";
* @private
*/
export { AnimationType } from "./fragment.transitions.types";
/** /**
* @private * @private
@ -17,12 +14,9 @@ export function _setAndroidFragmentTransitions(
navigationTransition: NavigationTransition, navigationTransition: NavigationTransition,
currentEntry: BackstackEntry, currentEntry: BackstackEntry,
newEntry: BackstackEntry, newEntry: BackstackEntry,
frameId: number,
fragmentTransaction: any, fragmentTransaction: any,
frameId: number): void; isNestedDefaultTransition?: boolean): void;
/**
* @private
*/
export function _onFragmentCreateAnimator(entry: BackstackEntry, fragment: any, nextAnim: number, enter: boolean): any;
/** /**
* @private * @private
*/ */
@ -57,4 +51,10 @@ export function _clearFragment(entry: BackstackEntry): void;
* @private * @private
*/ */
export function _createIOSAnimatedTransitioning(navigationTransition: NavigationTransition, nativeCurve: any, operation: number, fromVC: any, toVC: any): any; export function _createIOSAnimatedTransitioning(navigationTransition: NavigationTransition, nativeCurve: any, operation: number, fromVC: any, toVC: any): any;
/**
* @private
* nativeTransition: androidx.transition.Transition
*/
export function addNativeTransitionListener(entry: any, nativeTransition: any): any;
//@endprivate //@endprivate

View File

@ -14,8 +14,8 @@ import {
} from "./frame-common"; } from "./frame-common";
import { import {
_setAndroidFragmentTransitions, _onFragmentCreateAnimator, _getAnimatedEntries, _setAndroidFragmentTransitions, _getAnimatedEntries,
_updateTransitions, _reverseTransitions, _clearEntry, _clearFragment, AnimationType _updateTransitions, _reverseTransitions, _clearEntry, _clearFragment, addNativeTransitionListener
} from "./fragment.transitions"; } from "./fragment.transitions";
// TODO: Remove this and get it from global to decouple builder for angular // TODO: Remove this and get it from global to decouple builder for angular
@ -26,12 +26,13 @@ import { profile } from "../../profiling";
export * from "./frame-common"; export * from "./frame-common";
interface AnimatorState { interface TransitionState {
enterAnimator: any; enterTransitionListener: any;
exitAnimator: any; exitTransitionListener: any;
popEnterAnimator: any; reenterTransitionListener: any;
popExitAnimator: any; returnTransitionListener: any;
transitionName: string; transitionName: string;
entry: BackstackEntry;
} }
const ANDROID_PLATFORM = "android"; const ANDROID_PLATFORM = "android";
@ -47,6 +48,7 @@ const activityRootViewsMap = new Map<number, WeakRef<View>>();
let navDepth = -1; let navDepth = -1;
let fragmentId = -1; let fragmentId = -1;
export let moduleLoaded: boolean; export let moduleLoaded: boolean;
if (global && global.__inspector) { if (global && global.__inspector) {
@ -118,7 +120,7 @@ export class Frame extends FrameBase {
private _containerViewId: number = -1; private _containerViewId: number = -1;
private _tearDownPending = false; private _tearDownPending = false;
private _attachedToWindow = false; private _attachedToWindow = false;
private _cachedAnimatorState: AnimatorState; private _cachedTransitionState: TransitionState;
constructor() { constructor() {
super(); super();
@ -154,6 +156,13 @@ export class Frame extends FrameBase {
_onAttachedToWindow(): void { _onAttachedToWindow(): void {
super._onAttachedToWindow(); super._onAttachedToWindow();
this._attachedToWindow = true; this._attachedToWindow = true;
// _onAttachedToWindow called from OS again after it was detach
// TODO: Consider testing and removing it when update to androidx.fragment:1.2.0
if (this._manager && this._manager.isDestroyed()) {
return;
}
this._processNextNavigationEntry(); this._processNextNavigationEntry();
} }
@ -182,7 +191,9 @@ export class Frame extends FrameBase {
const manager = this._getFragmentManager(); const manager = this._getFragmentManager();
const entry = this._currentEntry; const entry = this._currentEntry;
if (entry && manager && !manager.findFragmentByTag(entry.fragmentTag)) { const isNewEntry = !this._cachedTransitionState || entry !== this._cachedTransitionState.entry;
if (isNewEntry && entry && manager && !manager.findFragmentByTag(entry.fragmentTag)) {
// Simulate first navigation (e.g. no animations or transitions) // Simulate first navigation (e.g. no animations or transitions)
// we need to cache the original animation settings so we can restore them later; otherwise as the // we need to cache the original animation settings so we can restore them later; otherwise as the
// simulated first navigation is not animated (it is actually a zero duration animator) the "popExit" animation // simulated first navigation is not animated (it is actually a zero duration animator) the "popExit" animation
@ -193,8 +204,10 @@ export class Frame extends FrameBase {
// simulated navigation (NoTransition, zero duration animator) and thus the fragment immediately disappears; // simulated navigation (NoTransition, zero duration animator) and thus the fragment immediately disappears;
// the user only sees the animation of the entering fragment as per its specific enter animation settings. // the user only sees the animation of the entering fragment as per its specific enter animation settings.
// NOTE: we are restoring the animation settings in Frame.setCurrent(...) as navigation completes asynchronously // NOTE: we are restoring the animation settings in Frame.setCurrent(...) as navigation completes asynchronously
this._cachedAnimatorState = getAnimatorState(this._currentEntry); let cachedTransitionState = getTransitionState(this._currentEntry);
if (cachedTransitionState) {
this._cachedTransitionState = cachedTransitionState;
this._currentEntry = null; this._currentEntry = null;
// NavigateCore will eventually call _processNextNavigationEntry again. // NavigateCore will eventually call _processNextNavigationEntry again.
this._navigateCore(entry); this._navigateCore(entry);
@ -202,6 +215,9 @@ export class Frame extends FrameBase {
} else { } else {
super._processNextNavigationEntry(); super._processNextNavigationEntry();
} }
} else {
super._processNextNavigationEntry();
}
} }
public _getChildFragmentManager() { public _getChildFragmentManager() {
@ -246,7 +262,15 @@ export class Frame extends FrameBase {
const manager: androidx.fragment.app.FragmentManager = this._getFragmentManager(); const manager: androidx.fragment.app.FragmentManager = this._getFragmentManager();
const transaction = manager.beginTransaction(); const transaction = manager.beginTransaction();
transaction.remove(this._currentEntry.fragment); const fragment = this._currentEntry.fragment;
const fragmentExitTransition = fragment.getExitTransition();
// Reset animation to its initial state to prevent mirrorered effect when restore current fragment transitions
if (fragmentExitTransition && fragmentExitTransition instanceof org.nativescript.widgets.CustomTransition) {
fragmentExitTransition.setResetOnTransitionEnd(true);
}
transaction.remove(fragment);
transaction.commitNowAllowingStateLoss(); transaction.commitNowAllowingStateLoss();
} }
@ -314,9 +338,9 @@ export class Frame extends FrameBase {
} }
// restore cached animation settings if we just completed simulated first navigation (no animation) // restore cached animation settings if we just completed simulated first navigation (no animation)
if (this._cachedAnimatorState) { if (this._cachedTransitionState) {
restoreAnimatorState(this._currentEntry, this._cachedAnimatorState); restoreTransitionState(this._currentEntry, this._cachedTransitionState);
this._cachedAnimatorState = null; this._cachedTransitionState = null;
} }
// restore original fragment transitions if we just completed replace navigation (hmr) // restore original fragment transitions if we just completed replace navigation (hmr)
@ -328,7 +352,7 @@ export class Frame extends FrameBase {
const currentEntry = null; const currentEntry = null;
const newEntry = entry; const newEntry = entry;
const transaction = null; const transaction = null;
_setAndroidFragmentTransitions(animated, navigationTransition, currentEntry, newEntry, transaction, this._android.frameId); _setAndroidFragmentTransitions(animated, navigationTransition, currentEntry, newEntry, this._android.frameId, transaction);
} }
} }
@ -404,10 +428,13 @@ export class Frame extends FrameBase {
navigationTransition = null; navigationTransition = null;
} }
_setAndroidFragmentTransitions(animated, navigationTransition, currentEntry, newEntry, transaction, this._android.frameId); let isNestedDefaultTransition = !currentEntry;
_setAndroidFragmentTransitions(animated, navigationTransition, currentEntry, newEntry, this._android.frameId, transaction, isNestedDefaultTransition);
if (currentEntry && animated && !navigationTransition) { if (currentEntry && animated && !navigationTransition) {
transaction.setTransition(androidx.fragment.app.FragmentTransaction.TRANSIT_FRAGMENT_OPEN); //TODO: Check whether or not this is still necessary. For Modal views?
//transaction.setTransition(androidx.fragment.app.FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
} }
transaction.replace(this.containerViewId, newFragment, newFragmentTag); transaction.replace(this.containerViewId, newFragment, newFragmentTag);
@ -430,12 +457,7 @@ export class Frame extends FrameBase {
_updateTransitions(backstackEntry); _updateTransitions(backstackEntry);
} }
const transitionReversed = _reverseTransitions(backstackEntry, this._currentEntry); _reverseTransitions(backstackEntry, this._currentEntry);
if (!transitionReversed) {
// If transition were not reversed then use animations.
// we do not use Android backstack so setting popEnter / popExit is meaningless (3rd and 4th optional args)
transaction.setCustomAnimations(AnimationType.popEnterFakeResourceId, AnimationType.popExitFakeResourceId);
}
transaction.replace(this.containerViewId, backstackEntry.fragment, backstackEntry.fragmentTag); transaction.replace(this.containerViewId, backstackEntry.fragment, backstackEntry.fragmentTag);
transaction.commitAllowingStateLoss(); transaction.commitAllowingStateLoss();
@ -534,48 +556,50 @@ export class Frame extends FrameBase {
}); });
} }
} }
function cloneExpandedTransitionListener(expandedTransitionListener: any) {
function cloneExpandedAnimator(expandedAnimator: any) { if (!expandedTransitionListener) {
if (!expandedAnimator) {
return null; return null;
} }
const clone = expandedAnimator.clone(); const cloneTransition = expandedTransitionListener.transition.clone();
clone.entry = expandedAnimator.entry;
clone.transitionType = expandedAnimator.transitionType;
return clone; return addNativeTransitionListener(expandedTransitionListener.entry, cloneTransition);
} }
function getAnimatorState(entry: BackstackEntry): AnimatorState { function getTransitionState(entry: BackstackEntry): TransitionState {
const expandedEntry = <any>entry; const expandedEntry = <any>entry;
const animatorState = <AnimatorState>{}; const transitionState = <TransitionState>{};
animatorState.enterAnimator = cloneExpandedAnimator(expandedEntry.enterAnimator); if (expandedEntry.enterTransitionListener && expandedEntry.exitTransitionListener) {
animatorState.exitAnimator = cloneExpandedAnimator(expandedEntry.exitAnimator); transitionState.enterTransitionListener = cloneExpandedTransitionListener(expandedEntry.enterTransitionListener);
animatorState.popEnterAnimator = cloneExpandedAnimator(expandedEntry.popEnterAnimator); transitionState.exitTransitionListener = cloneExpandedTransitionListener(expandedEntry.exitTransitionListener);
animatorState.popExitAnimator = cloneExpandedAnimator(expandedEntry.popExitAnimator); transitionState.reenterTransitionListener = cloneExpandedTransitionListener(expandedEntry.reenterTransitionListener);
animatorState.transitionName = expandedEntry.transitionName; transitionState.returnTransitionListener = cloneExpandedTransitionListener(expandedEntry.returnTransitionListener);
transitionState.transitionName = expandedEntry.transitionName;
return animatorState; transitionState.entry = entry;
} else {
return null;
} }
function restoreAnimatorState(entry: BackstackEntry, snapshot: AnimatorState): void { return transitionState;
}
function restoreTransitionState(entry: BackstackEntry, snapshot: TransitionState): void {
const expandedEntry = <any>entry; const expandedEntry = <any>entry;
if (snapshot.enterAnimator) { if (snapshot.enterTransitionListener) {
expandedEntry.enterAnimator = snapshot.enterAnimator; expandedEntry.enterTransitionListener = snapshot.enterTransitionListener;
} }
if (snapshot.exitAnimator) { if (snapshot.exitTransitionListener) {
expandedEntry.exitAnimator = snapshot.exitAnimator; expandedEntry.exitTransitionListener = snapshot.exitTransitionListener;
} }
if (snapshot.popEnterAnimator) { if (snapshot.reenterTransitionListener) {
expandedEntry.popEnterAnimator = snapshot.popEnterAnimator; expandedEntry.reenterTransitionListener = snapshot.reenterTransitionListener;
} }
if (snapshot.popExitAnimator) { if (snapshot.returnTransitionListener) {
expandedEntry.popExitAnimator = snapshot.popExitAnimator; expandedEntry.returnTransitionListener = snapshot.returnTransitionListener;
} }
expandedEntry.transitionName = snapshot.transitionName; expandedEntry.transitionName = snapshot.transitionName;
@ -777,6 +801,7 @@ export function setFragmentClass(clazz: any) {
class FragmentCallbacksImplementation implements AndroidFragmentCallbacks { class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
public frame: Frame; public frame: Frame;
public entry: BackstackEntry; public entry: BackstackEntry;
private backgroundBitmap: android.graphics.Bitmap = null;
@profile @profile
public onHiddenChanged(fragment: androidx.fragment.app.Fragment, hidden: boolean, superFunc: Function): void { public onHiddenChanged(fragment: androidx.fragment.app.Fragment, hidden: boolean, superFunc: Function): void {
@ -787,31 +812,17 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
} }
@profile @profile
public onCreateAnimator(fragment: org.nativescript.widgets.FragmentBase, transit: number, enter: boolean, nextAnim: number, superFunc: Function): android.animation.Animator { public onCreateAnimator(fragment: androidx.fragment.app.Fragment, transit: number, enter: boolean, nextAnim: number, superFunc: Function): android.animation.Animator {
// HACK: FragmentBase class MUST handle removing nested fragment scenario to workaround let animator = null;
// https://code.google.com/p/android/issues/detail?id=55228 const entry = <any>this.entry;
if (!enter && fragment.getRemovingParentFragment()) {
return superFunc.call(fragment, transit, enter, nextAnim); // Return enterAnimator only when new (no current entry) nested transition.
if (enter && entry.isNestedDefaultTransition) {
animator = entry.enterAnimator;
entry.isNestedDefaultTransition = false;
} }
let nextAnimString: string; return animator || superFunc.call(fragment, transit, enter, nextAnim);
switch (nextAnim) {
case AnimationType.enterFakeResourceId: nextAnimString = "enter"; break;
case AnimationType.exitFakeResourceId: nextAnimString = "exit"; break;
case AnimationType.popEnterFakeResourceId: nextAnimString = "popEnter"; break;
case AnimationType.popExitFakeResourceId: nextAnimString = "popExit"; break;
}
let animator = _onFragmentCreateAnimator(this.entry, fragment, nextAnim, enter);
if (!animator) {
animator = superFunc.call(fragment, transit, enter, nextAnim);
}
if (traceEnabled()) {
traceWrite(`${fragment}.onCreateAnimator(${transit}, ${enter ? "enter" : "exit"}, ${nextAnimString}): ${animator ? "animator" : "no animator"}`, traceCategories.NativeLifecycle);
}
return animator;
} }
@profile @profile
@ -902,7 +913,7 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
parentView.addViewInLayout(nativeView, -1, new org.nativescript.widgets.CommonLayoutParams()); parentView.addViewInLayout(nativeView, -1, new org.nativescript.widgets.CommonLayoutParams());
} }
parentView.removeView(nativeView); parentView.removeAllViews();
} }
} }
@ -918,11 +929,18 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
} }
@profile @profile
public onDestroyView(fragment: androidx.fragment.app.Fragment, superFunc: Function): void { public onDestroyView(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void {
if (traceEnabled()) { if (traceEnabled()) {
traceWrite(`${fragment}.onDestroyView()`, traceCategories.NativeLifecycle); traceWrite(`${fragment}.onDestroyView()`, traceCategories.NativeLifecycle);
} }
const hasRemovingParent = fragment.getRemovingParentFragment();
if (hasRemovingParent) {
const bitmapDrawable = new android.graphics.drawable.BitmapDrawable(application.android.context.getResources(), this.backgroundBitmap);
this.frame.nativeViewProtected.setBackgroundDrawable(bitmapDrawable);
this.backgroundBitmap = null;
}
superFunc.call(fragment); superFunc.call(fragment);
} }
@ -955,6 +973,18 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
} }
} }
@profile
public onPause(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void {
// Get view as bitmap and set it as background. This is workaround for the disapearing nested fragments.
// TODO: Consider removing it when update to androidx.fragment:1.2.0
const hasRemovingParent = fragment.getRemovingParentFragment();
if (hasRemovingParent) {
this.backgroundBitmap = this.loadBitmapFromView(this.frame.nativeViewProtected);
}
superFunc.call(fragment);
}
@profile @profile
public onStop(fragment: androidx.fragment.app.Fragment, superFunc: Function): void { public onStop(fragment: androidx.fragment.app.Fragment, superFunc: Function): void {
superFunc.call(fragment); superFunc.call(fragment);
@ -969,6 +999,22 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
return "NO ENTRY, " + superFunc.call(fragment); return "NO ENTRY, " + superFunc.call(fragment);
} }
} }
private loadBitmapFromView(view: android.view.View): android.graphics.Bitmap {
// Another way to get view bitmap. Test performance vs setDrawingCacheEnabled
// const width = view.getWidth();
// const height = view.getHeight();
// const bitmap = android.graphics.Bitmap.createBitmap(width, height, android.graphics.Bitmap.Config.ARGB_8888);
// const canvas = new android.graphics.Canvas(bitmap);
// view.layout(0, 0, width, height);
// view.draw(canvas);
view.setDrawingCacheEnabled(true);
const bitmap = android.graphics.Bitmap.createBitmap(view.getDrawingCache());
view.setDrawingCacheEnabled(false);
return bitmap;
}
} }
class ActivityCallbacksImplementation implements AndroidActivityCallbacks { class ActivityCallbacksImplementation implements AndroidActivityCallbacks {

View File

@ -418,6 +418,7 @@ export interface AndroidFragmentCallbacks {
onSaveInstanceState(fragment: any, outState: any, superFunc: Function): void; onSaveInstanceState(fragment: any, outState: any, superFunc: Function): void;
onDestroyView(fragment: any, superFunc: Function): void; onDestroyView(fragment: any, superFunc: Function): void;
onDestroy(fragment: any, superFunc: Function): void; onDestroy(fragment: any, superFunc: Function): void;
onPause(fragment: any, superFunc: Function): void;
onStop(fragment: any, superFunc: Function): void; onStop(fragment: any, superFunc: Function): void;
toStringOverride(fragment: any, superFunc: Function): string; toStringOverride(fragment: any, superFunc: Function): string;
} }

View File

@ -6,7 +6,7 @@ import { TabStripItem } from "../tab-strip-item";
import { ViewBase, AddArrayFromBuilder, AddChildFromBuilder, EventData } from "../../core/view"; import { ViewBase, AddArrayFromBuilder, AddChildFromBuilder, EventData } from "../../core/view";
// Requires // Requires
import { View, Property, CoercibleProperty, isIOS } from "../../core/view"; import { View, Property, CoercibleProperty, isIOS, Color } from "../../core/view";
// TODO: Impl trace // TODO: Impl trace
// export const traceCategory = "TabView"; // export const traceCategory = "TabView";
@ -265,6 +265,8 @@ export const selectedIndexProperty = new CoercibleProperty<TabNavigationBase, nu
}); });
selectedIndexProperty.register(TabNavigationBase); selectedIndexProperty.register(TabNavigationBase);
export const _tabs = new Array<WeakRef<TabNavigationBase>>();
export const itemsProperty = new Property<TabNavigationBase, TabContentItem[]>({ export const itemsProperty = new Property<TabNavigationBase, TabContentItem[]>({
name: "items", valueChanged: (target, oldValue, newValue) => { name: "items", valueChanged: (target, oldValue, newValue) => {
target.onItemsChanged(oldValue, newValue); target.onItemsChanged(oldValue, newValue);

View File

@ -27,6 +27,7 @@ interface PagerAdapter {
const TABID = "_tabId"; const TABID = "_tabId";
const INDEX = "_index"; const INDEX = "_index";
let PagerAdapter: PagerAdapter; let PagerAdapter: PagerAdapter;
let appResources: android.content.res.Resources;
function makeFragmentName(viewId: number, id: number): string { function makeFragmentName(viewId: number, id: number): string {
return "android:viewpager:" + viewId + ":" + id; return "android:viewpager:" + viewId + ":" + id;
@ -48,8 +49,9 @@ function initializeNativeClasses() {
} }
class TabFragmentImplementation extends org.nativescript.widgets.FragmentBase { class TabFragmentImplementation extends org.nativescript.widgets.FragmentBase {
private tab: TabView; private owner: TabView;
private index: number; private index: number;
private backgroundBitmap: android.graphics.Bitmap = null;
constructor() { constructor() {
super(); super();
@ -70,18 +72,61 @@ function initializeNativeClasses() {
public onCreate(savedInstanceState: android.os.Bundle): void { public onCreate(savedInstanceState: android.os.Bundle): void {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
const args = this.getArguments(); const args = this.getArguments();
this.tab = getTabById(args.getInt(TABID)); this.owner = getTabById(args.getInt(TABID));
this.index = args.getInt(INDEX); this.index = args.getInt(INDEX);
if (!this.tab) { if (!this.owner) {
throw new Error(`Cannot find TabView`); throw new Error(`Cannot find TabView`);
} }
} }
public onCreateView(inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle): android.view.View { public onCreateView(inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle): android.view.View {
const tabItem = this.tab.items[this.index]; const tabItem = this.owner.items[this.index];
return tabItem.view.nativeViewProtected; return tabItem.view.nativeViewProtected;
} }
public onDestroyView() {
const hasRemovingParent = this.getRemovingParentFragment();
// Get view as bitmap and set it as background. This is workaround for the disapearing nested fragments.
// TODO: Consider removing it when update to androidx.fragment:1.2.0
if (hasRemovingParent && this.owner.selectedIndex === this.index) {
const bitmapDrawable = new android.graphics.drawable.BitmapDrawable(appResources, this.backgroundBitmap);
this.owner._originalBackground = this.owner.backgroundColor || new Color("White");
this.owner.nativeViewProtected.setBackground(bitmapDrawable);
this.backgroundBitmap = null;
}
super.onDestroyView();
}
public onPause(): void {
const hasRemovingParent = this.getRemovingParentFragment();
// Get view as bitmap and set it as background. This is workaround for the disapearing nested fragments.
// TODO: Consider removing it when update to androidx.fragment:1.2.0
if (hasRemovingParent && this.owner.selectedIndex === this.index) {
this.backgroundBitmap = this.loadBitmapFromView(this.owner.nativeViewProtected);
}
super.onPause();
}
private loadBitmapFromView(view: android.view.View): android.graphics.Bitmap {
// Another way to get view bitmap. Test performance vs setDrawingCacheEnabled
// const width = view.getWidth();
// const height = view.getHeight();
// const bitmap = android.graphics.Bitmap.createBitmap(width, height, android.graphics.Bitmap.Config.ARGB_8888);
// const canvas = new android.graphics.Canvas(bitmap);
// view.layout(0, 0, width, height);
// view.draw(canvas);
view.setDrawingCacheEnabled(true);
const bitmap = android.graphics.Bitmap.createBitmap(view.getDrawingCache());
view.setDrawingCacheEnabled(false);
return bitmap;
}
} }
const POSITION_UNCHANGED = -1; const POSITION_UNCHANGED = -1;
@ -233,6 +278,7 @@ function initializeNativeClasses() {
} }
PagerAdapter = FragmentPagerAdapter; PagerAdapter = FragmentPagerAdapter;
appResources = application.android.context.getResources();
} }
function createTabItemSpec(item: TabViewItem): org.nativescript.widgets.TabItemSpec { function createTabItemSpec(item: TabViewItem): org.nativescript.widgets.TabItemSpec {
@ -249,7 +295,7 @@ function createTabItemSpec(item: TabViewItem): org.nativescript.widgets.TabItemS
const is = fromFileOrResource(item.iconSource); const is = fromFileOrResource(item.iconSource);
if (is) { if (is) {
// TODO: Make this native call that accepts string so that we don't load Bitmap in JS. // TODO: Make this native call that accepts string so that we don't load Bitmap in JS.
result.iconDrawable = new android.graphics.drawable.BitmapDrawable(application.android.context.getResources(), is.android); result.iconDrawable = new android.graphics.drawable.BitmapDrawable(appResources, is.android);
} else { } else {
traceMissingIcon(item.iconSource); traceMissingIcon(item.iconSource);
} }
@ -397,6 +443,7 @@ export class TabView extends TabViewBase {
private _viewPager: androidx.viewpager.widget.ViewPager; private _viewPager: androidx.viewpager.widget.ViewPager;
private _pagerAdapter: androidx.viewpager.widget.PagerAdapter; private _pagerAdapter: androidx.viewpager.widget.PagerAdapter;
private _androidViewId: number = -1; private _androidViewId: number = -1;
public _originalBackground: any;
constructor() { constructor() {
super(); super();
@ -534,6 +581,12 @@ export class TabView extends TabViewBase {
public onLoaded(): void { public onLoaded(): void {
super.onLoaded(); super.onLoaded();
if (this._originalBackground) {
this.backgroundColor = null;
this.backgroundColor = this._originalBackground;
this._originalBackground = null;
}
this.setAdapterItems(this.items); this.setAdapterItems(this.items);
} }

View File

@ -23,14 +23,16 @@ const ACCENT_COLOR = "colorAccent";
const PRIMARY_COLOR = "colorPrimary"; const PRIMARY_COLOR = "colorPrimary";
const DEFAULT_ELEVATION = 4; const DEFAULT_ELEVATION = 4;
const TABID = "_tabId";
const INDEX = "_index";
interface PagerAdapter { interface PagerAdapter {
new(owner: Tabs): androidx.viewpager.widget.PagerAdapter; new(owner: Tabs): androidx.viewpager.widget.PagerAdapter;
} }
const TABID = "_tabId";
const INDEX = "_index";
let PagerAdapter: PagerAdapter; let PagerAdapter: PagerAdapter;
let TabsBar: any; let TabsBar: any;
let appResources: android.content.res.Resources;
function makeFragmentName(viewId: number, id: number): string { function makeFragmentName(viewId: number, id: number): string {
return "android:viewpager:" + viewId + ":" + id; return "android:viewpager:" + viewId + ":" + id;
@ -52,8 +54,9 @@ function initializeNativeClasses() {
} }
class TabFragmentImplementation extends org.nativescript.widgets.FragmentBase { class TabFragmentImplementation extends org.nativescript.widgets.FragmentBase {
private tab: Tabs; private owner: Tabs;
private index: number; private index: number;
private backgroundBitmap: android.graphics.Bitmap = null;
constructor() { constructor() {
super(); super();
@ -74,18 +77,61 @@ function initializeNativeClasses() {
public onCreate(savedInstanceState: android.os.Bundle): void { public onCreate(savedInstanceState: android.os.Bundle): void {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
const args = this.getArguments(); const args = this.getArguments();
this.tab = getTabById(args.getInt(TABID)); this.owner = getTabById(args.getInt(TABID));
this.index = args.getInt(INDEX); this.index = args.getInt(INDEX);
if (!this.tab) { if (!this.owner) {
throw new Error(`Cannot find TabView`); throw new Error(`Cannot find TabView`);
} }
} }
public onCreateView(inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle): android.view.View { public onCreateView(inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle): android.view.View {
const tabItem = this.tab.items[this.index]; const tabItem = this.owner.items[this.index];
return tabItem.nativeViewProtected; return tabItem.nativeViewProtected;
} }
public onDestroyView() {
const hasRemovingParent = this.getRemovingParentFragment();
// Get view as bitmap and set it as background. This is workaround for the disapearing nested fragments.
// TODO: Consider removing it when update to androidx.fragment:1.2.0
if (hasRemovingParent && this.owner.selectedIndex === this.index) {
const bitmapDrawable = new android.graphics.drawable.BitmapDrawable(appResources, this.backgroundBitmap);
this.owner._originalBackground = this.owner.backgroundColor || new Color("White");
this.owner.nativeViewProtected.setBackgroundDrawable(bitmapDrawable);
this.backgroundBitmap = null;
}
super.onDestroyView();
}
public onPause(): void {
const hasRemovingParent = this.getRemovingParentFragment();
// Get view as bitmap and set it as background. This is workaround for the disapearing nested fragments.
// TODO: Consider removing it when update to androidx.fragment:1.2.0
if (hasRemovingParent && this.owner.selectedIndex === this.index) {
this.backgroundBitmap = this.loadBitmapFromView(this.owner.nativeViewProtected);
}
super.onPause();
}
private loadBitmapFromView(view: android.view.View): android.graphics.Bitmap {
// Another way to get view bitmap. Test performance vs setDrawingCacheEnabled
// const width = view.getWidth();
// const height = view.getHeight();
// const bitmap = android.graphics.Bitmap.createBitmap(width, height, android.graphics.Bitmap.Config.ARGB_8888);
// const canvas = new android.graphics.Canvas(bitmap);
// view.layout(0, 0, width, height);
// view.draw(canvas);
view.setDrawingCacheEnabled(true);
const bitmap = android.graphics.Bitmap.createBitmap(view.getDrawingCache());
view.setDrawingCacheEnabled(false);
return bitmap;
}
} }
const POSITION_UNCHANGED = -1; const POSITION_UNCHANGED = -1;
@ -285,6 +331,7 @@ function initializeNativeClasses() {
PagerAdapter = FragmentPagerAdapter; PagerAdapter = FragmentPagerAdapter;
TabsBar = TabsBarImplementation; TabsBar = TabsBarImplementation;
appResources = application.android.context.getResources();
} }
let defaultAccentColor: number = undefined; let defaultAccentColor: number = undefined;
@ -325,6 +372,7 @@ export class Tabs extends TabsBase {
private _viewPager: androidx.viewpager.widget.ViewPager; private _viewPager: androidx.viewpager.widget.ViewPager;
private _pagerAdapter: androidx.viewpager.widget.PagerAdapter; private _pagerAdapter: androidx.viewpager.widget.PagerAdapter;
private _androidViewId: number = -1; private _androidViewId: number = -1;
public _originalBackground: any;
constructor() { constructor() {
super(); super();
@ -456,6 +504,12 @@ export class Tabs extends TabsBase {
public onLoaded(): void { public onLoaded(): void {
super.onLoaded(); super.onLoaded();
if (this._originalBackground) {
this.backgroundColor = null;
this.backgroundColor = this._originalBackground;
this._originalBackground = null;
}
this.setItems((<any>this.items)); this.setItems((<any>this.items));
if (this.tabStrip) { if (this.tabStrip) {
@ -653,7 +707,7 @@ export class Tabs extends TabsBase {
image = this.getFixedSizeIcon(image); image = this.getFixedSizeIcon(image);
} }
imageDrawable = new android.graphics.drawable.BitmapDrawable(application.android.context.getResources(), image); imageDrawable = new android.graphics.drawable.BitmapDrawable(appResources, image);
} else { } else {
// TODO // TODO
// traceMissingIcon(iconSource); // traceMissingIcon(iconSource);

View File

@ -1,7 +1,8 @@
import { Transition, AndroidTransitionType } from "./transition"; import { Transition, AndroidTransitionType } from "./transition";
export class FadeTransition extends Transition { export class FadeTransition extends Transition {
public createAndroidAnimator(transitionType: string): android.animation.Animator { public createAndroidAnimator(transitionType: string): android.animation.AnimatorSet {
const animatorSet = new android.animation.AnimatorSet();
const alphaValues = Array.create("float", 2); const alphaValues = Array.create("float", 2);
switch (transitionType) { switch (transitionType) {
case AndroidTransitionType.enter: case AndroidTransitionType.enter:
@ -16,14 +17,15 @@ export class FadeTransition extends Transition {
break; break;
} }
const animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", alphaValues); const animator = <android.animation.Animator>android.animation.ObjectAnimator.ofFloat(null, "alpha", alphaValues);
const duration = this.getDuration(); const duration = this.getDuration();
if (duration !== undefined) { if (duration !== undefined) {
animator.setDuration(duration); animator.setDuration(duration);
} }
animator.setInterpolator(this.getCurve()); animator.setInterpolator(this.getCurve());
animatorSet.play(animator);
return animator; return animatorSet;
} }
} }

View File

@ -9,10 +9,10 @@ export class FlipTransition extends Transition {
this._direction = direction; this._direction = direction;
} }
public createAndroidAnimator(transitionType: string): android.animation.Animator { public createAndroidAnimator(transitionType: string): android.animation.AnimatorSet {
let objectAnimators; let objectAnimators;
let values; let values;
let animator: android.animation.ObjectAnimator; let animator: android.animation.Animator; //android.animation.ObjectAnimator;
const animatorSet = new android.animation.AnimatorSet(); const animatorSet = new android.animation.AnimatorSet();
const fullDuration = this.getDuration() || 300; const fullDuration = this.getDuration() || 300;
const interpolator = this.getCurve(); const interpolator = this.getCurve();
@ -20,30 +20,23 @@ export class FlipTransition extends Transition {
switch (transitionType) { switch (transitionType) {
case AndroidTransitionType.enter: // card_flip_right_in case AndroidTransitionType.enter: // card_flip_right_in
objectAnimators = Array.create(android.animation.Animator, 3); objectAnimators = Array.create(android.animation.Animator, 2);
values = Array.create("float", 2);
values[0] = 1.0;
values[1] = 0.0;
animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
animator.setDuration(0);
objectAnimators[0] = animator;
values = Array.create("float", 2); values = Array.create("float", 2);
values[0] = rotationY; values[0] = rotationY;
values[1] = 0.0; values[1] = 0.0;
animator = android.animation.ObjectAnimator.ofFloat(null, "rotationY", values); animator = <android.animation.Animator>android.animation.ObjectAnimator.ofFloat(null, "rotationY", values);
animator.setInterpolator(interpolator); animator.setInterpolator(interpolator);
animator.setDuration(fullDuration); animator.setDuration(fullDuration);
objectAnimators[1] = animator; objectAnimators[0] = animator;
values = Array.create("float", 2); values = Array.create("float", 3);
values[0] = 0.0; values[0] = 0.0;
values[1] = 1.0; values[1] = 0.0;
animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", values); values[2] = 255.0;
animator.setStartDelay(fullDuration / 2); animator = <android.animation.Animator>android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
animator.setDuration(1); animator.setDuration(fullDuration / 2);
objectAnimators[2] = animator; objectAnimators[1] = animator;
break; break;
case AndroidTransitionType.exit: // card_flip_right_out case AndroidTransitionType.exit: // card_flip_right_out
objectAnimators = Array.create(android.animation.Animator, 2); objectAnimators = Array.create(android.animation.Animator, 2);
@ -51,44 +44,37 @@ export class FlipTransition extends Transition {
values = Array.create("float", 2); values = Array.create("float", 2);
values[0] = 0.0; values[0] = 0.0;
values[1] = -rotationY; values[1] = -rotationY;
animator = android.animation.ObjectAnimator.ofFloat(null, "rotationY", values); animator = <android.animation.Animator>android.animation.ObjectAnimator.ofFloat(null, "rotationY", values);
animator.setInterpolator(interpolator); animator.setInterpolator(interpolator);
animator.setDuration(fullDuration); animator.setDuration(fullDuration);
objectAnimators[0] = animator; objectAnimators[0] = animator;
values = Array.create("float", 2); values = Array.create("float", 3);
values[0] = 1.0; values[0] = 255.0;
values[1] = 0.0; values[1] = 0.0;
animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", values); values[2] = 0.0;
animator.setStartDelay(fullDuration / 2); animator = <android.animation.Animator>android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
animator.setDuration(1); animator.setDuration(fullDuration / 2);
objectAnimators[1] = animator; objectAnimators[1] = animator;
break; break;
case AndroidTransitionType.popEnter: // card_flip_left_in case AndroidTransitionType.popEnter: // card_flip_left_in
objectAnimators = Array.create(android.animation.Animator, 3); objectAnimators = Array.create(android.animation.Animator, 2);
values = Array.create("float", 2);
values[0] = 1.0;
values[1] = 0.0;
animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
animator.setDuration(0);
objectAnimators[0] = animator;
values = Array.create("float", 2); values = Array.create("float", 2);
values[0] = -rotationY; values[0] = -rotationY;
values[1] = 0.0; values[1] = 0.0;
animator = android.animation.ObjectAnimator.ofFloat(null, "rotationY", values); animator = <android.animation.Animator>android.animation.ObjectAnimator.ofFloat(null, "rotationY", values);
animator.setInterpolator(interpolator); animator.setInterpolator(interpolator);
animator.setDuration(fullDuration); animator.setDuration(fullDuration);
objectAnimators[1] = animator; objectAnimators[0] = animator;
values = Array.create("float", 2); values = Array.create("float", 3);
values[0] = 0.0; values[0] = 0.0;
values[1] = 1.0; values[1] = 0.0;
animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", values); values[2] = 255.0;
animator.setStartDelay(fullDuration / 2); animator = <android.animation.Animator>android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
animator.setDuration(1); animator.setDuration(fullDuration / 2);
objectAnimators[2] = animator; objectAnimators[1] = animator;
break; break;
case AndroidTransitionType.popExit: // card_flip_left_out case AndroidTransitionType.popExit: // card_flip_left_out
objectAnimators = Array.create(android.animation.Animator, 2); objectAnimators = Array.create(android.animation.Animator, 2);
@ -96,17 +82,17 @@ export class FlipTransition extends Transition {
values = Array.create("float", 2); values = Array.create("float", 2);
values[0] = 0.0; values[0] = 0.0;
values[1] = rotationY; values[1] = rotationY;
animator = android.animation.ObjectAnimator.ofFloat(null, "rotationY", values); animator = <android.animation.Animator>android.animation.ObjectAnimator.ofFloat(null, "rotationY", values);
animator.setInterpolator(interpolator); animator.setInterpolator(interpolator);
animator.setDuration(fullDuration); animator.setDuration(fullDuration);
objectAnimators[0] = animator; objectAnimators[0] = animator;
values = Array.create("float", 2); values = Array.create("float", 3);
values[0] = 1.0; values[0] = 255.0;
values[1] = 0.0; values[1] = 0.0;
animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", values); values[2] = 0.0;
animator.setStartDelay(fullDuration / 2); animator = <android.animation.Animator>android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
animator.setDuration(1); animator.setDuration(fullDuration / 2);
objectAnimators[1] = animator; objectAnimators[1] = animator;
break; break;
} }

View File

@ -1,6 +1,11 @@
declare module org { declare module org {
module nativescript { module nativescript {
module widgets { module widgets {
export class CustomTransition extends androidx.transition.Visibility {
constructor(animatorSet: android.animation.AnimatorSet, transitionName: string);
public setResetOnTransitionEnd(resetOnTransitionEnd: boolean): void;
public getTransitionName(): string;
}
export module Async { export module Async {
export class CompleteCallback { export class CompleteCallback {
constructor(implementation: ICompleteCallback); constructor(implementation: ICompleteCallback);
@ -57,6 +62,12 @@
} }
} }
export class FragmentBase extends androidx.fragment.app.Fragment {
constructor();
public getRemovingParentFragment(): androidx.fragment.app.Fragment;
}
export class BorderDrawable extends android.graphics.drawable.ColorDrawable { export class BorderDrawable extends android.graphics.drawable.ColorDrawable {
constructor(density: number); constructor(density: number);
constructor(density: number, id: string); constructor(density: number, id: string);
@ -173,12 +184,6 @@
public verticalAlignment: VerticalAlignment; public verticalAlignment: VerticalAlignment;
} }
export class FragmentBase extends androidx.fragment.app.Fragment {
constructor();
public getRemovingParentFragment(): androidx.fragment.app.Fragment;
}
export enum Stretch { export enum Stretch {
none, none,
aspectFill, aspectFill,