mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-15 11:01:21 +08:00
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:

committed by
GitHub

parent
dc6540269f
commit
08e23bcc3b
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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()) {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
5
tns-core-modules/ui/core/view/view.d.ts
vendored
5
tns-core-modules/ui/core/view/view.d.ts
vendored
@ -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
|
||||||
*/
|
*/
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
entry.popExitAnimator = popExitAnimator;
|
||||||
|
}
|
||||||
|
|
||||||
case AnimationType.popExitFakeResourceId:
|
function setupExitAndPopEnterAnimation(entry: ExpandedEntry, transition: Transition): void {
|
||||||
animator = entry.popExitAnimator;
|
const listener = getAnimationListener();
|
||||||
break;
|
|
||||||
|
// 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 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!animator && sdkVersion() >= 21) {
|
onAnimationStart(animator: ExpandedAnimator): void {
|
||||||
const view = fragment.getView();
|
const entry = animator.entry;
|
||||||
const jsParent = entry.resolvedPage.parent;
|
addToWaitingQueue(entry);
|
||||||
const parent = view.getParent() || (jsParent && jsParent.nativeViewProtected);
|
if (traceEnabled()) {
|
||||||
const animatedEntries = _getAnimatedEntries(entry.frameId);
|
traceWrite(`START ${animator.transitionType} for ${entry.fragmentTag}`, traceCategories.Transition);
|
||||||
if (!animatedEntries || !animatedEntries.has(entry)) {
|
}
|
||||||
if (parent && !(<any>parent).isLaidOut()) {
|
}
|
||||||
animator = enter ? entry.defaultEnterAnimator : entry.defaultExitAnimator;
|
|
||||||
|
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) {
|
||||||
@ -449,7 +419,6 @@ function clearExitAndReenterTransitions(entry: ExpandedEntry, removeListener: bo
|
|||||||
entry.reenterTransitionListener = null;
|
entry.reenterTransitionListener = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function _clearFragment(entry: ExpandedEntry): void {
|
export function _clearFragment(entry: ExpandedEntry): void {
|
||||||
@ -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) {
|
||||||
@ -502,17 +470,6 @@ function clearEntry(entry: ExpandedEntry, removeListener: boolean): void {
|
|||||||
entry.returnTransitionListener = null;
|
entry.returnTransitionListener = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
||||||
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
//@endprivate
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* nativeTransition: androidx.transition.Transition
|
||||||
|
*/
|
||||||
|
export function addNativeTransitionListener(entry: any, nativeTransition: any): any;
|
||||||
|
//@endprivate
|
||||||
|
@ -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;
|
||||||
|
transitionState.entry = entry;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return animatorState;
|
return transitionState;
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreAnimatorState(entry: BackstackEntry, snapshot: AnimatorState): void {
|
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 {
|
||||||
|
1
tns-core-modules/ui/frame/frame.d.ts
vendored
1
tns-core-modules/ui/frame/frame.d.ts
vendored
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
Reference in New Issue
Block a user