Merge pull request #1473 from NativeScript/transitions

Resolved #811: Page Navigation Transitions
This commit is contained in:
Rossen Hristov
2016-02-03 13:45:53 +02:00
41 changed files with 2090 additions and 493 deletions

View File

@@ -70,7 +70,7 @@ export class Animation implements definition.Animation {
var i = 0;
var length = animationDefinitions.length;
for (; i < length; i++) {
animationDefinitions[i].curve = this._resolveAnimationCurve(animationDefinitions[i].curve);
animationDefinitions[i].curve = definition._resolveAnimationCurve(animationDefinitions[i].curve);
this._propertyAnimations = this._propertyAnimations.concat(Animation._createPropertyAnimations(animationDefinitions[i]));
}
@@ -92,10 +92,6 @@ export class Animation implements definition.Animation {
this._reject(new Error("Animation cancelled."));
}
_resolveAnimationCurve(curve: any): any {
//
}
private static _createPropertyAnimations(animationDefinition: definition.AnimationDefinition): Array<PropertyAnimation> {
if (!animationDefinition.target) {
throw new Error("No animation target specified.");

View File

@@ -298,27 +298,31 @@ export class Animation extends common.Animation implements definition.Animation
this._propertyResetCallbacks = this._propertyResetCallbacks.concat(propertyResetCallbacks);
}
_resolveAnimationCurve(curve: any): any {
switch (curve) {
case enums.AnimationCurve.easeIn:
trace.write("Animation curve resolved to android.view.animation.AccelerateInterpolator(1).", trace.categories.Animation);
return new android.view.animation.AccelerateInterpolator(1);
case enums.AnimationCurve.easeOut:
trace.write("Animation curve resolved to android.view.animation.DecelerateInterpolator(1).", trace.categories.Animation);
return new android.view.animation.DecelerateInterpolator(1);
case enums.AnimationCurve.easeInOut:
trace.write("Animation curve resolved to android.view.animation.AccelerateDecelerateInterpolator().", trace.categories.Animation);
return new android.view.animation.AccelerateDecelerateInterpolator();
case enums.AnimationCurve.linear:
trace.write("Animation curve resolved to android.view.animation.LinearInterpolator().", trace.categories.Animation);
return new android.view.animation.LinearInterpolator();
default:
trace.write("Animation curve resolved to original: " + curve, trace.categories.Animation);
return curve;
}
}
private static _getAndroidRepeatCount(iterations: number): number {
return (iterations === Number.POSITIVE_INFINITY) ? android.view.animation.Animation.INFINITE : iterations - 1;
}
}
}
var easeIn = new android.view.animation.AccelerateInterpolator(1);
var easeOut = new android.view.animation.DecelerateInterpolator(1);
var easeInOut = new android.view.animation.AccelerateDecelerateInterpolator();
var linear = new android.view.animation.LinearInterpolator();
export function _resolveAnimationCurve(curve: any): any {
switch (curve) {
case enums.AnimationCurve.easeIn:
trace.write("Animation curve resolved to android.view.animation.AccelerateInterpolator(1).", trace.categories.Animation);
return easeIn;
case enums.AnimationCurve.easeOut:
trace.write("Animation curve resolved to android.view.animation.DecelerateInterpolator(1).", trace.categories.Animation);
return easeOut;
case enums.AnimationCurve.easeInOut:
trace.write("Animation curve resolved to android.view.animation.AccelerateDecelerateInterpolator().", trace.categories.Animation);
return easeInOut;
case enums.AnimationCurve.linear:
trace.write("Animation curve resolved to android.view.animation.LinearInterpolator().", trace.categories.Animation);
return linear;
default:
trace.write("Animation curve resolved to original: " + curve, trace.categories.Animation);
return curve;
}
}

View File

@@ -76,8 +76,9 @@
public play: () => Promise<void>;
public cancel: () => void;
public isPlaying: boolean;
//@private
_resolveAnimationCurve(curve: any): any
//@endprivate
}
//@private
export function _resolveAnimationCurve(curve: any): any;
//@endprivate
}

View File

@@ -144,26 +144,6 @@ export class Animation extends common.Animation implements definition.Animation
this._iOSAnimationFunction = Animation._createiOSAnimationFunction(this._mergedPropertyAnimations, 0, this._playSequentially, animationFinishedCallback);
}
_resolveAnimationCurve(curve: any): any {
switch (curve) {
case enums.AnimationCurve.easeIn:
trace.write("Animation curve resolved to UIViewAnimationCurve.UIViewAnimationCurveEaseIn.", trace.categories.Animation);
return UIViewAnimationCurve.UIViewAnimationCurveEaseIn;
case enums.AnimationCurve.easeOut:
trace.write("Animation curve resolved to UIViewAnimationCurve.UIViewAnimationCurveEaseOut.", trace.categories.Animation);
return UIViewAnimationCurve.UIViewAnimationCurveEaseOut;
case enums.AnimationCurve.easeInOut:
trace.write("Animation curve resolved to UIViewAnimationCurve.UIViewAnimationCurveEaseInOut.", trace.categories.Animation);
return UIViewAnimationCurve.UIViewAnimationCurveEaseInOut;
case enums.AnimationCurve.linear:
trace.write("Animation curve resolved to UIViewAnimationCurve.UIViewAnimationCurveLinear.", trace.categories.Animation);
return UIViewAnimationCurve.UIViewAnimationCurveLinear;
default:
trace.write("Animation curve resolved to original: " + curve, trace.categories.Animation);
return curve;
}
}
private static _createiOSAnimationFunction(propertyAnimations: Array<common.PropertyAnimation>, index: number, playSequentially: boolean, finishedCallback: (cancelled?: boolean) => void): Function {
return (cancelled?: boolean) => {
if (cancelled && finishedCallback) {
@@ -382,3 +362,23 @@ export function _getTransformMismatchErrorMessage(view: viewModule.View): string
return undefined;
}
export function _resolveAnimationCurve(curve: any): any {
switch (curve) {
case enums.AnimationCurve.easeIn:
trace.write("Animation curve resolved to UIViewAnimationCurve.UIViewAnimationCurveEaseIn.", trace.categories.Animation);
return UIViewAnimationCurve.UIViewAnimationCurveEaseIn;
case enums.AnimationCurve.easeOut:
trace.write("Animation curve resolved to UIViewAnimationCurve.UIViewAnimationCurveEaseOut.", trace.categories.Animation);
return UIViewAnimationCurve.UIViewAnimationCurveEaseOut;
case enums.AnimationCurve.easeInOut:
trace.write("Animation curve resolved to UIViewAnimationCurve.UIViewAnimationCurveEaseInOut.", trace.categories.Animation);
return UIViewAnimationCurve.UIViewAnimationCurveEaseInOut;
case enums.AnimationCurve.linear:
trace.write("Animation curve resolved to UIViewAnimationCurve.UIViewAnimationCurveLinear.", trace.categories.Animation);
return UIViewAnimationCurve.UIViewAnimationCurveLinear;
default:
trace.write("Animation curve resolved to original: " + curve, trace.categories.Animation);
return curve;
}
}

View File

@@ -8,7 +8,6 @@ import styleScope = require("../styling/style-scope");
import enums = require("ui/enums");
import utils = require("utils/utils");
import color = require("color");
import animationModule = require("ui/animation");
import observable = require("data/observable");
import {PropertyMetadata, ProxyObject} from "ui/core/proxy";
import {PropertyMetadataSettings, PropertyChangeData, Property, ValueSource, PropertyMetadata as doPropertyMetadata} from "ui/core/dependency-observable";
@@ -17,6 +16,7 @@ import {CommonLayoutParams, nativeLayoutParamsProperty} from "ui/styling/style";
import * as visualStateConstants from "ui/styling/visual-state-constants";
import * as bindableModule from "ui/core/bindable";
import * as visualStateModule from "../styling/visual-state";
import * as animModule from "ui/animation";
var bindable: typeof bindableModule;
function ensureBindable() {
@@ -1139,11 +1139,12 @@ export class View extends ProxyObject implements definition.View {
return undefined;
}
public animate(animation: animationModule.AnimationDefinition): Promise<void> {
public animate(animation: any): Promise<void> {
return this.createAnimation(animation).play();
}
public createAnimation(animation: animationModule.AnimationDefinition): animationModule.Animation {
public createAnimation(animation: any): any {
var animationModule: typeof animModule = require("ui/animation");
var that = this;
animation.target = that;
return new animationModule.Animation([animation]);

View File

@@ -138,9 +138,11 @@ export class Frame extends CustomLayoutView implements definition.Frame {
private _backStack: Array<definition.BackstackEntry>;
public _currentEntry: definition.BackstackEntry;
private _animated: boolean;
private _navigationTransition: definition.NavigationTransition;
public _isInFrameStack = false;
public static defaultAnimatedNavigation = true;
public static defaultNavigationTransition: definition.NavigationTransition;
// TODO: Currently our navigation will not be synchronized in case users directly call native navigation methods like Activity.startActivity.
@@ -160,7 +162,7 @@ export class Frame extends CustomLayoutView implements definition.Frame {
* @param to The backstack entry to navigate back to.
*/
public goBack(backstackEntry?: definition.BackstackEntry) {
trace.write(this._getTraceId() + ".goBack();", trace.categories.Navigation);
trace.write(`GO BACK`, trace.categories.Navigation);
if (!this.canGoBack()) {
// TODO: Do we need to throw an error?
return;
@@ -187,12 +189,12 @@ export class Frame extends CustomLayoutView implements definition.Frame {
this._processNavigationContext(navigationContext);
}
else {
trace.write(this._getTraceId() + ".goBack scheduled;", trace.categories.Navigation);
trace.write(`Going back scheduled`, trace.categories.Navigation);
}
}
public navigate(param: any) {
trace.write(this._getTraceId() + ".navigate();", trace.categories.Navigation);
trace.write(`NAVIGATE`, trace.categories.Navigation);
var entry = buildEntryFromArgs(param);
var page = resolvePageFromEntry(entry);
@@ -215,7 +217,7 @@ export class Frame extends CustomLayoutView implements definition.Frame {
this._processNavigationContext(navigationContext);
}
else {
trace.write(this._getTraceId() + ".navigation scheduled;", trace.categories.Navigation);
trace.write(`Navigation scheduled`, trace.categories.Navigation);
}
}
@@ -258,7 +260,7 @@ export class Frame extends CustomLayoutView implements definition.Frame {
}
public _updateActionBar(page?: Page) {
trace.write("calling _updateActionBar on Frame", trace.categories.Navigation);
//trace.write("calling _updateActionBar on Frame", trace.categories.Navigation);
}
private _processNavigationContext(navigationContext: NavigationContext) {
@@ -318,10 +320,19 @@ export class Frame extends CustomLayoutView implements definition.Frame {
public get animated(): boolean {
return this._animated;
}
public set animated(value: boolean) {
this._animated = value;
}
public get navigationTransition(): definition.NavigationTransition {
return this._navigationTransition;
}
public set navigationTransition(value: definition.NavigationTransition) {
this._navigationTransition = value;
}
get backStack(): Array<definition.BackstackEntry> {
return this._backStack.slice();
}
@@ -375,7 +386,7 @@ export class Frame extends CustomLayoutView implements definition.Frame {
}
}
public _getIsAnimatedNavigation(entry: definition.NavigationEntry) {
public _getIsAnimatedNavigation(entry: definition.NavigationEntry): boolean {
if (entry && isDefined(entry.animated)) {
return entry.animated;
}
@@ -387,8 +398,16 @@ export class Frame extends CustomLayoutView implements definition.Frame {
return Frame.defaultAnimatedNavigation;
}
private _getTraceId(): string {
return "Frame<" + this._domId + ">";
public _getNavigationTransition(entry: definition.NavigationEntry): definition.NavigationTransition {
if (entry && isDefined(entry.navigationTransition)) {
return entry.navigationTransition;
}
if (isDefined(this.navigationTransition)) {
return this.navigationTransition;
}
return Frame.defaultNavigationTransition;
}
public get navigationBarHeight(): number {

View File

@@ -6,6 +6,7 @@ import observable = require("data/observable");
import application = require("application");
import * as types from "utils/types";
import * as utilsModule from "utils/utils";
import transitionModule = require("ui/transition");
global.moduleMerge(frameCommon, exports);
@@ -15,6 +16,7 @@ var HIDDEN = "_hidden";
var INTENT_EXTRA = "com.tns.activity";
var ANDROID_FRAME = "android_frame";
var BACKSTACK_TAG = "_backstackTag";
var IS_BACK = "_isBack";
var NAV_DEPTH = "_navDepth";
var CLEARING_HISTORY = "_clearingHistory";
var activityInitialized = false;
@@ -28,67 +30,78 @@ function ensureFragmentClass() {
}
FragmentClass = (<any>android.app.Fragment).extend({
onCreate: function (savedInstanceState: android.os.Bundle) {
trace.write(`${this.getTag()}.onCreate(${savedInstanceState})`, trace.categories.NativeLifecycle);
this.super.onCreate(savedInstanceState);
this.super.setHasOptionsMenu(true);
},
onCreate: function (savedInstanceState: android.os.Bundle) {
trace.write(`PageFragmentBody.onCreate(${savedInstanceState})`, trace.categories.NativeLifecycle);
this.super.onCreate(savedInstanceState);
this.super.setHasOptionsMenu(true);
},
onCreateView: function (inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle): android.view.View {
var entry = this.entry;
var page = entry.resolvedPage;
trace.write(`PageFragmentBody.onCreateView(${inflater}, ${page}, ${savedInstanceState})`, trace.categories.NativeLifecycle);
if (savedInstanceState && savedInstanceState.getBoolean(HIDDEN, false)) {
this.super.getFragmentManager().beginTransaction().hide(this).commit();
page._onAttached(this.getActivity());
}
else {
onFragmentShown(this);
}
return page._nativeView;
},
onHiddenChanged: function (hidden: boolean) {
trace.write(`PageFragmentBody.onHiddenChanged(${hidden})`, trace.categories.NativeLifecycle);
this.super.onHiddenChanged(hidden);
if (hidden) {
onFragmentHidden(this);
}
else {
onFragmentShown(this);
}
},
onSaveInstanceState: function (outState: android.os.Bundle) {
trace.write(`PageFragmentBody.onSaveInstanceState(${outState})`, trace.categories.NativeLifecycle);
this.super.onSaveInstanceState(outState);
if (this.isHidden()) {
outState.putBoolean(HIDDEN, true);
}
},
onDestroyView: function () {
trace.write(`PageFragmentBody.onDestroyView()`, trace.categories.NativeLifecycle);
this.super.onDestroyView();
onFragmentHidden(this);
},
onDestroy: function () {
trace.write(`PageFragmentBody.onDestroy()`, trace.categories.NativeLifecycle);
this.super.onDestroy();
var utils: typeof utilsModule = require("utils/utils");
utils.GC();
onCreateView: function (inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle): android.view.View {
trace.write(`${this.getTag()}.onCreateView(inflater, container, ${savedInstanceState})`, trace.categories.NativeLifecycle);
var entry = this.entry;
var page = entry.resolvedPage;
if (savedInstanceState && savedInstanceState.getBoolean(HIDDEN, false)) {
this.super.getFragmentManager().beginTransaction().hide(this).commit();
page._onAttached(this.getActivity());
}
});
else {
onFragmentShown(this);
}
return page._nativeView;
},
onHiddenChanged: function (hidden: boolean) {
trace.write(`${this.getTag()}.onHiddenChanged(${hidden})`, trace.categories.NativeLifecycle);
this.super.onHiddenChanged(hidden);
if (hidden) {
onFragmentHidden(this);
}
else {
onFragmentShown(this);
}
},
onSaveInstanceState: function (outState: android.os.Bundle) {
trace.write(`${this.getTag()}.onSaveInstanceState(${outState})`, trace.categories.NativeLifecycle);
this.super.onSaveInstanceState(outState);
if (this.isHidden()) {
outState.putBoolean(HIDDEN, true);
}
},
onDestroyView: function () {
trace.write(`${this.getTag()}.onDestroyView()`, trace.categories.NativeLifecycle);
this.super.onDestroyView();
onFragmentHidden(this);
},
onDestroy: function () {
trace.write(`${this.getTag()}.onDestroy()`, trace.categories.NativeLifecycle);
this.super.onDestroy();
var utils: typeof utilsModule = require("utils/utils");
utils.GC();
},
onCreateAnimator: function (transit: number, enter: boolean, nextAnim: number): android.animation.Animator {
var animator = transitionModule._onFragmentCreateAnimator(this, nextAnim);
if (!animator) {
animator = this.super.onCreateAnimator(transit, enter, nextAnim);
}
trace.write(`${this.getTag() }.onCreateAnimator(${transit}, ${enter}, ${nextAnim}): ${animator}`, trace.categories.NativeLifecycle);
return animator;
}
});
}
function onFragmentShown(fragment) {
trace.write(`onFragmentShown(${fragment.toString()})`, trace.categories.NativeLifecycle);
trace.write(`SHOWN ${fragment.getTag()}`, trace.categories.NativeLifecycle);
if (fragment[CLEARING_HISTORY]) {
trace.write(`${fragment.toString() } has been shown, but we are currently clearing history. Returning.`, trace.categories.NativeLifecycle);
trace.write(`${fragment.getTag()} has been shown, but we are currently clearing history. Returning.`, trace.categories.NativeLifecycle);
return null;
}
@@ -99,7 +112,7 @@ function onFragmentShown(fragment) {
var page: pages.Page = entry.resolvedPage;
let currentNavigationContext;
let navigationQueue = (<any>frame)._navigationQueue;
let navigationQueue = frame._navigationQueue;
for (let i = 0; i < navigationQueue.length; i++) {
if (navigationQueue[i].entry === entry) {
currentNavigationContext = navigationQueue[i];
@@ -108,34 +121,33 @@ function onFragmentShown(fragment) {
}
var isBack = currentNavigationContext ? currentNavigationContext.isBackNavigation : false;
frame._currentEntry = entry;
frame._addView(page);
// onFragmentShown is called before NativeActivity.start where we call frame.onLoaded
// We need to call frame.onLoaded() here so that the call to frame._addView(page) will emit the page.loaded event
// before the page.navigatedTo event making the two platforms identical.
if (!frame.isLoaded) {
frame._currentEntry = entry;
frame.onLoaded();
}
page.onNavigatedTo(isBack);
frame._processNavigationQueue(page);
// Handle page transitions.
transitionModule._onFragmentShown(fragment, isBack);
}
function onFragmentHidden(fragment) {
trace.write(`onFragmentHidden(${fragment.toString()})`, trace.categories.NativeLifecycle);
trace.write(`HIDDEN ${fragment.getTag()}`, trace.categories.NativeLifecycle);
if (fragment[CLEARING_HISTORY]) {
trace.write(`${fragment.toString() } has been hidden, but we are currently clearing history. Returning.`, trace.categories.NativeLifecycle);
trace.write(`${fragment.getTag()} has been hidden, but we are currently clearing history. Returning.`, trace.categories.NativeLifecycle);
return null;
}
var entry: definition.BackstackEntry = fragment.entry;
var page: pages.Page = entry.resolvedPage;
// This might be a second call if the fragment is hidden and then destroyed.
if (page && page.frame) {
var frame = page.frame;
frame._removeView(page);
}
var isBack = fragment.entry[IS_BACK];
fragment.entry[IS_BACK] = undefined;
// Handle page transitions.
transitionModule._onFragmentHidden(fragment, isBack);
}
export class Frame extends frameCommon.Frame {
@@ -157,6 +169,13 @@ export class Frame extends frameCommon.Frame {
frameCommon.Frame.defaultAnimatedNavigation = value;
}
public static get defaultNavigationTransition(): definition.NavigationTransition {
return frameCommon.Frame.defaultNavigationTransition;
}
public static set defaultNavigationTransition(value: definition.NavigationTransition) {
frameCommon.Frame.defaultNavigationTransition = value;
}
get containerViewId(): number {
return this._containerViewId;
}
@@ -170,7 +189,7 @@ export class Frame extends frameCommon.Frame {
}
public _navigateCore(backstackEntry: definition.BackstackEntry) {
trace.write(`_navigateCore; id: ${backstackEntry.resolvedPage.id}; backstackVisible: ${this._isEntryBackstackVisible(backstackEntry)}; clearHistory: ${backstackEntry.entry.clearHistory};`, trace.categories.Navigation);
trace.write(`${this}._navigateCore(page: ${backstackEntry.resolvedPage}, backstackVisible: ${this._isEntryBackstackVisible(backstackEntry)}, clearHistory: ${backstackEntry.entry.clearHistory}), navDepth: ${navDepth}`, trace.categories.Navigation);
var activity = this._android.activity;
if (!activity) {
@@ -195,7 +214,7 @@ export class Frame extends frameCommon.Frame {
var fragment: android.app.Fragment;
while (i >= 0) {
fragment = manager.findFragmentByTag(manager.getBackStackEntryAt(i--).getName());
trace.write(`${fragment.toString()}[CLEARING_HISTORY] = true;`, trace.categories.NativeLifecycle);
trace.write(`${fragment.getTag()}[CLEARING_HISTORY] = true;`, trace.categories.NativeLifecycle);
fragment[CLEARING_HISTORY] = true;
}
@@ -204,7 +223,7 @@ export class Frame extends frameCommon.Frame {
fragment = manager.findFragmentByTag(this.currentPage[TAG]);
if (fragment) {
fragment[CLEARING_HISTORY] = true;
trace.write(`${fragment.toString() }[CLEARING_HISTORY] = true;`, trace.categories.NativeLifecycle);
trace.write(`${fragment.getTag()}[CLEARING_HISTORY] = true;`, trace.categories.NativeLifecycle);
}
}
@@ -221,10 +240,27 @@ export class Frame extends frameCommon.Frame {
var fragmentTransaction = manager.beginTransaction();
var currentFragmentTag: string;
var currentFragment: android.app.Fragment;
if (this.currentPage) {
currentFragmentTag = this.currentPage[TAG];
currentFragment = manager.findFragmentByTag(currentFragmentTag);
}
var newFragmentTag = "fragment" + navDepth;
ensureFragmentClass();
var newFragment = new FragmentClass();
var animated = this._getIsAnimatedNavigation(backstackEntry.entry);
var navigationTransition = this._getNavigationTransition(backstackEntry.entry);
if (currentFragment) {
// There might be transitions left over from previous forward navigations from the current page.
transitionModule._clearForwardTransitions(currentFragment);
}
if (animated && navigationTransition) {
transitionModule._setAndroidFragmentTransitions(navigationTransition, currentFragment, newFragment, fragmentTransaction);
}
newFragment.frame = this;
newFragment.entry = backstackEntry;
@@ -233,31 +269,27 @@ export class Frame extends frameCommon.Frame {
// remember the fragment tag at page level so that we can retrieve the fragment associated with a Page instance
backstackEntry.resolvedPage[TAG] = newFragmentTag;
trace.write("Frame<" + this._domId + ">.fragmentTransaction PUSH depth = " + navDepth, trace.categories.Navigation);
if (this._isFirstNavigation) {
fragmentTransaction.add(this.containerViewId, newFragment, newFragmentTag);
trace.write("fragmentTransaction.add(" + this.containerViewId + ", " + newFragment + ", " + newFragmentTag + ");", trace.categories.NativeLifecycle);
trace.write(`fragmentTransaction.add(${newFragmentTag});`, trace.categories.NativeLifecycle);
}
else {
if (this.android.cachePagesOnNavigate && !backstackEntry.entry.clearHistory) {
var currentFragmentTag = this.currentPage[TAG];
var currentFragment = manager.findFragmentByTag(currentFragmentTag);
if (currentFragment) {
fragmentTransaction.hide(currentFragment);
trace.write("fragmentTransaction.hide(" + currentFragment + ");", trace.categories.NativeLifecycle);
trace.write(`fragmentTransaction.hide(${currentFragmentTag});`, trace.categories.NativeLifecycle);
}
else {
trace.write("Could not find " + currentFragmentTag + " to hide", trace.categories.NativeLifecycle);
trace.write(`Could not find ${currentFragmentTag} to hide.`, trace.categories.NativeLifecycle);
}
fragmentTransaction.add(this.containerViewId, newFragment, newFragmentTag);
trace.write("fragmentTransaction.add(" + this.containerViewId + ", " + newFragment + ", " + newFragmentTag + ");", trace.categories.NativeLifecycle);
trace.write(`fragmentTransaction.add(${newFragmentTag});`, trace.categories.NativeLifecycle);
}
else {
fragmentTransaction.replace(this.containerViewId, newFragment, newFragmentTag);
trace.write("fragmentTransaction.replace(" + this.containerViewId + ", " + newFragment + ", " + newFragmentTag + ");", trace.categories.NativeLifecycle);
trace.write(`fragmentTransaction.replace(${newFragmentTag});`, trace.categories.NativeLifecycle);
}
// Add to backStack if needed.
@@ -265,13 +297,11 @@ export class Frame extends frameCommon.Frame {
// We add each entry in the backstack to avoid the "Stack corrupted" mismatch
var backstackTag = this._currentEntry[BACKSTACK_TAG];
fragmentTransaction.addToBackStack(backstackTag);
trace.write("fragmentTransaction.addToBackStack(" + backstackTag + ");", trace.categories.NativeLifecycle);
trace.write(`fragmentTransaction.addToBackStack(${backstackTag});`, trace.categories.NativeLifecycle);
}
}
if (!this._isFirstNavigation) {
var animated = this._getIsAnimatedNavigation(backstackEntry.entry);
if (this.android.cachePagesOnNavigate) {
// Apparently, there is an Android bug with when hiding fragments with animation.
// https://code.google.com/p/android/issues/detail?id=32405
@@ -279,18 +309,24 @@ export class Frame extends frameCommon.Frame {
fragmentTransaction.setTransition(android.app.FragmentTransaction.TRANSIT_NONE);
}
else {
var transition = animated ? android.app.FragmentTransaction.TRANSIT_FRAGMENT_OPEN : android.app.FragmentTransaction.TRANSIT_NONE;
fragmentTransaction.setTransition(transition);
var transit = animated ? android.app.FragmentTransaction.TRANSIT_FRAGMENT_OPEN : android.app.FragmentTransaction.TRANSIT_NONE;
fragmentTransaction.setTransition(transit);
}
}
fragmentTransaction.commit();
trace.write("fragmentTransaction.commit();", trace.categories.NativeLifecycle);
trace.write(`fragmentTransaction.commit();`, trace.categories.NativeLifecycle);
}
public _goBackCore(backstackEntry: definition.BackstackEntry) {
navDepth = backstackEntry[NAV_DEPTH];
trace.write("Frame<" + this._domId + ">.fragmentTransaction POP depth = " + navDepth, trace.categories.Navigation);
if (this._currentEntry) {
// We need this information inside onFragmentHidden
this._currentEntry[IS_BACK] = true;
}
trace.write(`${this}._goBackCore(pageId: ${backstackEntry.resolvedPage.id}, backstackVisible: ${this._isEntryBackstackVisible(backstackEntry) }, clearHistory: ${backstackEntry.entry.clearHistory}), navDepth: ${navDepth}`, trace.categories.Navigation);
var manager = this._android.activity.getFragmentManager();
if (manager.getBackStackEntryCount() > 0) {
@@ -350,7 +386,7 @@ export class Frame extends frameCommon.Frame {
console.log("Fragment Manager Back Stack (" + length + ")");
while (i >= 0) {
var fragment = <any>manager.findFragmentByTag(manager.getBackStackEntryAt(i--).getName());
console.log("[ " + fragment.toString() + " ]");
console.log("[ " + fragment.getTag() + " ]");
}
}
@@ -392,7 +428,7 @@ var NativeActivity = {
},
onCreate: function (savedInstanceState: android.os.Bundle) {
trace.write("NativeScriptActivity.onCreate(); savedInstanceState: " + savedInstanceState, trace.categories.NativeLifecycle);
trace.write(`NativeActivity.onCreate(${savedInstanceState})`, trace.categories.NativeLifecycle);
// Find the frame for this activity.
var frameId = this.getIntent().getExtras().getInt(INTENT_EXTRA);
@@ -433,7 +469,7 @@ var NativeActivity = {
onActivityResult: function (requestCode: number, resultCode: number, data: android.content.Intent) {
this.super.onActivityResult(requestCode, resultCode, data);
trace.write("NativeScriptActivity.onActivityResult();", trace.categories.NativeLifecycle);
trace.write(`NativeActivity.onActivityResult(${requestCode}, ${resultCode}, ${data})`, trace.categories.NativeLifecycle);
var result = application.android.onActivityResult;
if (result) {
@@ -451,7 +487,7 @@ var NativeActivity = {
},
onAttachFragment: function (fragment: android.app.Fragment) {
trace.write("NativeScriptActivity.onAttachFragment() : " + fragment.getTag(), trace.categories.NativeLifecycle);
trace.write(`NativeActivity.onAttachFragment(${fragment.getTag()})`, trace.categories.NativeLifecycle);
this.super.onAttachFragment(fragment);
if (!(<any>fragment).entry) {
@@ -463,7 +499,7 @@ var NativeActivity = {
onStart: function () {
this.super.onStart();
trace.write("NativeScriptActivity.onStart();", trace.categories.NativeLifecycle);
trace.write("NativeActivity.onStart()", trace.categories.NativeLifecycle);
if (!this.frame.isLoaded) {
this.frame.onLoaded();
}
@@ -471,11 +507,12 @@ var NativeActivity = {
onStop: function () {
this.super.onStop();
trace.write("NativeScriptActivity.onStop();", trace.categories.NativeLifecycle);
trace.write("NativeActivity.onStop()", trace.categories.NativeLifecycle);
this.frame.onUnloaded();
},
onDestroy: function () {
trace.write("NativeActivity.onDestroy()", trace.categories.NativeLifecycle);
// TODO: Implement uninitialized(detached) routine
var frame = this.frame;
frame._onDetached(true);
@@ -488,10 +525,10 @@ var NativeActivity = {
this.androidFrame.reset();
this.super.onDestroy();
trace.write("NativeScriptActivity.onDestroy();", trace.categories.NativeLifecycle);
},
onOptionsItemSelected: function (menuItem: android.view.IMenuItem) {
trace.write(`NativeActivity.onOptionsItemSelected(${menuItem})`, trace.categories.NativeLifecycle);
if (!this.androidFrame.hasListeners(frameCommon.Frame.androidOptionSelectedEvent)) {
return false;
}
@@ -508,7 +545,7 @@ var NativeActivity = {
},
onBackPressed: function () {
trace.write("NativeScriptActivity.onBackPressed;", trace.categories.NativeLifecycle);
trace.write("NativeActivity.onBackPressed()", trace.categories.NativeLifecycle);
var args = <application.AndroidActivityBackPressedEventData>{
eventName: "activityBackPressed",
@@ -528,6 +565,7 @@ var NativeActivity = {
},
onLowMemory: function () {
trace.write("NativeActivity.onLowMemory()", trace.categories.NativeLifecycle);
gc();
java.lang.System.gc();
this.super.onLowMemory();
@@ -536,6 +574,7 @@ var NativeActivity = {
},
onTrimMemory: function (level: number) {
trace.write(`NativeActivity.onTrimMemory(${level})`, trace.categories.NativeLifecycle);
gc();
java.lang.System.gc();
this.super.onTrimMemory(level);
@@ -688,26 +727,31 @@ function findPageForFragment(fragment: android.app.Fragment, frame: Frame) {
var page: pages.Page;
var entry: definition.BackstackEntry;
trace.write("Attached fragment with no page: " + fragmentTag, trace.categories.NativeLifecycle);
trace.write(`Finding page for ${fragmentTag}.`, trace.categories.NativeLifecycle);
if (fragmentTag === (<any>pages).DIALOG_FRAGMENT_TAG) {
trace.write(`No need to find page for dialog fragment.`, trace.categories.NativeLifecycle);
return;
}
if (frame.currentPage && frame.currentPage[TAG] === fragmentTag) {
page = frame.currentPage;
entry = frame._currentEntry;
trace.write("Current page matches fragment: " + fragmentTag, trace.categories.NativeLifecycle);
trace.write(`Current page matches fragment ${fragmentTag}.`, trace.categories.NativeLifecycle);
}
else {
var backStack = frame.backStack;
for (var i = 0; i < backStack.length; i++) {
entry = backStack[i];
if (backStack[i].resolvedPage[TAG] === fragmentTag) {
entry = backStack[i];
break;
}
}
if (entry) {
trace.write("Found entry:" + entry + " for fragment: " + fragmentTag, trace.categories.NativeLifecycle);
page = entry.resolvedPage;
trace.write(`Found ${page} for ${fragmentTag}`, trace.categories.NativeLifecycle);
}
}
if (page) {
(<any>fragment).frame = frame;
(<any>fragment).entry = entry;
@@ -715,7 +759,7 @@ function findPageForFragment(fragment: android.app.Fragment, frame: Frame) {
page[TAG] = fragmentTag;
}
else {
//throw new Error("Could not find Page for Fragment.");
//throw new Error(`Could not find a page for ${fragmentTag}.`);
}
}

36
ui/frame/frame.d.ts vendored
View File

@@ -73,11 +73,21 @@ declare module "ui/frame" {
*/
animated: boolean;
/**
* Gets or sets the default navigation transition for this frame.
*/
navigationTransition: NavigationTransition;
/**
* Gets or sets if navigation transitions should be animated globally.
*/
static defaultAnimatedNavigation: boolean;
/**
* Gets or sets the default NavigationTransition for all frames across the app.
*/
static defaultNavigationTransition: NavigationTransition;
/**
* Gets the AndroidFrame object that represents the Android-specific APIs for this Frame. Valid when running on Android OS.
*/
@@ -149,6 +159,11 @@ declare module "ui/frame" {
*/
animated?: boolean;
/**
* Specifies an optional navigation transition. If not specified, the default platform transition will be used.
*/
navigationTransition?: NavigationTransition;
/**
* True to record the navigation in the backstack, false otherwise.
* If the parameter is set to false then the Page will be displayed but once navigated from it will not be able to be navigated back to.
@@ -161,6 +176,27 @@ declare module "ui/frame" {
clearHistory?: boolean;
}
/**
* Represents an object specifying a page navigation transition.
*/
export interface NavigationTransition {
/**
* Either a string specifying one of the built-in transitions or an user-defined instance of the "ui/transition".Transition class.
*/
transition: any;
/**
* The length of the transition in milliseconds. If you do not specify this, the default platform transition duration will be used.
*/
duration?: number;
/**
* An optional transition animation curve. Possible values are contained in the [AnimationCurve enumeration](../enums/AnimationCurve/README.md).
* Alternatively, you can pass an instance of type UIViewAnimationCurve for iOS or android.animation.TimeInterpolator for Android.
*/
curve?: any;
}
/**
* Represents an entry in the back stack of a Frame object.
*/

View File

@@ -7,12 +7,14 @@ import utils = require("utils/utils");
import view = require("ui/core/view");
import uiUtils = require("ui/utils");
import * as types from "utils/types";
import * as animationModule from "ui/animation";
import * as transitionModule from "ui/transition";
global.moduleMerge(frameCommon, exports);
var ENTRY = "_entry";
var NAV_DEPTH = "_navDepth";
var TRANSITION = "_transition";
var navDepth = -1;
export class Frame extends frameCommon.Frame {
@@ -50,6 +52,7 @@ export class Frame extends frameCommon.Frame {
}
public _navigateCore(backstackEntry: definition.BackstackEntry) {
trace.write(`${this}._navigateCore(pageId: ${backstackEntry.resolvedPage.id}, backstackVisible: ${this._isEntryBackstackVisible(backstackEntry) }, clearHistory: ${backstackEntry.entry.clearHistory}), navDepth: ${navDepth}`, trace.categories.Navigation);
var viewController: UIViewController = backstackEntry.resolvedPage.ios;
if (!viewController) {
throw new Error("Required page does not have a viewController created.");
@@ -57,9 +60,10 @@ export class Frame extends frameCommon.Frame {
navDepth++;
var animated = false;
if (this.currentPage) {
animated = this._getIsAnimatedNavigation(backstackEntry.entry);
var animated = this.currentPage ? this._getIsAnimatedNavigation(backstackEntry.entry) : false;
var navigationTransition = this._getNavigationTransition(backstackEntry.entry);
if (animated && navigationTransition) {
viewController[TRANSITION] = navigationTransition;
}
backstackEntry[NAV_DEPTH] = navDepth;
@@ -70,7 +74,7 @@ export class Frame extends frameCommon.Frame {
// First navigation.
if (!this._currentEntry) {
this._ios.controller.pushViewControllerAnimated(viewController, animated);
trace.write("Frame<" + this._domId + ">.pushViewControllerAnimated(newController) depth = " + navDepth, trace.categories.Navigation);
trace.write(`${this}.pushViewControllerAnimated(${viewController}, ${animated}); depth = ${navDepth}`, trace.categories.Navigation);
return;
}
@@ -80,7 +84,7 @@ export class Frame extends frameCommon.Frame {
var newControllers = NSMutableArray.alloc().initWithCapacity(1);
newControllers.addObject(viewController);
this._ios.controller.setViewControllersAnimated(newControllers, animated);
trace.write("Frame<" + this._domId + ">.setViewControllersAnimated([newController]) depth = " + navDepth, trace.categories.Navigation);
trace.write(`${this}.setViewControllersAnimated([${viewController}], ${animated}); depth = ${navDepth}`, trace.categories.Navigation);
return;
}
@@ -102,24 +106,25 @@ export class Frame extends frameCommon.Frame {
// replace the controllers instead of pushing directly
this._ios.controller.setViewControllersAnimated(newControllers, animated);
trace.write("Frame<" + this._domId + ">.setViewControllersAnimated([originalControllers - lastController + newController]) depth = " + navDepth, trace.categories.Navigation);
trace.write(`${this}.setViewControllersAnimated([originalControllers - lastController + ${viewController}], ${animated}); depth = ${navDepth}`, trace.categories.Navigation);
return;
}
// General case.
this._ios.controller.pushViewControllerAnimated(viewController, animated);
trace.write("Frame<" + this._domId + ">.pushViewControllerAnimated(newController) depth = " + navDepth, trace.categories.Navigation);
trace.write(`${this}.pushViewControllerAnimated(${viewController}, ${animated}); depth = ${navDepth}`, trace.categories.Navigation);
}
public _goBackCore(backstackEntry: definition.BackstackEntry) {
navDepth = backstackEntry[NAV_DEPTH];
trace.write("Frame<" + this._domId + ">.popViewControllerAnimated depth = " + navDepth, trace.categories.Navigation);
trace.write(`${this}._goBackCore(pageId: ${backstackEntry.resolvedPage.id}, backstackVisible: ${this._isEntryBackstackVisible(backstackEntry) }, clearHistory: ${backstackEntry.entry.clearHistory}), navDepth: ${navDepth}`, trace.categories.Navigation);
if (!this._shouldSkipNativePop) {
var controller = backstackEntry.resolvedPage.ios;
var animated = this._getIsAnimatedNavigation(backstackEntry.entry);
var animated = this._currentEntry ? this._getIsAnimatedNavigation(this._currentEntry.entry) : false;
this._updateActionBar(backstackEntry.resolvedPage);
trace.write(`${this}.popToViewControllerAnimated(${controller}, ${animated}); depth = ${navDepth}`, trace.categories.Navigation);
this._ios.controller.popToViewControllerAnimated(controller, animated);
}
}
@@ -174,6 +179,13 @@ export class Frame extends frameCommon.Frame {
frameCommon.Frame.defaultAnimatedNavigation = value;
}
public static get defaultNavigationTransition(): definition.NavigationTransition {
return frameCommon.Frame.defaultNavigationTransition;
}
public static set defaultNavigationTransition(value: definition.NavigationTransition) {
frameCommon.Frame.defaultNavigationTransition = value;
}
public requestLayout(): void {
super.requestLayout();
// Invalidate our Window so that layout is triggered again.
@@ -265,14 +277,58 @@ export class Frame extends frameCommon.Frame {
}
}
class TransitionDelegate extends NSObject {
static new(): TransitionDelegate {
return <TransitionDelegate>super.new();
}
private _owner: UINavigationControllerImpl;
private _id: string;
public initWithOwnerId(owner: UINavigationControllerImpl, id: string): TransitionDelegate {
this._owner = owner;
this._owner.transitionDelegates.push(this);
this._id = id;
return this;
}
public animationWillStart(animationID: string, context: any): void {
trace.write(`START ${this._id}`, trace.categories.Transition);
}
public animationDidStop(animationID: string, finished: boolean, context: any): void {
if (finished) {
trace.write(`END ${this._id}`, trace.categories.Transition);
}
else {
trace.write(`CANCEL ${this._id}`, trace.categories.Transition);
}
if (this._owner) {
var index = this._owner.transitionDelegates.indexOf(this);
if (index > -1) {
this._owner.transitionDelegates.splice(index, 1);
}
}
}
public static ObjCExposedMethods = {
"animationWillStart": { returns: interop.types.void, params: [NSString, NSObject] },
"animationDidStop": { returns: interop.types.void, params: [NSString, NSNumber, NSObject] }
};
}
var _defaultTransitionDuration = 0.35;
class UINavigationControllerImpl extends UINavigationController implements UINavigationControllerDelegate {
public static ObjCProtocols = [UINavigationControllerDelegate];
private _owner: WeakRef<Frame>;
private _transitionDelegates: Array<TransitionDelegate>;
public static initWithOwner(owner: WeakRef<Frame>): UINavigationControllerImpl {
var controller = <UINavigationControllerImpl>UINavigationControllerImpl.new();
controller._owner = owner;
controller._transitionDelegates = new Array<TransitionDelegate>();
return controller;
}
@@ -280,6 +336,10 @@ class UINavigationControllerImpl extends UINavigationController implements UINav
return this._owner.get();
}
get transitionDelegates(): Array<TransitionDelegate> {
return this._transitionDelegates;
}
public viewDidLoad(): void {
let owner = this._owner.get();
if (owner) {
@@ -332,12 +392,6 @@ class UINavigationControllerImpl extends UINavigationController implements UINav
let newEntry: definition.BackstackEntry = viewController[ENTRY];
let newPage = newEntry.resolvedPage;
// For some reason iOS calls navigationControllerDidShowViewControllerAnimated twice for the
// main-page resulting in double 'loaded' and 'navigatedTo' events being fired.
if (!(<any>newPage)._delayLoadedEvent) {
return;
}
let backStack = frame.backStack;
let currentEntry = backStack.length > 0 ? backStack[backStack.length - 1] : null;
@@ -374,14 +428,6 @@ class UINavigationControllerImpl extends UINavigationController implements UINav
frame._navigateToEntry = null;
frame._currentEntry = newEntry;
frame.remeasureFrame();
// In iOS we intentionally delay the raising of the 'loaded' event so both platforms behave identically.
// The loaded event must be raised AFTER the page is part of the windows hierarchy and
// frame.topmost().currentPage is set to the page instance.
// https://github.com/NativeScript/NativeScript/issues/779
(<any>newPage)._delayLoadedEvent = false;
newPage._emit(view.View.loadedEvent);
frame._updateActionBar(newPage);
// notify the page
@@ -392,6 +438,187 @@ class UINavigationControllerImpl extends UINavigationController implements UINav
public supportedInterfaceOrientation(): number {
return UIInterfaceOrientationMask.UIInterfaceOrientationMaskAll;
}
public pushViewControllerAnimated(viewController: UIViewController, animated: boolean): void {
var navigationTransition = <definition.NavigationTransition>viewController[TRANSITION];
trace.write(`UINavigationControllerImpl.pushViewControllerAnimated(${viewController}, ${animated}); transition: ${JSON.stringify(navigationTransition)}`, trace.categories.NativeLifecycle);
if (!animated || !navigationTransition) {
super.pushViewControllerAnimated(viewController, animated);
return;
}
var nativeTransition = _getNativeTransition(navigationTransition, true);
if (!nativeTransition) {
super.pushViewControllerAnimated(viewController, animated);
return;
}
var duration = navigationTransition.duration ? navigationTransition.duration / 1000 : _defaultTransitionDuration;
var curve = _getNativeCurve(navigationTransition);
var id = _getTransitionId(nativeTransition, "push");
var transitionDelegate = TransitionDelegate.new().initWithOwnerId(this, id);
UIView.animateWithDurationAnimations(duration, () => {
UIView.setAnimationDelegate(transitionDelegate);
UIView.setAnimationWillStartSelector("animationWillStart");
UIView.setAnimationDidStopSelector("animationDidStop");
UIView.setAnimationCurve(curve);
super.pushViewControllerAnimated(viewController, false);
UIView.setAnimationTransitionForViewCache(nativeTransition, this.view, true);
});
}
public setViewControllersAnimated(viewControllers: NSArray, animated: boolean): void {
var viewController = viewControllers.lastObject;
var navigationTransition = <definition.NavigationTransition>viewController[TRANSITION];
trace.write(`UINavigationControllerImpl.setViewControllersAnimated(${viewControllers}, ${animated}); transition: ${JSON.stringify(navigationTransition)}`, trace.categories.NativeLifecycle);
if (!animated || !navigationTransition) {
super.setViewControllersAnimated(viewControllers, animated);
return;
}
var nativeTransition = _getNativeTransition(navigationTransition, true);
if (!nativeTransition) {
super.setViewControllersAnimated(viewControllers, animated);
return;
}
var duration = navigationTransition.duration ? navigationTransition.duration / 1000 : _defaultTransitionDuration;
var curve = _getNativeCurve(navigationTransition);
var id = _getTransitionId(nativeTransition, "set");
var transitionDelegate = TransitionDelegate.new().initWithOwnerId(this, id);
UIView.animateWithDurationAnimations(duration, () => {
UIView.setAnimationDelegate(transitionDelegate);
UIView.setAnimationWillStartSelector("animationWillStart");
UIView.setAnimationDidStopSelector("animationDidStop");
UIView.setAnimationCurve(curve);
super.setViewControllersAnimated(viewControllers, false);
UIView.setAnimationTransitionForViewCache(nativeTransition, this.view, true);
});
}
public popViewControllerAnimated(animated: boolean): UIViewController {
var lastViewController = this.viewControllers.lastObject;
var navigationTransition = <definition.NavigationTransition>lastViewController[TRANSITION];
trace.write(`UINavigationControllerImpl.popViewControllerAnimated(${animated}); transition: ${JSON.stringify(navigationTransition)}`, trace.categories.NativeLifecycle);
if (!animated || !navigationTransition) {
return super.popViewControllerAnimated(animated);
}
var nativeTransition = _getNativeTransition(navigationTransition, false);
if (!nativeTransition) {
return super.popViewControllerAnimated(animated);
}
var duration = navigationTransition.duration ? navigationTransition.duration / 1000 : _defaultTransitionDuration;
var curve = _getNativeCurve(navigationTransition);
var id = _getTransitionId(nativeTransition, "pop");
var transitionDelegate = TransitionDelegate.new().initWithOwnerId(this, id);
UIView.animateWithDurationAnimations(duration, () => {
UIView.setAnimationDelegate(transitionDelegate);
UIView.setAnimationWillStartSelector("animationWillStart");
UIView.setAnimationDidStopSelector("animationDidStop");
UIView.setAnimationCurve(curve);
super.popViewControllerAnimated(false);
UIView.setAnimationTransitionForViewCache(nativeTransition, this.view, true);
});
return null;
}
public popToViewControllerAnimated(viewController: UIViewController, animated: boolean): NSArray {
var lastViewController = this.viewControllers.lastObject;
var navigationTransition = <definition.NavigationTransition>lastViewController[TRANSITION];
trace.write(`UINavigationControllerImpl.popToViewControllerAnimated(${viewController}, ${animated}); transition: ${JSON.stringify(navigationTransition)}`, trace.categories.NativeLifecycle);
if (!animated || !navigationTransition) {
return super.popToViewControllerAnimated(viewController, animated);
}
var nativeTransition = _getNativeTransition(navigationTransition, false);
if (!nativeTransition) {
return super.popToViewControllerAnimated(viewController, animated);
}
var duration = navigationTransition.duration ? navigationTransition.duration / 1000 : _defaultTransitionDuration;
var curve = _getNativeCurve(navigationTransition);
var id = _getTransitionId(nativeTransition, "popTo");
var transitionDelegate = TransitionDelegate.new().initWithOwnerId(this, id);
UIView.animateWithDurationAnimations(duration, () => {
UIView.setAnimationDelegate(transitionDelegate);
UIView.setAnimationWillStartSelector("animationWillStart");
UIView.setAnimationDidStopSelector("animationDidStop");
UIView.setAnimationCurve(curve);
super.popToViewControllerAnimated(viewController, false);
UIView.setAnimationTransitionForViewCache(nativeTransition, this.view, true);
});
return null;
}
public navigationControllerAnimationControllerForOperationFromViewControllerToViewController(navigationController: UINavigationController, operation: number, fromVC: UIViewController, toVC: UIViewController): UIViewControllerAnimatedTransitioning {
var viewController: UIViewController;
switch (operation) {
case UINavigationControllerOperation.UINavigationControllerOperationPush:
viewController = toVC;
break;
case UINavigationControllerOperation.UINavigationControllerOperationPop:
viewController = fromVC;
break;
}
if (!viewController) {
return null;
}
var navigationTransition = <definition.NavigationTransition>viewController[TRANSITION];
if (!navigationTransition) {
return null;
}
trace.write(`UINavigationControllerImpl.navigationControllerAnimationControllerForOperationFromViewControllerToViewController(${operation}, ${fromVC}, ${toVC}), transition: ${JSON.stringify(navigationTransition)}`, trace.categories.NativeLifecycle);
var _transitionModule: typeof transitionModule = require("ui/transition");
return _transitionModule._createIOSAnimatedTransitioning(navigationTransition, operation, fromVC, toVC);
}
}
function _getTransitionId(nativeTransition: UIViewAnimationTransition, transitionType: string): string {
var name;
switch (nativeTransition) {
case UIViewAnimationTransition.UIViewAnimationTransitionCurlDown: name = "CurlDown"; break;
case UIViewAnimationTransition.UIViewAnimationTransitionCurlUp: name = "CurlUp"; break;
case UIViewAnimationTransition.UIViewAnimationTransitionFlipFromLeft: name = "FlipFromLeft"; break;
case UIViewAnimationTransition.UIViewAnimationTransitionFlipFromRight: name = "FlipFromRight"; break;
case UIViewAnimationTransition.UIViewAnimationTransitionNone: name = "None"; break;
}
return `${name} ${transitionType}`;
}
function _getNativeTransition(navigationTransition: definition.NavigationTransition, push: boolean): UIViewAnimationTransition {
if (types.isString(navigationTransition.transition)) {
switch (navigationTransition.transition.toLowerCase()) {
case "flip":
case "flipright":
return push ? UIViewAnimationTransition.UIViewAnimationTransitionFlipFromRight : UIViewAnimationTransition.UIViewAnimationTransitionFlipFromLeft;
case "flipleft":
return push ? UIViewAnimationTransition.UIViewAnimationTransitionFlipFromLeft : UIViewAnimationTransition.UIViewAnimationTransitionFlipFromRight;
case "curl":
case "curlup":
return push ? UIViewAnimationTransition.UIViewAnimationTransitionCurlUp : UIViewAnimationTransition.UIViewAnimationTransitionCurlDown;
case "curldown":
return push ? UIViewAnimationTransition.UIViewAnimationTransitionCurlDown : UIViewAnimationTransition.UIViewAnimationTransitionCurlUp;
}
}
return null;
}
function _getNativeCurve(transition: definition.NavigationTransition) : UIViewAnimationCurve{
if (transition.curve) {
var animation: typeof animationModule = require("ui/animation");
return animation._resolveAnimationCurve(transition.curve);
}
return UIViewAnimationCurve.UIViewAnimationCurveEaseInOut;
}
/* tslint:disable */

View File

@@ -23,6 +23,8 @@ function ensureColor() {
}
}
export var DIALOG_FRAGMENT_TAG = "dialog";
var DialogFragmentClass;
function ensureDialogFragmentClass() {
if (DialogFragmentClass) {
@@ -120,7 +122,7 @@ export class Page extends pageCommon.Page {
if (skipDetached) {
ensureTrace();
// Do not detach the context and android reference.
trace.write("Caching Page " + this._domId, trace.categories.NativeLifecycle);
trace.write(`Caching ${this}`, trace.categories.NativeLifecycle);
}
else {
super._onDetached();
@@ -153,7 +155,7 @@ export class Page extends pageCommon.Page {
});
super._raiseShowingModallyEvent();
this._dialogFragment.show(parent.frame.android.activity.getFragmentManager(), "dialog");
this._dialogFragment.show(parent.frame.android.activity.getFragmentManager(), DIALOG_FRAGMENT_TAG);
super._raiseShownModallyEvent(parent, context, closeCallback);
}

View File

@@ -98,15 +98,6 @@ class UIViewControllerImpl extends UIViewController {
//https://github.com/NativeScript/NativeScript/issues/1201
owner._viewWillDisappear = false;
// In iOS we intentionally delay the raising of the 'loaded' event so both platforms behave identically.
// The loaded event must be raised AFTER the page is part of the windows hierarchy and
// frame.topmost().currentPage is set to the page instance.
// https://github.com/NativeScript/NativeScript/issues/779
if (!owner._isModal) {
owner._delayLoadedEvent = true;
}
owner.onLoaded();
owner._enableLoadedEvents = false;
}
@@ -145,7 +136,6 @@ export class Page extends pageCommon.Page {
public _enableLoadedEvents: boolean;
public _isModal: boolean;
public _UIModalPresentationFormSheet: boolean;
public _delayLoadedEvent: boolean;
public _viewWillDisappear: boolean;
constructor(options?: definition.Options) {
@@ -174,18 +164,6 @@ export class Page extends pageCommon.Page {
this._updateActionBar(false);
}
public notify<T extends observable.EventData>(data: T) {
// In iOS we intentionally delay the raising of the 'loaded' event so both platforms behave identically.
// The loaded event must be raised AFTER the page is part of the windows hierarchy and
// frame.topmost().currentPage is set to the page instance.
// https://github.com/NativeScript/NativeScript/issues/779
if (data.eventName === View.loadedEvent && this._delayLoadedEvent) {
return;
}
super.notify(data);
}
public onUnloaded() {
// loaded/unloaded events are handled in page viewWillAppear/viewDidDisappear
if (this._enableLoadedEvents) {

View File

@@ -0,0 +1,29 @@
import transition = require("ui/transition");
import platform = require("platform");
var floatType = java.lang.Float.class.getField("TYPE").get(null);
export class FadeTransition extends transition.Transition {
public createAndroidAnimator(transitionType: string): android.animation.Animator {
var alphaValues = java.lang.reflect.Array.newInstance(floatType, 2);
switch (transitionType) {
case transition.AndroidTransitionType.enter:
case transition.AndroidTransitionType.popEnter:
alphaValues[0] = 0;
alphaValues[1] = 1;
break;
case transition.AndroidTransitionType.exit:
case transition.AndroidTransitionType.popExit:
alphaValues[0] = 1;
alphaValues[1] = 0;
break;
}
var animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", alphaValues);
var duration = this.getDuration();
if (duration !== undefined) {
animator.setDuration(duration);
}
animator.setInterpolator(this.getCurve());
return animator;
}
}

View File

@@ -0,0 +1,25 @@
import transition = require("ui/transition");
export class FadeTransition extends transition.Transition {
public animateIOSTransition(containerView: UIView, fromView: UIView, toView: UIView, operation: UINavigationControllerOperation, completion: (finished: boolean) => void): void {
toView.alpha = 0.0;
fromView.alpha = 1.0;
switch (operation) {
case UINavigationControllerOperation.UINavigationControllerOperationPush:
containerView.insertSubviewAboveSubview(toView, fromView);
break;
case UINavigationControllerOperation.UINavigationControllerOperationPop:
containerView.insertSubviewBelowSubview(toView, fromView);
break;
}
var duration = this.getDuration();
var curve = this.getCurve();
UIView.animateWithDurationAnimationsCompletion(duration, () => {
UIView.setAnimationCurve(curve);
toView.alpha = 1.0;
fromView.alpha = 0.0;
}, completion);
}
}

View File

@@ -0,0 +1,120 @@
import transition = require("ui/transition");
import platform = require("platform");
var floatType = java.lang.Float.class.getField("TYPE").get(null);
//http://developer.android.com/training/animation/cardflip.html
export class FlipTransition extends transition.Transition {
private _direction: string;
constructor(direction: string, duration: number, curve: any) {
super(duration, curve);
this._direction = direction;
}
public createAndroidAnimator(transitionType: string): android.animation.Animator {
var objectAnimators;
var values;
var animator: android.animation.ObjectAnimator;
var animatorSet = new android.animation.AnimatorSet();
var fullDuration = this.getDuration() || 300;
var interpolator = this.getCurve();
var rotationY = this._direction === "right" ? 180 : -180;
switch (transitionType) {
case transition.AndroidTransitionType.enter: // card_flip_right_in
objectAnimators = java.lang.reflect.Array.newInstance(android.animation.Animator.class, 3);
values = java.lang.reflect.Array.newInstance(floatType, 2);
values[0] = 1.0;
values[1] = 0.0;
animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
animator.setDuration(0);
objectAnimators[0] = animator;
values = java.lang.reflect.Array.newInstance(floatType, 2);
values[0] = rotationY;
values[1] = 0.0;
animator = android.animation.ObjectAnimator.ofFloat(null, "rotationY", values);
animator.setInterpolator(interpolator);
animator.setDuration(fullDuration);
objectAnimators[1] = animator;
values = java.lang.reflect.Array.newInstance(floatType, 2);
values[0] = 0.0;
values[1] = 1.0;
animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
animator.setStartDelay(fullDuration / 2);
animator.setDuration(1);
objectAnimators[2] = animator;
break;
case transition.AndroidTransitionType.exit: // card_flip_right_out
objectAnimators = java.lang.reflect.Array.newInstance(android.animation.Animator.class, 2);
values = java.lang.reflect.Array.newInstance(floatType, 2);
values[0] = 0.0;
values[1] = -rotationY;
animator = android.animation.ObjectAnimator.ofFloat(null, "rotationY", values);
animator.setInterpolator(interpolator);
animator.setDuration(fullDuration);
objectAnimators[0] = animator;
values = java.lang.reflect.Array.newInstance(floatType, 2);
values[0] = 1.0;
values[1] = 0.0;
animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
animator.setStartDelay(fullDuration / 2);
animator.setDuration(1);
objectAnimators[1] = animator;
break;
case transition.AndroidTransitionType.popEnter: // card_flip_left_in
objectAnimators = java.lang.reflect.Array.newInstance(android.animation.Animator.class, 3);
values = java.lang.reflect.Array.newInstance(floatType, 2);
values[0] = 1.0;
values[1] = 0.0;
animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
animator.setDuration(0);
objectAnimators[0] = animator;
values = java.lang.reflect.Array.newInstance(floatType, 2);
values[0] = -rotationY;
values[1] = 0.0;
animator = android.animation.ObjectAnimator.ofFloat(null, "rotationY", values);
animator.setInterpolator(interpolator);
animator.setDuration(fullDuration);
objectAnimators[1] = animator;
values = java.lang.reflect.Array.newInstance(floatType, 2);
values[0] = 0.0;
values[1] = 1.0;
animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
animator.setStartDelay(fullDuration / 2);
animator.setDuration(1);
objectAnimators[2] = animator;
break;
case transition.AndroidTransitionType.popExit: // card_flip_left_out
objectAnimators = java.lang.reflect.Array.newInstance(android.animation.Animator.class, 2);
values = java.lang.reflect.Array.newInstance(floatType, 2);
values[0] = 0.0;
values[1] = rotationY;
animator = android.animation.ObjectAnimator.ofFloat(null, "rotationY", values);
animator.setInterpolator(interpolator);
animator.setDuration(fullDuration);
objectAnimators[0] = animator;
values = java.lang.reflect.Array.newInstance(floatType, 2);
values[0] = 1.0;
values[1] = 0.0;
animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
animator.setStartDelay(fullDuration / 2);
animator.setDuration(1);
objectAnimators[1] = animator;
break;
}
animatorSet.playTogether(objectAnimators);
return animatorSet;
}
}

View File

@@ -0,0 +1,2 @@
{ "name" : "transition",
"main" : "transition.js" }

View File

@@ -0,0 +1,116 @@
import transition = require("ui/transition");
import platform = require("platform");
var floatType = java.lang.Float.class.getField("TYPE").get(null);
var screenWidth = platform.screen.mainScreen.widthPixels;
var screenHeight = platform.screen.mainScreen.heightPixels;
export class SlideTransition extends transition.Transition {
private _direction: string;
constructor(direction: string, duration: number, curve: any) {
super(duration, curve);
this._direction = direction;
}
public createAndroidAnimator(transitionType: string): android.animation.Animator {
var translationValues = java.lang.reflect.Array.newInstance(floatType, 2);
switch (this._direction) {
case "left":
switch (transitionType) {
case transition.AndroidTransitionType.enter:
translationValues[0] = screenWidth;
translationValues[1] = 0;
break;
case transition.AndroidTransitionType.exit:
translationValues[0] = 0;
translationValues[1] = -screenWidth;
break;
case transition.AndroidTransitionType.popEnter:
translationValues[0] = -screenWidth;
translationValues[1] = 0;
break;
case transition.AndroidTransitionType.popExit:
translationValues[0] = 0;
translationValues[1] = screenWidth;
break;
}
break;
case "right":
switch (transitionType) {
case transition.AndroidTransitionType.enter:
translationValues[0] = -screenWidth;
translationValues[1] = 0;
break;
case transition.AndroidTransitionType.exit:
translationValues[0] = 0;
translationValues[1] = screenWidth;
break;
case transition.AndroidTransitionType.popEnter:
translationValues[0] = screenWidth;
translationValues[1] = 0;
break;
case transition.AndroidTransitionType.popExit:
translationValues[0] = 0;
translationValues[1] = -screenWidth;
break;
}
break;
case "top":
switch (transitionType) {
case transition.AndroidTransitionType.enter:
translationValues[0] = screenHeight;
translationValues[1] = 0;
break;
case transition.AndroidTransitionType.exit:
translationValues[0] = 0;
translationValues[1] = -screenHeight;
break;
case transition.AndroidTransitionType.popEnter:
translationValues[0] = -screenHeight;
translationValues[1] = 0;
break;
case transition.AndroidTransitionType.popExit:
translationValues[0] = 0;
translationValues[1] = screenHeight;
break;
}
break;
case "bottom":
switch (transitionType) {
case transition.AndroidTransitionType.enter:
translationValues[0] = -screenHeight;
translationValues[1] = 0;
break;
case transition.AndroidTransitionType.exit:
translationValues[0] = 0;
translationValues[1] = screenHeight;
break;
case transition.AndroidTransitionType.popEnter:
translationValues[0] = screenHeight;
translationValues[1] = 0;
break;
case transition.AndroidTransitionType.popExit:
translationValues[0] = 0;
translationValues[1] = -screenHeight;
break;
}
break;
}
var prop;
if (this._direction === "left" || this._direction === "right") {
prop = "translationX";
}
else {
prop = "translationY";
}
var animator = android.animation.ObjectAnimator.ofFloat(null, prop, translationValues);
var duration = this.getDuration();
if (duration !== undefined) {
animator.setDuration(duration);
}
animator.setInterpolator(this.getCurve());
return animator;
}
}

View File

@@ -0,0 +1,64 @@
import transition = require("ui/transition");
import platform = require("platform");
var screenWidth = platform.screen.mainScreen.widthDIPs;
var screenHeight = platform.screen.mainScreen.heightDIPs;
var leftEdge = CGAffineTransformMakeTranslation(-screenWidth, 0);
var rightEdge = CGAffineTransformMakeTranslation(screenWidth, 0);
var topEdge = CGAffineTransformMakeTranslation(0, -screenHeight);
var bottomEdge = CGAffineTransformMakeTranslation(0, screenHeight);
export class SlideTransition extends transition.Transition {
private _direction: string;
constructor(direction: string, duration: number, curve: any) {
super(duration, curve);
this._direction = direction;
}
public animateIOSTransition(containerView: UIView, fromView: UIView, toView: UIView, operation: UINavigationControllerOperation, completion: (finished: boolean) => void): void {
var fromViewEndTransform: CGAffineTransform;
var toViewBeginTransform: CGAffineTransform;
var push = (operation === UINavigationControllerOperation.UINavigationControllerOperationPush);
switch (this._direction) {
case "left":
toViewBeginTransform = push ? rightEdge : leftEdge;
fromViewEndTransform = push ? leftEdge : rightEdge;
break;
case "right":
toViewBeginTransform = push ? leftEdge : rightEdge;
fromViewEndTransform = push ? rightEdge : leftEdge;
break;
case "top":
toViewBeginTransform = push ? bottomEdge : topEdge;
fromViewEndTransform = push ? topEdge : bottomEdge;
break;
case "bottom":
toViewBeginTransform = push ? topEdge : bottomEdge;
fromViewEndTransform = push ? bottomEdge : topEdge;
break;
}
var originalToViewTransform = toView.transform;
toView.transform = toViewBeginTransform;
//fromView.transform = CGAffineTransformIdentity;
switch (operation) {
case UINavigationControllerOperation.UINavigationControllerOperationPush:
containerView.insertSubviewAboveSubview(toView, fromView);
break;
case UINavigationControllerOperation.UINavigationControllerOperationPop:
containerView.insertSubviewBelowSubview(toView, fromView);
break;
}
var duration = this.getDuration();
var curve = this.getCurve();
UIView.animateWithDurationAnimationsCompletion(duration, () => {
UIView.setAnimationCurve(curve);
toView.transform = originalToViewTransform;
fromView.transform = fromViewEndTransform;
}, completion);
}
}

View File

@@ -0,0 +1,395 @@
import definition = require("ui/transition");
import platform = require("platform");
import frameModule = require("ui/frame");
import pageModule = require("ui/page");
import * as animationModule from "ui/animation";
import types = require("utils/types");
import trace = require("trace");
var _sdkVersion = parseInt(platform.device.sdkVersion);
var _defaultInterpolator = new android.view.animation.AccelerateDecelerateInterpolator();
var ENTER_POPEXIT_TRANSITION = "ENTER_POPEXIT_TRANSITION";
var EXIT_POPENTER_TRANSITION = "EXIT_POPENTER_TRANSITION";
var COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS = "COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS";
var COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS = "COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS";
var enterFakeResourceId = -10;
var exitFakeResourceId = -20;
var popEnterFakeResourceId = -30;
var popExitFakeResourceId = -40;
export module AndroidTransitionType {
export var enter: string = "enter";
export var exit: string = "exit";
export var popEnter: string = "popEnter";
export var popExit: string = "popExit";
}
export function _clearForwardTransitions(fragment: any): void {
if (fragment[EXIT_POPENTER_TRANSITION]) {
trace.write(`Cleared EXIT_POPENTER_TRANSITION ${fragment[EXIT_POPENTER_TRANSITION]} for ${fragment.getTag()}`, trace.categories.Transition);
fragment[EXIT_POPENTER_TRANSITION] = undefined;
}
if (_sdkVersion >= 21) {
var exitTransition = (<any>fragment).getExitTransition();
if (exitTransition) {
trace.write(`Cleared Exit ${exitTransition.getClass().getSimpleName()} transition for ${fragment.getTag()}`, trace.categories.Transition);
(<any>fragment).setExitTransition(null);//exit
}
var reenterTransition = (<any>fragment).getReenterTransition();
if (reenterTransition) {
trace.write(`Cleared Pop Enter ${reenterTransition.getClass().getSimpleName()} transition for ${fragment.getTag()}`, trace.categories.Transition);
(<any>fragment).setReenterTransition(null);//popEnter
}
}
}
export function _setAndroidFragmentTransitions(navigationTransition: frameModule.NavigationTransition, currentFragment: any, newFragment: any, fragmentTransaction: any): void {
var name;
if (types.isString(navigationTransition.transition)) {
name = navigationTransition.transition.toLowerCase();
}
var useLollipopTransition = name && (name.indexOf("slide") === 0 || name === "fade" || name === "explode") && _sdkVersion >= 21;
if (useLollipopTransition) {
// setEnterTransition: Enter
// setExitTransition: Exit
// setReenterTransition: Pop Enter, same as Exit if not specified
// setReturnTransition: Pop Exit, same as Enter if not specified
newFragment.setAllowEnterTransitionOverlap(true);
newFragment.setAllowReturnTransitionOverlap(true);
if (currentFragment) {
currentFragment.setAllowEnterTransitionOverlap(true);
currentFragment.setAllowReturnTransitionOverlap(true);
}
if (name.indexOf("slide") === 0) {
var direction = name.substr("slide".length) || "left"; //Extract the direction from the string
switch (direction) {
case "left":
let rightEdge = new (<any>android).transition.Slide((<any>android).view.Gravity.RIGHT);
_setUpNativeTransition(navigationTransition, rightEdge);
_addNativeTransitionListener(newFragment, rightEdge);
newFragment.setEnterTransition(rightEdge);
if (currentFragment) {
let leftEdge = new (<any>android).transition.Slide((<any>android).view.Gravity.LEFT);
_setUpNativeTransition(navigationTransition, leftEdge);
_addNativeTransitionListener(currentFragment, leftEdge);
currentFragment.setExitTransition(leftEdge);
}
break;
case "right":
let leftEdge = new (<any>android).transition.Slide((<any>android).view.Gravity.LEFT);
_setUpNativeTransition(navigationTransition, leftEdge);
_addNativeTransitionListener(newFragment, leftEdge);
newFragment.setEnterTransition(leftEdge);
if (currentFragment) {
let rightEdge = new (<any>android).transition.Slide((<any>android).view.Gravity.RIGHT);
_setUpNativeTransition(navigationTransition, rightEdge);
_addNativeTransitionListener(currentFragment, rightEdge);
currentFragment.setExitTransition(rightEdge);
}
break;
case "top":
let bottomEdge = new (<any>android).transition.Slide((<any>android).view.Gravity.BOTTOM);
_setUpNativeTransition(navigationTransition, bottomEdge);
_addNativeTransitionListener(newFragment, bottomEdge);
newFragment.setEnterTransition(bottomEdge);
if (currentFragment) {
let topEdge = new (<any>android).transition.Slide((<any>android).view.Gravity.TOP);
_setUpNativeTransition(navigationTransition, topEdge);
_addNativeTransitionListener(currentFragment, topEdge);
currentFragment.setExitTransition(topEdge);
}
break;
case "bottom":
let topEdge = new (<any>android).transition.Slide((<any>android).view.Gravity.TOP);
_setUpNativeTransition(navigationTransition, topEdge);
_addNativeTransitionListener(newFragment, topEdge);
newFragment.setEnterTransition(topEdge);
if (currentFragment) {
let bottomEdge = new (<any>android).transition.Slide((<any>android).view.Gravity.BOTTOM);
_setUpNativeTransition(navigationTransition, bottomEdge);
_addNativeTransitionListener(currentFragment, bottomEdge);
currentFragment.setExitTransition(bottomEdge);
}
break;
}
}
else if (name === "fade") {
let fadeEnter = new (<any>android).transition.Fade((<any>android).transition.Fade.IN);
_setUpNativeTransition(navigationTransition, fadeEnter);
_addNativeTransitionListener(newFragment, fadeEnter);
newFragment.setEnterTransition(fadeEnter);
let fadeReturn = new (<any>android).transition.Fade((<any>android).transition.Fade.OUT);
_setUpNativeTransition(navigationTransition, fadeReturn);
_addNativeTransitionListener(newFragment, fadeReturn);
newFragment.setReturnTransition(fadeReturn);
if (currentFragment) {
let fadeExit = new (<any>android).transition.Fade((<any>android).transition.Fade.OUT);
_setUpNativeTransition(navigationTransition, fadeExit);
_addNativeTransitionListener(currentFragment, fadeExit);
currentFragment.setExitTransition(fadeExit);
let fadeReenter = new (<any>android).transition.Fade((<any>android).transition.Fade.IN);
_setUpNativeTransition(navigationTransition, fadeReenter);
_addNativeTransitionListener(currentFragment, fadeReenter);
currentFragment.setReenterTransition(fadeReenter);
}
}
else if (name === "explode") {
let explodeEnter = new (<any>android).transition.Explode();
_setUpNativeTransition(navigationTransition, explodeEnter);
_addNativeTransitionListener(newFragment, explodeEnter);
newFragment.setEnterTransition(explodeEnter);
if (currentFragment) {
let explodeExit = new (<any>android).transition.Explode();
_setUpNativeTransition(navigationTransition, explodeExit);
_addNativeTransitionListener(currentFragment, explodeExit);
currentFragment.setExitTransition(explodeExit);
}
}
return;
}
var transition: Transition;
if (name) {
if (name.indexOf("slide") === 0) {
var slideTransitionModule = require("./slide-transition");
var direction = name.substr("slide".length) || "left"; //Extract the direction from the string
transition = new slideTransitionModule.SlideTransition(direction, navigationTransition.duration, navigationTransition.curve);
}
else if (name === "fade") {
var fadeTransitionModule = require("./fade-transition");
transition = new fadeTransitionModule.FadeTransition(navigationTransition.duration, navigationTransition.curve);
}
else if (name.indexOf("flip") === 0) {
var flipTransitionModule = require("./flip-transition");
var direction = name.substr("flip".length) || "right"; //Extract the direction from the string
transition = new flipTransitionModule.FlipTransition(direction, navigationTransition.duration, navigationTransition.curve);
}
}
else {
transition = navigationTransition.transition; // User-defined instance of Transition
}
if (transition) {
newFragment[ENTER_POPEXIT_TRANSITION] = transition;
if (currentFragment) {
currentFragment[EXIT_POPENTER_TRANSITION] = transition;
}
fragmentTransaction.setCustomAnimations(enterFakeResourceId, exitFakeResourceId, popEnterFakeResourceId, popExitFakeResourceId);
}
}
function _setUpNativeTransition(navigationTransition: frameModule.NavigationTransition, nativeTransition: any/*android.transition.Transition*/) {
if (navigationTransition.duration) {
nativeTransition.setDuration(navigationTransition.duration);
}
if (navigationTransition.curve) {
var animation: typeof animationModule = require("ui/animation");
var interpolator = animation._resolveAnimationCurve(navigationTransition.curve);
nativeTransition.setInterpolator(interpolator);
}
else {
nativeTransition.setInterpolator(_defaultInterpolator);
}
}
export function _onFragmentShown(fragment: android.app.Fragment, isBack: boolean): void {
var transitionType = isBack ? "Pop Enter" : "Enter";
var relevantTransition = isBack ? EXIT_POPENTER_TRANSITION : ENTER_POPEXIT_TRANSITION;
if (fragment[relevantTransition]) {
trace.write(`${fragment.getTag()} has been shown when going ${isBack ? "back" : "forward"}, but there is ${transitionType} ${fragment[relevantTransition]}. Will complete page addition when transition ends.`, trace.categories.Transition);
fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS] = { isBack: isBack };
}
else if (_sdkVersion >= 21) {
var nativeTransition = isBack ? (<any>fragment).getReenterTransition() : (<any>fragment).getEnterTransition();
if (nativeTransition) {
trace.write(`${fragment.getTag() } has been shown when going ${isBack ? "back" : "forward"}, but there is ${transitionType} ${nativeTransition.getClass().getSimpleName()} transition. Will complete page addition when transition ends.`, trace.categories.Transition);
fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS] = { isBack: isBack };
}
}
if (fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS] === undefined) {
_completePageAddition(fragment, isBack, true);
}
}
export function _onFragmentHidden(fragment: android.app.Fragment, isBack: boolean) {
var transitionType = isBack ? "Pop Exit" : "Exit";
var relevantTransition = isBack ? ENTER_POPEXIT_TRANSITION : EXIT_POPENTER_TRANSITION;
if (fragment[relevantTransition]) {
trace.write(`${fragment.getTag()} has been hidden when going ${isBack ? "back" : "forward"}, but there is ${transitionType} ${fragment[relevantTransition]}. Will complete page removal when transition ends.`, trace.categories.Transition);
fragment[COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS] = true;
}
else if (_sdkVersion >= 21) {
var nativeTransition = isBack ? (<any>fragment).getReturnTransition() : (<any>fragment).getExitTransition();
if (nativeTransition) {
trace.write(`${fragment.getTag()} has been hidden when going ${isBack ? "back" : "forward"}, but there is ${transitionType} ${nativeTransition.getClass().getSimpleName()} transition. Will complete page removal when transition ends.`, trace.categories.Transition);
fragment[COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS] = true;
}
}
if (fragment[COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS] === undefined) {
// This might be a second call if the fragment is hidden and then destroyed.
_completePageRemoval(fragment, true);
}
}
function _completePageAddition(fragment: android.app.Fragment, isBack: boolean, force?: boolean) {
if (fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS] || force) {
fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS] = undefined;
var frame = (<any>fragment).frame;
var entry: frameModule.BackstackEntry = (<any>fragment).entry;
var page: pageModule.Page = entry.resolvedPage;
// The original code that was once in Frame onFragmentShown
frame._currentEntry = entry;
page.onNavigatedTo(isBack);
frame._processNavigationQueue(page);
trace.write(`ADDITION of ${page} completed`, trace.categories.Transition);
}
}
function _completePageRemoval(fragment: android.app.Fragment, force?: boolean) {
if (fragment[COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS] || force) {
fragment[COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS] = undefined;
var frame = (<any>fragment).frame;
var entry: frameModule.BackstackEntry = (<any>fragment).entry;
var page: pageModule.Page = entry.resolvedPage;
if (page.frame) {
frame._removeView(page);
}
trace.write(`REMOVAL of ${page} completed`, trace.categories.Transition);
}
}
function _addNativeTransitionListener(fragment: android.app.Fragment, nativeTransition: any/*android.transition.Transition*/) {
var transitionListener = new (<any>android).transition.Transition.TransitionListener({
onTransitionCancel: function (transition: any): void {
trace.write(`CANCEL ${nativeTransition} transition for ${fragment}`, trace.categories.Transition);
if (fragment[COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS]) {
_completePageRemoval(fragment);
}
if (fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS]) {
_completePageAddition(fragment, fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS].isBack);
}
},
onTransitionEnd: function (transition: any): void {
trace.write(`END ${nativeTransition} transition for ${fragment}`, trace.categories.Transition);
if (fragment[COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS]) {
_completePageRemoval(fragment);
}
if (fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS]) {
_completePageAddition(fragment, fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS].isBack);
}
},
onTransitionPause: function (transition: any): void {
trace.write(`PAUSE ${nativeTransition} transition for ${fragment}`, trace.categories.Transition);
},
onTransitionResume: function (transition: any): void {
trace.write(`RESUME ${nativeTransition} transition for ${fragment}`, trace.categories.Transition);
},
onTransitionStart: function (transition: any): void {
trace.write(`START ${nativeTransition} transition for ${fragment}`, trace.categories.Transition);
}
});
nativeTransition.addListener(transitionListener);
}
export function _onFragmentCreateAnimator(fragment: android.app.Fragment, nextAnim: number): android.animation.Animator {
var transitionType;
switch (nextAnim) {
case enterFakeResourceId: transitionType = AndroidTransitionType.enter; break;
case exitFakeResourceId: transitionType = AndroidTransitionType.exit; break;
case popEnterFakeResourceId: transitionType = AndroidTransitionType.popEnter; break;
case popExitFakeResourceId: transitionType = AndroidTransitionType.popExit; break;
}
var transition;
switch (transitionType) {
case AndroidTransitionType.enter:
case AndroidTransitionType.popExit:
transition = <Transition>fragment[ENTER_POPEXIT_TRANSITION];
break;
case AndroidTransitionType.exit:
case AndroidTransitionType.popEnter:
transition = <Transition>fragment[EXIT_POPENTER_TRANSITION];
break;
}
var animator: android.animation.Animator;
if (transition) {
animator = <android.animation.Animator>transition.createAndroidAnimator(transitionType);
var transitionListener = new android.animation.Animator.AnimatorListener({
onAnimationStart: function (animator: android.animation.Animator): void {
trace.write(`START ${transitionType} ${transition} for ${fragment.getTag()}`, trace.categories.Transition);
},
onAnimationRepeat: function (animator: android.animation.Animator): void {
trace.write(`REPEAT ${transitionType} ${transition} for ${fragment.getTag()}`, trace.categories.Transition);
},
onAnimationEnd: function (animator: android.animation.Animator): void {
trace.write(`END ${transitionType} ${transition}`, trace.categories.Transition);
if (fragment[COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS]) {
_completePageRemoval(fragment);
}
if (fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS]) {
_completePageAddition(fragment, fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS].isBack);
}
},
onAnimationCancel: function (animator: android.animation.Animator): void {
trace.write(`CANCEL ${transitionType} ${transition} for ${fragment.getTag()}`, trace.categories.Transition);
if (fragment[COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS]) {
_completePageRemoval(fragment);
}
if (fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS]) {
_completePageAddition(fragment, fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS].isBack);
}
}
});
animator.addListener(transitionListener);
}
return animator;
}
var transitionId = 0;
export class Transition implements definition.Transition {
private _duration: number;
private _interpolator: android.view.animation.Interpolator;
private _id: number;
constructor(duration: number, curve: any) {
this._duration = duration;
if (curve) {
var animation: typeof animationModule = require("ui/animation");
this._interpolator = animation._resolveAnimationCurve(curve);
}
else {
this._interpolator = _defaultInterpolator;
}
this._id = transitionId++;
}
public getDuration(): number {
return this._duration;
}
public getCurve(): android.view.animation.Interpolator {
return this._interpolator;
}
public animateIOSTransition(containerView: any, fromView: any, toView: any, operation: any, completion: (finished: boolean) => void): void {
throw new Error("Abstract method call");
}
public createAndroidAnimator(transitionType: string): android.animation.Animator {
throw new Error("Abstract method call");
}
public toString(): string {
return `${types.getClass(this)}@${this._id}`;
}
}

29
ui/transition/transition.d.ts vendored Normal file
View File

@@ -0,0 +1,29 @@
declare module "ui/transition" {
import frame = require("ui/frame");
export module AndroidTransitionType {
export var enter: string;
export var exit: string;
export var popEnter: string;
export var popExit: string;
}
export class Transition {
constructor(duration: number, curve: any);
public getDuration(): number;
public getCurve(): any;
public animateIOSTransition(containerView: any, fromView: any, toView: any, operation: any, completion: (finished: boolean) => void): void;
public createAndroidAnimator(transitionType: string): any;
public toString(): string;
}
//@private
export function _clearForwardTransitions(fragment: any): void;
export function _setAndroidFragmentTransitions(navigationTransition: frame.NavigationTransition, currentFragment: any, newFragment: any, fragmentTransaction: any): void;
export function _onFragmentCreateAnimator(fragment: any, nextAnim: number): any;
export function _onFragmentShown(fragment: any, isBack: boolean): void;
export function _onFragmentHidden(fragment: any, isBack: boolean): void;
export function _createIOSAnimatedTransitioning(navigationTransition: frame.NavigationTransition, operation: number, fromVC: any, toVC: any): any;
//@endprivate
}

View File

@@ -0,0 +1,115 @@
import definition = require("ui/transition");
import frame = require("ui/frame");
import * as animationModule from "ui/animation";
import types = require("utils/types");
import trace = require("trace");
class AnimatedTransitioning extends NSObject implements UIViewControllerAnimatedTransitioning {
public static ObjCProtocols = [UIViewControllerAnimatedTransitioning];
private _transition: Transition;
private _operation: UINavigationControllerOperation;
private _fromVC: UIViewController;
private _toVC: UIViewController;
private _transitionType: string;
public static init(transition: Transition, operation: UINavigationControllerOperation, fromVC: UIViewController, toVC: UIViewController): AnimatedTransitioning {
var impl = <AnimatedTransitioning>AnimatedTransitioning.new();
impl._transition = transition;
impl._operation = operation;
impl._fromVC = fromVC;
impl._toVC = toVC;
return impl;
}
public animateTransition(transitionContext: any): void {
let containerView = transitionContext.performSelector("containerView");
var completion = (finished: boolean) => {
transitionContext.performSelectorWithObject("completeTransition:", finished);
}
switch (this._operation) {
case UINavigationControllerOperation.UINavigationControllerOperationPush: this._transitionType = "push"; break;
case UINavigationControllerOperation.UINavigationControllerOperationPop: this._transitionType = "pop"; break;
case UINavigationControllerOperation.UINavigationControllerOperationNone: this._transitionType = "none"; break;
}
trace.write(`START ${this._transition} ${this._transitionType}`, trace.categories.Transition);
this._transition.animateIOSTransition(containerView, this._fromVC.view, this._toVC.view, this._operation, completion);
}
public transitionDuration(transitionContext: UIViewControllerContextTransitioning): number {
return this._transition.getDuration();
}
public animationEnded(transitionCompleted: boolean): void {
if (transitionCompleted) {
trace.write(`END ${this._transition} ${this._transitionType}`, trace.categories.Transition);
}
else {
trace.write(`CANCEL ${this._transition} ${this._transitionType}`, trace.categories.Transition);
}
}
}
var transitionId = 0;
export class Transition implements definition.Transition {
private _duration: number;
private _curve: UIViewAnimationCurve;
private _id: number;
constructor(duration: number, curve: any) {
this._duration = duration ? (duration / 1000) : 0.35;
if (curve) {
var animation: typeof animationModule = require("ui/animation");
this._curve = animation._resolveAnimationCurve(curve);
}
else {
this._curve = UIViewAnimationCurve.UIViewAnimationCurveEaseInOut;
}
}
public getDuration(): number {
return this._duration;
}
public getCurve(): UIViewAnimationCurve {
return this._curve;
}
public animateIOSTransition(containerView: UIView, fromView: UIView, toView: UIView, operation: UINavigationControllerOperation, completion: (finished: boolean) => void): void {
throw new Error("Abstract method call");
}
public createAndroidAnimator(transitionType: string): any {
throw new Error("Abstract method call");
}
public toString(): string {
return `${types.getClass(this)}@${this._id}`;
}
}
export function _createIOSAnimatedTransitioning(navigationTransition: frame.NavigationTransition, operation: UINavigationControllerOperation, fromVC: UIViewController, toVC: UIViewController): UIViewControllerAnimatedTransitioning {
var transition: Transition;
if (types.isString(navigationTransition.transition)) {
var name = navigationTransition.transition.toLowerCase();
if (name.indexOf("slide") === 0) {
var slideTransitionModule = require("./slide-transition");
var direction = name.substr("slide".length) || "left"; //Extract the direction from the string
transition = new slideTransitionModule.SlideTransition(direction, navigationTransition.duration, navigationTransition.curve);
}
else if (name === "fade") {
var fadeTransitionModule = require("./fade-transition");
transition = new fadeTransitionModule.FadeTransition(navigationTransition.duration, navigationTransition.curve);
}
}
else {
transition = navigationTransition.transition;
}
if (transition) {
return AnimatedTransitioning.init(transition, operation, fromVC, toVC);
}
return null;
}

1
ui/ui.d.ts vendored
View File

@@ -4,6 +4,7 @@
declare module "ui" {
export * from "ui/action-bar";
export * from "ui/activity-indicator";
export * from "ui/animation";
export * from "ui/builder";
export * from "ui/button";
export * from "ui/content-view";