mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-14 18:12:09 +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();
|
||||
|
||||
if (isAndroid) {
|
||||
TKUnit.waitUntilReady(() => page.frame._currentEntry.fragment.isAdded());
|
||||
TKUnit.waitUntilReady(() => page.frame._currentEntry.fragment && page.frame._currentEntry.fragment.isAdded());
|
||||
} else {
|
||||
TKUnit.waitUntilReady(() => page.isLoaded);
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ const { NativeScriptWorkerPlugin } = require("nativescript-worker-loader/NativeS
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
const hashSalt = Date.now().toString();
|
||||
|
||||
const ANDROID_MAX_CYCLES = 68;
|
||||
const ANDROID_MAX_CYCLES = 66;
|
||||
const IOS_MAX_CYCLES = 39;
|
||||
let numCyclesDetected = 0;
|
||||
|
||||
|
@ -77,6 +77,7 @@ dependencies {
|
||||
def androidxVersion = computeAndroidXVersion()
|
||||
implementation 'androidx.viewpager:viewpager:' + androidxVersion
|
||||
implementation 'androidx.fragment:fragment:' + androidxVersion
|
||||
implementation 'androidx.transition:transition:' + androidxVersion
|
||||
} else {
|
||||
println 'Using support library'
|
||||
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;
|
||||
|
||||
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() {
|
||||
Fragment parentFragment = this.getParentFragment();
|
||||
while (parentFragment != null && !parentFragment.isRemoving()) {
|
||||
@ -33,4 +12,4 @@ public abstract class FragmentBase extends Fragment {
|
||||
|
||||
return parentFragment;
|
||||
}
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ const ownerSymbol = Symbol("_owner");
|
||||
let TabFragment: any;
|
||||
let BottomNavigationBar: any;
|
||||
let AttachStateChangeListener: any;
|
||||
let appResources: android.content.res.Resources;
|
||||
|
||||
function makeFragmentName(viewId: number, id: number): string {
|
||||
return "android:bottomnavigation:" + viewId + ":" + id;
|
||||
@ -55,8 +56,9 @@ function initializeNativeClasses() {
|
||||
}
|
||||
|
||||
class TabFragmentImplementation extends org.nativescript.widgets.FragmentBase {
|
||||
private tab: BottomNavigation;
|
||||
private owner: BottomNavigation;
|
||||
private index: number;
|
||||
private backgroundBitmap: android.graphics.Bitmap = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@ -77,18 +79,61 @@ function initializeNativeClasses() {
|
||||
public onCreate(savedInstanceState: android.os.Bundle): void {
|
||||
super.onCreate(savedInstanceState);
|
||||
const args = this.getArguments();
|
||||
this.tab = getTabById(args.getInt(TABID));
|
||||
this.owner = getTabById(args.getInt(TABID));
|
||||
this.index = args.getInt(INDEX);
|
||||
if (!this.tab) {
|
||||
if (!this.owner) {
|
||||
throw new Error(`Cannot find BottomNavigation`);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -168,6 +213,7 @@ function initializeNativeClasses() {
|
||||
TabFragment = TabFragmentImplementation;
|
||||
BottomNavigationBar = BottomNavigationBarImplementation;
|
||||
AttachStateChangeListener = new AttachListener();
|
||||
appResources = application.android.context.getResources();
|
||||
}
|
||||
|
||||
function setElevation(bottomNavigationBar: org.nativescript.widgets.BottomNavigationBar) {
|
||||
@ -196,6 +242,7 @@ export class BottomNavigation extends TabNavigationBase {
|
||||
private _currentFragment: androidx.fragment.app.Fragment;
|
||||
private _currentTransaction: androidx.fragment.app.FragmentTransaction;
|
||||
private _attachedToWindow = false;
|
||||
public _originalBackground: any;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@ -320,6 +367,12 @@ export class BottomNavigation extends TabNavigationBase {
|
||||
public onLoaded(): void {
|
||||
super.onLoaded();
|
||||
|
||||
if (this._originalBackground) {
|
||||
this.backgroundColor = null;
|
||||
this.backgroundColor = this._originalBackground;
|
||||
this._originalBackground = null;
|
||||
}
|
||||
|
||||
if (this.tabStrip) {
|
||||
this.setTabStripItems(this.tabStrip.items);
|
||||
} else {
|
||||
@ -334,8 +387,14 @@ export class BottomNavigation extends TabNavigationBase {
|
||||
|
||||
_onAttachedToWindow(): void {
|
||||
super._onAttachedToWindow();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -84,7 +84,7 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
||||
public static showingModallyEvent = "showingModally";
|
||||
|
||||
protected _closeModalCallback: Function;
|
||||
|
||||
public _manager: any;
|
||||
public _modalParent: ViewCommon;
|
||||
private _modalContext: any;
|
||||
private _modal: ViewCommon;
|
||||
|
@ -272,12 +272,12 @@ export class View extends ViewCommon {
|
||||
public static androidBackPressedEvent = androidBackPressedEvent;
|
||||
|
||||
public _dialogFragment: androidx.fragment.app.DialogFragment;
|
||||
public _manager: androidx.fragment.app.FragmentManager;
|
||||
private _isClickable: boolean;
|
||||
private touchListenerIsSet: boolean;
|
||||
private touchListener: android.view.View.OnTouchListener;
|
||||
private layoutChangeListenerIsSet: boolean;
|
||||
private layoutChangeListener: android.view.View.OnLayoutChangeListener;
|
||||
private _manager: androidx.fragment.app.FragmentManager;
|
||||
private _rootManager: androidx.fragment.app.FragmentManager;
|
||||
|
||||
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
|
||||
*/
|
||||
_gestureObservers: any;
|
||||
/**
|
||||
* @private
|
||||
* androidx.fragment.app.FragmentManager
|
||||
*/
|
||||
_manager: any;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
|
@ -23,6 +23,10 @@ class FragmentClass extends org.nativescript.widgets.FragmentBase {
|
||||
this._callbacks.onStop(this, super.onStop);
|
||||
}
|
||||
|
||||
public onPause(): void {
|
||||
this._callbacks.onPause(this, super.onStop);
|
||||
}
|
||||
|
||||
public onCreate(savedInstanceState: android.os.Bundle) {
|
||||
if (!this._callbacks) {
|
||||
setFragmentCallbacks(this);
|
||||
|
@ -3,23 +3,29 @@
|
||||
// Definitions.
|
||||
import { NavigationType } from "./frame-common";
|
||||
import { NavigationTransition, BackstackEntry } from "../frame";
|
||||
import { AnimationType } from "./fragment.transitions.types";
|
||||
|
||||
// Types.
|
||||
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 { _resolveAnimationCurve } from "../animation";
|
||||
import { device } from "../../platform";
|
||||
import lazy from "../../utils/lazy";
|
||||
|
||||
import { isEnabled as traceEnabled, write as traceWrite, categories as traceCategories } from "../../trace";
|
||||
|
||||
export { AnimationType } from "./fragment.transitions.types";
|
||||
|
||||
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 {
|
||||
@ -27,12 +33,8 @@ interface ExpandedAnimator extends android.animation.Animator {
|
||||
transitionType?: string;
|
||||
}
|
||||
|
||||
interface ExpandedTransitionListener extends android.transition.Transition.TransitionListener {
|
||||
entry: ExpandedEntry;
|
||||
transition: android.transition.Transition;
|
||||
}
|
||||
|
||||
interface ExpandedEntry extends BackstackEntry {
|
||||
|
||||
enterTransitionListener: ExpandedTransitionListener;
|
||||
exitTransitionListener: ExpandedTransitionListener;
|
||||
reenterTransitionListener: ExpandedTransitionListener;
|
||||
@ -43,32 +45,21 @@ interface ExpandedEntry extends BackstackEntry {
|
||||
popEnterAnimator: ExpandedAnimator;
|
||||
popExitAnimator: ExpandedAnimator;
|
||||
|
||||
defaultEnterAnimator: ExpandedAnimator;
|
||||
defaultExitAnimator: ExpandedAnimator;
|
||||
|
||||
transition: Transition;
|
||||
transitionName: string;
|
||||
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(
|
||||
animated: boolean,
|
||||
navigationTransition: NavigationTransition,
|
||||
currentEntry: ExpandedEntry,
|
||||
newEntry: ExpandedEntry,
|
||||
fragmentTransaction: androidx.fragment.app.FragmentTransaction,
|
||||
frameId: number): void {
|
||||
frameId: number,
|
||||
fragmentTransaction: any,
|
||||
isNestedDefaultTransition?: boolean): void {
|
||||
|
||||
const currentFragment: androidx.fragment.app.Fragment = currentEntry ? currentEntry.fragment : null;
|
||||
const newFragment: androidx.fragment.app.Fragment = newEntry.fragment;
|
||||
@ -77,10 +68,8 @@ export function _setAndroidFragmentTransitions(
|
||||
throw new Error("Calling navigation before previous navigation finish.");
|
||||
}
|
||||
|
||||
if (sdkVersion() >= 21) {
|
||||
allowTransitionOverlap(currentFragment);
|
||||
allowTransitionOverlap(newFragment);
|
||||
}
|
||||
allowTransitionOverlap(currentFragment);
|
||||
allowTransitionOverlap(newFragment);
|
||||
|
||||
let name = "";
|
||||
let transition: Transition;
|
||||
@ -90,29 +79,11 @@ export function _setAndroidFragmentTransitions(
|
||||
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) {
|
||||
name = "none";
|
||||
} else if (transition) {
|
||||
name = "custom";
|
||||
// specifiying transition should override default one even if name match the lollipop transition name.
|
||||
useLollipopTransition = false;
|
||||
} else if (!useLollipopTransition && name.indexOf("slide") !== 0 && name !== "fade" && name.indexOf("flip") !== 0) {
|
||||
} else if (name.indexOf("slide") !== 0 && name !== "fade" && name.indexOf("flip") !== 0 && name.indexOf("explode") !== 0) {
|
||||
// If we are given name that doesn't match any of ours - fallback to default.
|
||||
name = "default";
|
||||
}
|
||||
@ -121,67 +92,67 @@ export function _setAndroidFragmentTransitions(
|
||||
if (currentEntry) {
|
||||
_updateTransitions(currentEntry);
|
||||
if (currentEntry.transitionName !== name ||
|
||||
currentEntry.transition !== transition ||
|
||||
!!currentEntry.useLollipopTransition !== useLollipopTransition ||
|
||||
!useLollipopTransition) {
|
||||
currentEntry.transition !== transition || isNestedDefaultTransition) {
|
||||
clearExitAndReenterTransitions(currentEntry, true);
|
||||
currentFragmentNeedsDifferentAnimation = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (name === "none") {
|
||||
transition = 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
|
||||
const noTransition = new NoTransition(0, null);
|
||||
|
||||
if (name.indexOf("slide") === 0) {
|
||||
setupNewFragmentSlideTransition(navigationTransition, newEntry, name);
|
||||
if (currentFragmentNeedsDifferentAnimation) {
|
||||
setupCurrentFragmentSlideTransition(navigationTransition, currentEntry, name);
|
||||
}
|
||||
} else if (name === "fade") {
|
||||
setupNewFragmentFadeTransition(navigationTransition, newEntry);
|
||||
if (currentFragmentNeedsDifferentAnimation) {
|
||||
setupCurrentFragmentFadeTransition(navigationTransition, currentEntry);
|
||||
}
|
||||
} else if (name === "explode") {
|
||||
setupNewFragmentExplodeTransition(navigationTransition, newEntry);
|
||||
if (currentFragmentNeedsDifferentAnimation) {
|
||||
setupCurrentFragmentExplodeTransition(navigationTransition, currentEntry);
|
||||
}
|
||||
// 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) {
|
||||
const direction = name.substr("slide".length) || "left"; //Extract the direction from the string
|
||||
transition = new SlideTransition(direction, navigationTransition.duration, navigationTransition.curve);
|
||||
setupNewFragmentSlideTransition(navigationTransition, newEntry, name);
|
||||
if (currentFragmentNeedsDifferentAnimation) {
|
||||
setupCurrentFragmentSlideTransition(navigationTransition, currentEntry, name);
|
||||
}
|
||||
} else if (name === "fade") {
|
||||
transition = new FadeTransition(navigationTransition.duration, navigationTransition.curve);
|
||||
} else if (name.indexOf("flip") === 0) {
|
||||
setupNewFragmentFadeTransition(navigationTransition, newEntry);
|
||||
if (currentFragmentNeedsDifferentAnimation) {
|
||||
setupCurrentFragmentFadeTransition(navigationTransition, currentEntry);
|
||||
}
|
||||
} else if (name === "explode") {
|
||||
setupNewFragmentExplodeTransition(navigationTransition, newEntry);
|
||||
if (currentFragmentNeedsDifferentAnimation) {
|
||||
setupCurrentFragmentExplodeTransition(navigationTransition, currentEntry);
|
||||
}
|
||||
} else if (name === "flip") {
|
||||
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;
|
||||
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) {
|
||||
currentEntry.transitionName = name;
|
||||
@ -190,45 +161,106 @@ export function _setAndroidFragmentTransitions(
|
||||
}
|
||||
}
|
||||
|
||||
setupDefaultAnimations(newEntry, new FadeTransition(150, null));
|
||||
|
||||
printTransitions(currentEntry);
|
||||
printTransitions(newEntry);
|
||||
}
|
||||
|
||||
export function _onFragmentCreateAnimator(entry: ExpandedEntry, fragment: androidx.fragment.app.Fragment, nextAnim: number, enter: boolean): android.animation.Animator {
|
||||
let animator: android.animation.Animator;
|
||||
switch (nextAnim) {
|
||||
case AnimationType.enterFakeResourceId:
|
||||
animator = entry.enterAnimator || entry.defaultEnterAnimator /* HACK */;
|
||||
break;
|
||||
function setupAllAnimation(entry: ExpandedEntry, transition: Transition): void {
|
||||
setupExitAndPopEnterAnimation(entry, transition);
|
||||
const listener = getAnimationListener();
|
||||
|
||||
case AnimationType.exitFakeResourceId:
|
||||
animator = entry.exitAnimator || entry.defaultExitAnimator /* HACK */;
|
||||
break;
|
||||
// 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;
|
||||
|
||||
case AnimationType.popEnterFakeResourceId:
|
||||
animator = entry.popEnterAnimator;
|
||||
break;
|
||||
const popExitAnimator = <ExpandedAnimator>transition.createAndroidAnimator(AndroidTransitionType.popExit);
|
||||
popExitAnimator.transitionType = AndroidTransitionType.popExit;
|
||||
popExitAnimator.entry = entry;
|
||||
popExitAnimator.addListener(listener);
|
||||
entry.popExitAnimator = popExitAnimator;
|
||||
}
|
||||
|
||||
case AnimationType.popExitFakeResourceId:
|
||||
animator = entry.popExitAnimator;
|
||||
break;
|
||||
}
|
||||
function setupExitAndPopEnterAnimation(entry: ExpandedEntry, transition: Transition): void {
|
||||
const listener = getAnimationListener();
|
||||
|
||||
if (!animator && sdkVersion() >= 21) {
|
||||
const view = fragment.getView();
|
||||
const jsParent = entry.resolvedPage.parent;
|
||||
const parent = view.getParent() || (jsParent && jsParent.nativeViewProtected);
|
||||
const animatedEntries = _getAnimatedEntries(entry.frameId);
|
||||
if (!animatedEntries || !animatedEntries.has(entry)) {
|
||||
if (parent && !(<any>parent).isLaidOut()) {
|
||||
animator = enter ? entry.defaultEnterAnimator : entry.defaultExitAnimator;
|
||||
// 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);
|
||||
}
|
||||
|
||||
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 animator;
|
||||
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> {
|
||||
@ -262,22 +294,21 @@ export function _reverseTransitions(previousEntry: ExpandedEntry, currentEntry:
|
||||
const previousFragment = previousEntry.fragment;
|
||||
const currentFragment = currentEntry.fragment;
|
||||
let transitionUsed = false;
|
||||
if (sdkVersion() >= 21) {
|
||||
const returnTransitionListener = currentEntry.returnTransitionListener;
|
||||
if (returnTransitionListener) {
|
||||
transitionUsed = true;
|
||||
currentFragment.setExitTransition(returnTransitionListener.transition);
|
||||
} else {
|
||||
currentFragment.setExitTransition(null);
|
||||
}
|
||||
|
||||
const reenterTransitionListener = previousEntry.reenterTransitionListener;
|
||||
if (reenterTransitionListener) {
|
||||
transitionUsed = true;
|
||||
previousFragment.setEnterTransition(reenterTransitionListener.transition);
|
||||
} else {
|
||||
previousFragment.setEnterTransition(null);
|
||||
}
|
||||
const returnTransitionListener = currentEntry.returnTransitionListener;
|
||||
if (returnTransitionListener) {
|
||||
transitionUsed = true;
|
||||
currentFragment.setExitTransition(returnTransitionListener.transition);
|
||||
} else {
|
||||
currentFragment.setExitTransition(null);
|
||||
}
|
||||
|
||||
const reenterTransitionListener = previousEntry.reenterTransitionListener;
|
||||
if (reenterTransitionListener) {
|
||||
transitionUsed = true;
|
||||
previousFragment.setEnterTransition(reenterTransitionListener.transition);
|
||||
} else {
|
||||
previousFragment.setEnterTransition(null);
|
||||
}
|
||||
|
||||
return transitionUsed;
|
||||
@ -285,17 +316,17 @@ export function _reverseTransitions(previousEntry: ExpandedEntry, currentEntry:
|
||||
|
||||
// Transition listener can't be static because
|
||||
// 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) {
|
||||
@Interfaces([(<any>android).transition.Transition.TransitionListener])
|
||||
class TransitionListenerImpl extends java.lang.Object implements android.transition.Transition.TransitionListener {
|
||||
constructor(public entry: ExpandedEntry, public transition: android.transition.Transition) {
|
||||
@Interfaces([(<any>androidx).transition.Transition.TransitionListener])
|
||||
class TransitionListenerImpl extends java.lang.Object implements androidx.transition.Transition.TransitionListener {
|
||||
constructor(public entry: ExpandedEntry, public transition: androidx.transition.Transition) {
|
||||
super();
|
||||
|
||||
return global.__native(this);
|
||||
}
|
||||
|
||||
public onTransitionStart(transition: android.transition.Transition): void {
|
||||
public onTransitionStart(transition: androidx.transition.Transition): void {
|
||||
const entry = this.entry;
|
||||
addToWaitingQueue(entry);
|
||||
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;
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`END ${toShortString(transition)} transition for ${entry.fragmentTag}`, traceCategories.Transition);
|
||||
@ -312,20 +343,20 @@ function getTransitionListener(entry: ExpandedEntry, transition: android.transit
|
||||
transitionOrAnimationCompleted(entry);
|
||||
}
|
||||
|
||||
onTransitionResume(transition: android.transition.Transition): void {
|
||||
onTransitionResume(transition: androidx.transition.Transition): void {
|
||||
if (traceEnabled()) {
|
||||
const fragment = this.entry.fragmentTag;
|
||||
traceWrite(`RESUME ${toShortString(transition)} transition for ${fragment}`, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
|
||||
onTransitionPause(transition: android.transition.Transition): void {
|
||||
onTransitionPause(transition: androidx.transition.Transition): void {
|
||||
if (traceEnabled()) {
|
||||
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()) {
|
||||
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);
|
||||
}
|
||||
|
||||
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 {
|
||||
const frameId = entry.frameId;
|
||||
let entries = waitingQueue.get(frameId);
|
||||
@ -394,60 +380,43 @@ function addToWaitingQueue(entry: ExpandedEntry): void {
|
||||
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 {
|
||||
if (sdkVersion() >= 21) {
|
||||
const fragment: androidx.fragment.app.Fragment = entry.fragment;
|
||||
const exitListener = entry.exitTransitionListener;
|
||||
if (exitListener) {
|
||||
const exitTransition = fragment.getExitTransition();
|
||||
if (exitTransition) {
|
||||
if (removeListener) {
|
||||
exitTransition.removeListener(exitListener);
|
||||
}
|
||||
|
||||
fragment.setExitTransition(null);
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`Cleared Exit ${exitTransition.getClass().getSimpleName()} transition for ${fragment}`, traceCategories.Transition);
|
||||
}
|
||||
const fragment: androidx.fragment.app.Fragment = entry.fragment;
|
||||
const exitListener = entry.exitTransitionListener;
|
||||
if (exitListener) {
|
||||
const exitTransition = fragment.getExitTransition();
|
||||
if (exitTransition) {
|
||||
if (removeListener) {
|
||||
exitTransition.removeListener(exitListener);
|
||||
}
|
||||
|
||||
if (removeListener) {
|
||||
entry.exitTransitionListener = null;
|
||||
fragment.setExitTransition(null);
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`Cleared Exit ${exitTransition.getClass().getSimpleName()} transition for ${fragment}`, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
|
||||
const reenterListener = entry.reenterTransitionListener;
|
||||
if (reenterListener) {
|
||||
const reenterTransition = fragment.getReenterTransition();
|
||||
if (reenterTransition) {
|
||||
if (removeListener) {
|
||||
reenterTransition.removeListener(reenterListener);
|
||||
}
|
||||
|
||||
fragment.setReenterTransition(null);
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`Cleared Reenter ${reenterTransition.getClass().getSimpleName()} transition for ${fragment}`, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
if (removeListener) {
|
||||
entry.exitTransitionListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
const reenterListener = entry.reenterTransitionListener;
|
||||
if (reenterListener) {
|
||||
const reenterTransition = fragment.getReenterTransition();
|
||||
if (reenterTransition) {
|
||||
if (removeListener) {
|
||||
entry.reenterTransitionListener = null;
|
||||
reenterTransition.removeListener(reenterListener);
|
||||
}
|
||||
|
||||
fragment.setReenterTransition(null);
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`Cleared Reenter ${reenterTransition.getClass().getSimpleName()} transition for ${fragment}`, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
|
||||
if (removeListener) {
|
||||
entry.reenterTransitionListener = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -463,55 +432,43 @@ export function _clearEntry(entry: ExpandedEntry): void {
|
||||
function clearEntry(entry: ExpandedEntry, removeListener: boolean): void {
|
||||
clearExitAndReenterTransitions(entry, removeListener);
|
||||
|
||||
if (sdkVersion() >= 21) {
|
||||
const fragment: androidx.fragment.app.Fragment = entry.fragment;
|
||||
const enterListener = entry.enterTransitionListener;
|
||||
if (enterListener) {
|
||||
const enterTransition = fragment.getEnterTransition();
|
||||
if (enterTransition) {
|
||||
if (removeListener) {
|
||||
enterTransition.removeListener(enterListener);
|
||||
}
|
||||
|
||||
fragment.setEnterTransition(null);
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`Cleared Enter ${enterTransition.getClass().getSimpleName()} transition for ${fragment}`, traceCategories.Transition);
|
||||
}
|
||||
const fragment: androidx.fragment.app.Fragment = entry.fragment;
|
||||
const enterListener = entry.enterTransitionListener;
|
||||
if (enterListener) {
|
||||
const enterTransition = fragment.getEnterTransition();
|
||||
if (enterTransition) {
|
||||
if (removeListener) {
|
||||
enterTransition.removeListener(enterListener);
|
||||
}
|
||||
|
||||
if (removeListener) {
|
||||
entry.enterTransitionListener = null;
|
||||
fragment.setEnterTransition(null);
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`Cleared Enter ${enterTransition.getClass().getSimpleName()} transition for ${fragment}`, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
|
||||
const returnListener = entry.returnTransitionListener;
|
||||
if (returnListener) {
|
||||
const returnTransition = fragment.getReturnTransition();
|
||||
if (returnTransition) {
|
||||
if (removeListener) {
|
||||
returnTransition.removeListener(returnListener);
|
||||
}
|
||||
|
||||
fragment.setReturnTransition(null);
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`Cleared Return ${returnTransition.getClass().getSimpleName()} transition for ${fragment}`, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
|
||||
if (removeListener) {
|
||||
entry.returnTransitionListener = null;
|
||||
}
|
||||
if (removeListener) {
|
||||
entry.enterTransitionListener = 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);
|
||||
const returnListener = entry.returnTransitionListener;
|
||||
if (returnListener) {
|
||||
const returnTransition = fragment.getReturnTransition();
|
||||
if (returnTransition) {
|
||||
if (removeListener) {
|
||||
returnTransition.removeListener(returnListener);
|
||||
}
|
||||
|
||||
fragment.setReturnTransition(null);
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`Cleared Return ${returnTransition.getClass().getSimpleName()} transition for ${fragment}`, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
|
||||
if (removeListener) {
|
||||
entry.returnTransitionListener = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
const listener = addNativeTransitionListener(entry, transition);
|
||||
|
||||
@ -532,7 +489,7 @@ function setEnterTransition(navigationTransition: NavigationTransition, entry: E
|
||||
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);
|
||||
const listener = addNativeTransitionListener(entry, transition);
|
||||
|
||||
@ -542,7 +499,7 @@ function setExitTransition(navigationTransition: NavigationTransition, entry: Ex
|
||||
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);
|
||||
const listener = addNativeTransitionListener(entry, transition);
|
||||
|
||||
@ -552,7 +509,7 @@ function setReenterTransition(navigationTransition: NavigationTransition, entry:
|
||||
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);
|
||||
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
|
||||
switch (direction) {
|
||||
case "left":
|
||||
setEnterTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.RIGHT));
|
||||
setReturnTransition(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 androidx.transition.Slide(android.view.Gravity.RIGHT));
|
||||
break;
|
||||
|
||||
case "right":
|
||||
setEnterTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.LEFT));
|
||||
setReturnTransition(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 androidx.transition.Slide(android.view.Gravity.LEFT));
|
||||
break;
|
||||
|
||||
case "top":
|
||||
setEnterTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.BOTTOM));
|
||||
setReturnTransition(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 androidx.transition.Slide(android.view.Gravity.BOTTOM));
|
||||
break;
|
||||
|
||||
case "bottom":
|
||||
setEnterTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.TOP));
|
||||
setReturnTransition(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 androidx.transition.Slide(android.view.Gravity.TOP));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -592,115 +549,85 @@ function setupCurrentFragmentSlideTransition(navTransition: NavigationTransition
|
||||
const direction = name.substr("slide".length) || "left"; //Extract the direction from the string
|
||||
switch (direction) {
|
||||
case "left":
|
||||
setExitTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.LEFT));
|
||||
setReenterTransition(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 androidx.transition.Slide(android.view.Gravity.LEFT));
|
||||
break;
|
||||
|
||||
case "right":
|
||||
setExitTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.RIGHT));
|
||||
setReenterTransition(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 androidx.transition.Slide(android.view.Gravity.RIGHT));
|
||||
break;
|
||||
|
||||
case "top":
|
||||
setExitTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.TOP));
|
||||
setReenterTransition(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 androidx.transition.Slide(android.view.Gravity.TOP));
|
||||
break;
|
||||
|
||||
case "bottom":
|
||||
setExitTransition(navTransition, entry, new android.transition.Slide(android.view.Gravity.BOTTOM));
|
||||
setReenterTransition(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 androidx.transition.Slide(android.view.Gravity.BOTTOM));
|
||||
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 {
|
||||
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);
|
||||
|
||||
const fadeOutReturn = new android.transition.Fade(android.transition.Fade.OUT);
|
||||
const fadeOutReturn = new androidx.transition.Fade(androidx.transition.Fade.OUT);
|
||||
setReturnTransition(navTransition, entry, fadeOutReturn);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 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).
|
||||
const fadeInReenter = new android.transition.Fade(android.transition.Fade.IN);
|
||||
const fadeInReenter = new androidx.transition.Fade(androidx.transition.Fade.IN);
|
||||
setReenterTransition(navTransition, entry, fadeInReenter);
|
||||
}
|
||||
|
||||
function setupCurrentFragmentExplodeTransition(navTransition: NavigationTransition, entry: ExpandedEntry): void {
|
||||
setExitTransition(navTransition, entry, new android.transition.Explode());
|
||||
setReenterTransition(navTransition, entry, new android.transition.Explode());
|
||||
setExitTransition(navTransition, entry, new androidx.transition.Explode());
|
||||
setReenterTransition(navTransition, entry, new androidx.transition.Explode());
|
||||
}
|
||||
|
||||
function setupNewFragmentExplodeTransition(navTransition: NavigationTransition, entry: ExpandedEntry): void {
|
||||
setupCurrentFragmentExplodeTransition(navTransition, entry);
|
||||
|
||||
setEnterTransition(navTransition, entry, new android.transition.Explode());
|
||||
setReturnTransition(navTransition, entry, new android.transition.Explode());
|
||||
setEnterTransition(navTransition, entry, new androidx.transition.Explode());
|
||||
setReturnTransition(navTransition, entry, new androidx.transition.Explode());
|
||||
}
|
||||
|
||||
function setupExitAndPopEnterAnimation(entry: ExpandedEntry, transition: Transition): void {
|
||||
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) {
|
||||
function setUpNativeTransition(navigationTransition: NavigationTransition, nativeTransition: androidx.transition.Transition) {
|
||||
if (navigationTransition.duration) {
|
||||
nativeTransition.setDuration(navigationTransition.duration);
|
||||
}
|
||||
@ -709,7 +636,7 @@ function setUpNativeTransition(navigationTransition: NavigationTransition, nativ
|
||||
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);
|
||||
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)}`;
|
||||
}
|
||||
|
||||
@ -761,19 +688,12 @@ function printTransitions(entry: ExpandedEntry) {
|
||||
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;
|
||||
result += `${fragment.getEnterTransition() ? " enter=" + toShortString(fragment.getEnterTransition()) : ""}`;
|
||||
result += `${fragment.getExitTransition() ? " exit=" + toShortString(fragment.getExitTransition()) : ""}`;
|
||||
result += `${fragment.getReenterTransition() ? " popEnter=" + toShortString(fragment.getReenterTransition()) : ""}`;
|
||||
result += `${fragment.getReturnTransition() ? " popExit=" + toShortString(fragment.getReturnTransition()) : ""}`;
|
||||
}
|
||||
const fragment = entry.fragment;
|
||||
result += `${fragment.getEnterTransition() ? " enter=" + toShortString(fragment.getEnterTransition()) : ""}`;
|
||||
result += `${fragment.getExitTransition() ? " exit=" + toShortString(fragment.getExitTransition()) : ""}`;
|
||||
result += `${fragment.getReenterTransition() ? " popEnter=" + toShortString(fragment.getReenterTransition()) : ""}`;
|
||||
result += `${fragment.getReturnTransition() ? " popExit=" + toShortString(fragment.getReturnTransition()) : ""}`;
|
||||
|
||||
traceWrite(result, traceCategories.Transition);
|
||||
}
|
||||
}
|
||||
@ -785,16 +705,24 @@ function javaObjectArray(...params: java.lang.Object[]) {
|
||||
return nativeArray;
|
||||
}
|
||||
|
||||
function createDummyZeroDurationAnimator(): android.animation.Animator {
|
||||
const animator = android.animation.ValueAnimator.ofObject(intEvaluator(), javaObjectArray(java.lang.Integer.valueOf(0), java.lang.Integer.valueOf(1)));
|
||||
// TODO: investigate why this is necessary for 3 levels of nested frames
|
||||
animator.setDuration(1);
|
||||
function createDummyZeroDurationAnimator(duration: number): android.animation.AnimatorSet {
|
||||
const animatorSet = new android.animation.AnimatorSet();
|
||||
const objectAnimators = Array.create(android.animation.Animator, 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 {
|
||||
public createAndroidAnimator(transitionType: string): android.animation.Animator {
|
||||
return createDummyZeroDurationAnimator();
|
||||
public createAndroidAnimator(transitionType: string): android.animation.AnimatorSet {
|
||||
return createDummyZeroDurationAnimator(this.getDuration());
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,8 @@
|
||||
*/ /** */
|
||||
|
||||
import { NavigationTransition, BackstackEntry } from "../frame";
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export { AnimationType } from "./fragment.transitions.types";
|
||||
// Types.
|
||||
import { Transition, AndroidTransitionType } from "../transition/transition";
|
||||
|
||||
/**
|
||||
* @private
|
||||
@ -17,12 +14,9 @@ export function _setAndroidFragmentTransitions(
|
||||
navigationTransition: NavigationTransition,
|
||||
currentEntry: BackstackEntry,
|
||||
newEntry: BackstackEntry,
|
||||
frameId: number,
|
||||
fragmentTransaction: any,
|
||||
frameId: number): void;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export function _onFragmentCreateAnimator(entry: BackstackEntry, fragment: any, nextAnim: number, enter: boolean): any;
|
||||
isNestedDefaultTransition?: boolean): void;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@ -57,4 +51,10 @@ export function _clearFragment(entry: BackstackEntry): void;
|
||||
* @private
|
||||
*/
|
||||
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";
|
||||
|
||||
import {
|
||||
_setAndroidFragmentTransitions, _onFragmentCreateAnimator, _getAnimatedEntries,
|
||||
_updateTransitions, _reverseTransitions, _clearEntry, _clearFragment, AnimationType
|
||||
_setAndroidFragmentTransitions, _getAnimatedEntries,
|
||||
_updateTransitions, _reverseTransitions, _clearEntry, _clearFragment, addNativeTransitionListener
|
||||
} from "./fragment.transitions";
|
||||
|
||||
// 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";
|
||||
|
||||
interface AnimatorState {
|
||||
enterAnimator: any;
|
||||
exitAnimator: any;
|
||||
popEnterAnimator: any;
|
||||
popExitAnimator: any;
|
||||
interface TransitionState {
|
||||
enterTransitionListener: any;
|
||||
exitTransitionListener: any;
|
||||
reenterTransitionListener: any;
|
||||
returnTransitionListener: any;
|
||||
transitionName: string;
|
||||
entry: BackstackEntry;
|
||||
}
|
||||
|
||||
const ANDROID_PLATFORM = "android";
|
||||
@ -47,6 +48,7 @@ const activityRootViewsMap = new Map<number, WeakRef<View>>();
|
||||
|
||||
let navDepth = -1;
|
||||
let fragmentId = -1;
|
||||
|
||||
export let moduleLoaded: boolean;
|
||||
|
||||
if (global && global.__inspector) {
|
||||
@ -118,7 +120,7 @@ export class Frame extends FrameBase {
|
||||
private _containerViewId: number = -1;
|
||||
private _tearDownPending = false;
|
||||
private _attachedToWindow = false;
|
||||
private _cachedAnimatorState: AnimatorState;
|
||||
private _cachedTransitionState: TransitionState;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@ -154,6 +156,13 @@ export class Frame extends FrameBase {
|
||||
_onAttachedToWindow(): void {
|
||||
super._onAttachedToWindow();
|
||||
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();
|
||||
}
|
||||
|
||||
@ -182,7 +191,9 @@ export class Frame extends FrameBase {
|
||||
|
||||
const manager = this._getFragmentManager();
|
||||
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)
|
||||
// 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
|
||||
@ -193,12 +204,17 @@ export class Frame extends FrameBase {
|
||||
// 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.
|
||||
// NOTE: we are restoring the animation settings in Frame.setCurrent(...) as navigation completes asynchronously
|
||||
this._cachedAnimatorState = getAnimatorState(this._currentEntry);
|
||||
let cachedTransitionState = getTransitionState(this._currentEntry);
|
||||
|
||||
this._currentEntry = null;
|
||||
// NavigateCore will eventually call _processNextNavigationEntry again.
|
||||
this._navigateCore(entry);
|
||||
this._currentEntry = entry;
|
||||
if (cachedTransitionState) {
|
||||
this._cachedTransitionState = cachedTransitionState;
|
||||
this._currentEntry = null;
|
||||
// NavigateCore will eventually call _processNextNavigationEntry again.
|
||||
this._navigateCore(entry);
|
||||
this._currentEntry = entry;
|
||||
} else {
|
||||
super._processNextNavigationEntry();
|
||||
}
|
||||
} else {
|
||||
super._processNextNavigationEntry();
|
||||
}
|
||||
@ -246,7 +262,15 @@ export class Frame extends FrameBase {
|
||||
|
||||
const manager: androidx.fragment.app.FragmentManager = this._getFragmentManager();
|
||||
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();
|
||||
}
|
||||
|
||||
@ -314,9 +338,9 @@ export class Frame extends FrameBase {
|
||||
}
|
||||
|
||||
// restore cached animation settings if we just completed simulated first navigation (no animation)
|
||||
if (this._cachedAnimatorState) {
|
||||
restoreAnimatorState(this._currentEntry, this._cachedAnimatorState);
|
||||
this._cachedAnimatorState = null;
|
||||
if (this._cachedTransitionState) {
|
||||
restoreTransitionState(this._currentEntry, this._cachedTransitionState);
|
||||
this._cachedTransitionState = null;
|
||||
}
|
||||
|
||||
// restore original fragment transitions if we just completed replace navigation (hmr)
|
||||
@ -328,7 +352,7 @@ export class Frame extends FrameBase {
|
||||
const currentEntry = null;
|
||||
const newEntry = entry;
|
||||
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;
|
||||
}
|
||||
|
||||
_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) {
|
||||
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);
|
||||
@ -430,12 +457,7 @@ export class Frame extends FrameBase {
|
||||
_updateTransitions(backstackEntry);
|
||||
}
|
||||
|
||||
const transitionReversed = _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);
|
||||
}
|
||||
_reverseTransitions(backstackEntry, this._currentEntry);
|
||||
|
||||
transaction.replace(this.containerViewId, backstackEntry.fragment, backstackEntry.fragmentTag);
|
||||
transaction.commitAllowingStateLoss();
|
||||
@ -534,48 +556,50 @@ export class Frame extends FrameBase {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function cloneExpandedAnimator(expandedAnimator: any) {
|
||||
if (!expandedAnimator) {
|
||||
function cloneExpandedTransitionListener(expandedTransitionListener: any) {
|
||||
if (!expandedTransitionListener) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const clone = expandedAnimator.clone();
|
||||
clone.entry = expandedAnimator.entry;
|
||||
clone.transitionType = expandedAnimator.transitionType;
|
||||
const cloneTransition = expandedTransitionListener.transition.clone();
|
||||
|
||||
return clone;
|
||||
return addNativeTransitionListener(expandedTransitionListener.entry, cloneTransition);
|
||||
}
|
||||
|
||||
function getAnimatorState(entry: BackstackEntry): AnimatorState {
|
||||
function getTransitionState(entry: BackstackEntry): TransitionState {
|
||||
const expandedEntry = <any>entry;
|
||||
const animatorState = <AnimatorState>{};
|
||||
const transitionState = <TransitionState>{};
|
||||
|
||||
animatorState.enterAnimator = cloneExpandedAnimator(expandedEntry.enterAnimator);
|
||||
animatorState.exitAnimator = cloneExpandedAnimator(expandedEntry.exitAnimator);
|
||||
animatorState.popEnterAnimator = cloneExpandedAnimator(expandedEntry.popEnterAnimator);
|
||||
animatorState.popExitAnimator = cloneExpandedAnimator(expandedEntry.popExitAnimator);
|
||||
animatorState.transitionName = expandedEntry.transitionName;
|
||||
if (expandedEntry.enterTransitionListener && expandedEntry.exitTransitionListener) {
|
||||
transitionState.enterTransitionListener = cloneExpandedTransitionListener(expandedEntry.enterTransitionListener);
|
||||
transitionState.exitTransitionListener = cloneExpandedTransitionListener(expandedEntry.exitTransitionListener);
|
||||
transitionState.reenterTransitionListener = cloneExpandedTransitionListener(expandedEntry.reenterTransitionListener);
|
||||
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;
|
||||
if (snapshot.enterAnimator) {
|
||||
expandedEntry.enterAnimator = snapshot.enterAnimator;
|
||||
if (snapshot.enterTransitionListener) {
|
||||
expandedEntry.enterTransitionListener = snapshot.enterTransitionListener;
|
||||
}
|
||||
|
||||
if (snapshot.exitAnimator) {
|
||||
expandedEntry.exitAnimator = snapshot.exitAnimator;
|
||||
if (snapshot.exitTransitionListener) {
|
||||
expandedEntry.exitTransitionListener = snapshot.exitTransitionListener;
|
||||
}
|
||||
|
||||
if (snapshot.popEnterAnimator) {
|
||||
expandedEntry.popEnterAnimator = snapshot.popEnterAnimator;
|
||||
if (snapshot.reenterTransitionListener) {
|
||||
expandedEntry.reenterTransitionListener = snapshot.reenterTransitionListener;
|
||||
}
|
||||
|
||||
if (snapshot.popExitAnimator) {
|
||||
expandedEntry.popExitAnimator = snapshot.popExitAnimator;
|
||||
if (snapshot.returnTransitionListener) {
|
||||
expandedEntry.returnTransitionListener = snapshot.returnTransitionListener;
|
||||
}
|
||||
|
||||
expandedEntry.transitionName = snapshot.transitionName;
|
||||
@ -777,6 +801,7 @@ export function setFragmentClass(clazz: any) {
|
||||
class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
|
||||
public frame: Frame;
|
||||
public entry: BackstackEntry;
|
||||
private backgroundBitmap: android.graphics.Bitmap = null;
|
||||
|
||||
@profile
|
||||
public onHiddenChanged(fragment: androidx.fragment.app.Fragment, hidden: boolean, superFunc: Function): void {
|
||||
@ -787,31 +812,17 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
|
||||
}
|
||||
|
||||
@profile
|
||||
public onCreateAnimator(fragment: org.nativescript.widgets.FragmentBase, transit: number, enter: boolean, nextAnim: number, superFunc: Function): android.animation.Animator {
|
||||
// HACK: FragmentBase class MUST handle removing nested fragment scenario to workaround
|
||||
// https://code.google.com/p/android/issues/detail?id=55228
|
||||
if (!enter && fragment.getRemovingParentFragment()) {
|
||||
return superFunc.call(fragment, transit, enter, nextAnim);
|
||||
public onCreateAnimator(fragment: androidx.fragment.app.Fragment, transit: number, enter: boolean, nextAnim: number, superFunc: Function): android.animation.Animator {
|
||||
let animator = null;
|
||||
const entry = <any>this.entry;
|
||||
|
||||
// Return enterAnimator only when new (no current entry) nested transition.
|
||||
if (enter && entry.isNestedDefaultTransition) {
|
||||
animator = entry.enterAnimator;
|
||||
entry.isNestedDefaultTransition = false;
|
||||
}
|
||||
|
||||
let nextAnimString: string;
|
||||
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;
|
||||
return animator || superFunc.call(fragment, transit, enter, nextAnim);
|
||||
}
|
||||
|
||||
@profile
|
||||
@ -902,7 +913,7 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
|
||||
parentView.addViewInLayout(nativeView, -1, new org.nativescript.widgets.CommonLayoutParams());
|
||||
}
|
||||
|
||||
parentView.removeView(nativeView);
|
||||
parentView.removeAllViews();
|
||||
}
|
||||
}
|
||||
|
||||
@ -918,11 +929,18 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
|
||||
}
|
||||
|
||||
@profile
|
||||
public onDestroyView(fragment: androidx.fragment.app.Fragment, superFunc: Function): void {
|
||||
public onDestroyView(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void {
|
||||
if (traceEnabled()) {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
public onStop(fragment: androidx.fragment.app.Fragment, superFunc: Function): void {
|
||||
superFunc.call(fragment);
|
||||
@ -969,6 +999,22 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
|
||||
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 {
|
||||
|
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;
|
||||
onDestroyView(fragment: any, superFunc: Function): void;
|
||||
onDestroy(fragment: any, superFunc: Function): void;
|
||||
onPause(fragment: any, superFunc: Function): void;
|
||||
onStop(fragment: any, superFunc: Function): void;
|
||||
toStringOverride(fragment: any, superFunc: Function): string;
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import { TabStripItem } from "../tab-strip-item";
|
||||
import { ViewBase, AddArrayFromBuilder, AddChildFromBuilder, EventData } from "../../core/view";
|
||||
|
||||
// Requires
|
||||
import { View, Property, CoercibleProperty, isIOS } from "../../core/view";
|
||||
import { View, Property, CoercibleProperty, isIOS, Color } from "../../core/view";
|
||||
|
||||
// TODO: Impl trace
|
||||
// export const traceCategory = "TabView";
|
||||
@ -265,6 +265,8 @@ export const selectedIndexProperty = new CoercibleProperty<TabNavigationBase, nu
|
||||
});
|
||||
selectedIndexProperty.register(TabNavigationBase);
|
||||
|
||||
export const _tabs = new Array<WeakRef<TabNavigationBase>>();
|
||||
|
||||
export const itemsProperty = new Property<TabNavigationBase, TabContentItem[]>({
|
||||
name: "items", valueChanged: (target, oldValue, newValue) => {
|
||||
target.onItemsChanged(oldValue, newValue);
|
||||
|
@ -27,6 +27,7 @@ interface PagerAdapter {
|
||||
const TABID = "_tabId";
|
||||
const INDEX = "_index";
|
||||
let PagerAdapter: PagerAdapter;
|
||||
let appResources: android.content.res.Resources;
|
||||
|
||||
function makeFragmentName(viewId: number, id: number): string {
|
||||
return "android:viewpager:" + viewId + ":" + id;
|
||||
@ -48,8 +49,9 @@ function initializeNativeClasses() {
|
||||
}
|
||||
|
||||
class TabFragmentImplementation extends org.nativescript.widgets.FragmentBase {
|
||||
private tab: TabView;
|
||||
private owner: TabView;
|
||||
private index: number;
|
||||
private backgroundBitmap: android.graphics.Bitmap = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@ -70,18 +72,61 @@ function initializeNativeClasses() {
|
||||
public onCreate(savedInstanceState: android.os.Bundle): void {
|
||||
super.onCreate(savedInstanceState);
|
||||
const args = this.getArguments();
|
||||
this.tab = getTabById(args.getInt(TABID));
|
||||
this.owner = getTabById(args.getInt(TABID));
|
||||
this.index = args.getInt(INDEX);
|
||||
if (!this.tab) {
|
||||
if (!this.owner) {
|
||||
throw new Error(`Cannot find TabView`);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
@ -233,6 +278,7 @@ function initializeNativeClasses() {
|
||||
}
|
||||
|
||||
PagerAdapter = FragmentPagerAdapter;
|
||||
appResources = application.android.context.getResources();
|
||||
}
|
||||
|
||||
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);
|
||||
if (is) {
|
||||
// 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 {
|
||||
traceMissingIcon(item.iconSource);
|
||||
}
|
||||
@ -397,6 +443,7 @@ export class TabView extends TabViewBase {
|
||||
private _viewPager: androidx.viewpager.widget.ViewPager;
|
||||
private _pagerAdapter: androidx.viewpager.widget.PagerAdapter;
|
||||
private _androidViewId: number = -1;
|
||||
public _originalBackground: any;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@ -534,6 +581,12 @@ export class TabView extends TabViewBase {
|
||||
public onLoaded(): void {
|
||||
super.onLoaded();
|
||||
|
||||
if (this._originalBackground) {
|
||||
this.backgroundColor = null;
|
||||
this.backgroundColor = this._originalBackground;
|
||||
this._originalBackground = null;
|
||||
}
|
||||
|
||||
this.setAdapterItems(this.items);
|
||||
}
|
||||
|
||||
|
@ -23,14 +23,16 @@ const ACCENT_COLOR = "colorAccent";
|
||||
const PRIMARY_COLOR = "colorPrimary";
|
||||
const DEFAULT_ELEVATION = 4;
|
||||
|
||||
const TABID = "_tabId";
|
||||
const INDEX = "_index";
|
||||
|
||||
interface PagerAdapter {
|
||||
new(owner: Tabs): androidx.viewpager.widget.PagerAdapter;
|
||||
}
|
||||
|
||||
const TABID = "_tabId";
|
||||
const INDEX = "_index";
|
||||
let PagerAdapter: PagerAdapter;
|
||||
let TabsBar: any;
|
||||
let appResources: android.content.res.Resources;
|
||||
|
||||
function makeFragmentName(viewId: number, id: number): string {
|
||||
return "android:viewpager:" + viewId + ":" + id;
|
||||
@ -52,8 +54,9 @@ function initializeNativeClasses() {
|
||||
}
|
||||
|
||||
class TabFragmentImplementation extends org.nativescript.widgets.FragmentBase {
|
||||
private tab: Tabs;
|
||||
private owner: Tabs;
|
||||
private index: number;
|
||||
private backgroundBitmap: android.graphics.Bitmap = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@ -74,18 +77,61 @@ function initializeNativeClasses() {
|
||||
public onCreate(savedInstanceState: android.os.Bundle): void {
|
||||
super.onCreate(savedInstanceState);
|
||||
const args = this.getArguments();
|
||||
this.tab = getTabById(args.getInt(TABID));
|
||||
this.owner = getTabById(args.getInt(TABID));
|
||||
this.index = args.getInt(INDEX);
|
||||
if (!this.tab) {
|
||||
if (!this.owner) {
|
||||
throw new Error(`Cannot find TabView`);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
@ -285,6 +331,7 @@ function initializeNativeClasses() {
|
||||
|
||||
PagerAdapter = FragmentPagerAdapter;
|
||||
TabsBar = TabsBarImplementation;
|
||||
appResources = application.android.context.getResources();
|
||||
}
|
||||
|
||||
let defaultAccentColor: number = undefined;
|
||||
@ -325,6 +372,7 @@ export class Tabs extends TabsBase {
|
||||
private _viewPager: androidx.viewpager.widget.ViewPager;
|
||||
private _pagerAdapter: androidx.viewpager.widget.PagerAdapter;
|
||||
private _androidViewId: number = -1;
|
||||
public _originalBackground: any;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@ -456,6 +504,12 @@ export class Tabs extends TabsBase {
|
||||
public onLoaded(): void {
|
||||
super.onLoaded();
|
||||
|
||||
if (this._originalBackground) {
|
||||
this.backgroundColor = null;
|
||||
this.backgroundColor = this._originalBackground;
|
||||
this._originalBackground = null;
|
||||
}
|
||||
|
||||
this.setItems((<any>this.items));
|
||||
|
||||
if (this.tabStrip) {
|
||||
@ -653,7 +707,7 @@ export class Tabs extends TabsBase {
|
||||
image = this.getFixedSizeIcon(image);
|
||||
}
|
||||
|
||||
imageDrawable = new android.graphics.drawable.BitmapDrawable(application.android.context.getResources(), image);
|
||||
imageDrawable = new android.graphics.drawable.BitmapDrawable(appResources, image);
|
||||
} else {
|
||||
// TODO
|
||||
// traceMissingIcon(iconSource);
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Transition, AndroidTransitionType } from "./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);
|
||||
switch (transitionType) {
|
||||
case AndroidTransitionType.enter:
|
||||
@ -16,14 +17,15 @@ export class FadeTransition extends Transition {
|
||||
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();
|
||||
if (duration !== undefined) {
|
||||
animator.setDuration(duration);
|
||||
}
|
||||
|
||||
animator.setInterpolator(this.getCurve());
|
||||
|
||||
return animator;
|
||||
animatorSet.play(animator);
|
||||
|
||||
return animatorSet;
|
||||
}
|
||||
}
|
||||
|
@ -9,10 +9,10 @@ export class FlipTransition extends Transition {
|
||||
this._direction = direction;
|
||||
}
|
||||
|
||||
public createAndroidAnimator(transitionType: string): android.animation.Animator {
|
||||
public createAndroidAnimator(transitionType: string): android.animation.AnimatorSet {
|
||||
let objectAnimators;
|
||||
let values;
|
||||
let animator: android.animation.ObjectAnimator;
|
||||
let animator: android.animation.Animator; //android.animation.ObjectAnimator;
|
||||
const animatorSet = new android.animation.AnimatorSet();
|
||||
const fullDuration = this.getDuration() || 300;
|
||||
const interpolator = this.getCurve();
|
||||
@ -20,30 +20,23 @@ export class FlipTransition extends Transition {
|
||||
|
||||
switch (transitionType) {
|
||||
case AndroidTransitionType.enter: // card_flip_right_in
|
||||
objectAnimators = Array.create(android.animation.Animator, 3);
|
||||
|
||||
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;
|
||||
objectAnimators = Array.create(android.animation.Animator, 2);
|
||||
|
||||
values = Array.create("float", 2);
|
||||
values[0] = rotationY;
|
||||
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.setDuration(fullDuration);
|
||||
objectAnimators[1] = animator;
|
||||
objectAnimators[0] = animator;
|
||||
|
||||
values = Array.create("float", 2);
|
||||
values = Array.create("float", 3);
|
||||
values[0] = 0.0;
|
||||
values[1] = 1.0;
|
||||
animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
|
||||
animator.setStartDelay(fullDuration / 2);
|
||||
animator.setDuration(1);
|
||||
objectAnimators[2] = animator;
|
||||
values[1] = 0.0;
|
||||
values[2] = 255.0;
|
||||
animator = <android.animation.Animator>android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
|
||||
animator.setDuration(fullDuration / 2);
|
||||
objectAnimators[1] = animator;
|
||||
break;
|
||||
case AndroidTransitionType.exit: // card_flip_right_out
|
||||
objectAnimators = Array.create(android.animation.Animator, 2);
|
||||
@ -51,44 +44,37 @@ export class FlipTransition extends Transition {
|
||||
values = Array.create("float", 2);
|
||||
values[0] = 0.0;
|
||||
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.setDuration(fullDuration);
|
||||
objectAnimators[0] = animator;
|
||||
|
||||
values = Array.create("float", 2);
|
||||
values[0] = 1.0;
|
||||
values = Array.create("float", 3);
|
||||
values[0] = 255.0;
|
||||
values[1] = 0.0;
|
||||
animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
|
||||
animator.setStartDelay(fullDuration / 2);
|
||||
animator.setDuration(1);
|
||||
values[2] = 0.0;
|
||||
animator = <android.animation.Animator>android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
|
||||
animator.setDuration(fullDuration / 2);
|
||||
objectAnimators[1] = animator;
|
||||
break;
|
||||
case AndroidTransitionType.popEnter: // card_flip_left_in
|
||||
objectAnimators = Array.create(android.animation.Animator, 3);
|
||||
|
||||
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;
|
||||
objectAnimators = Array.create(android.animation.Animator, 2);
|
||||
|
||||
values = Array.create("float", 2);
|
||||
values[0] = -rotationY;
|
||||
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.setDuration(fullDuration);
|
||||
objectAnimators[1] = animator;
|
||||
objectAnimators[0] = animator;
|
||||
|
||||
values = Array.create("float", 2);
|
||||
values = Array.create("float", 3);
|
||||
values[0] = 0.0;
|
||||
values[1] = 1.0;
|
||||
animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
|
||||
animator.setStartDelay(fullDuration / 2);
|
||||
animator.setDuration(1);
|
||||
objectAnimators[2] = animator;
|
||||
values[1] = 0.0;
|
||||
values[2] = 255.0;
|
||||
animator = <android.animation.Animator>android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
|
||||
animator.setDuration(fullDuration / 2);
|
||||
objectAnimators[1] = animator;
|
||||
break;
|
||||
case AndroidTransitionType.popExit: // card_flip_left_out
|
||||
objectAnimators = Array.create(android.animation.Animator, 2);
|
||||
@ -96,17 +82,17 @@ export class FlipTransition extends Transition {
|
||||
values = Array.create("float", 2);
|
||||
values[0] = 0.0;
|
||||
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.setDuration(fullDuration);
|
||||
objectAnimators[0] = animator;
|
||||
|
||||
values = Array.create("float", 2);
|
||||
values[0] = 1.0;
|
||||
values = Array.create("float", 3);
|
||||
values[0] = 255.0;
|
||||
values[1] = 0.0;
|
||||
animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
|
||||
animator.setStartDelay(fullDuration / 2);
|
||||
animator.setDuration(1);
|
||||
values[2] = 0.0;
|
||||
animator = <android.animation.Animator>android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
|
||||
animator.setDuration(fullDuration / 2);
|
||||
objectAnimators[1] = animator;
|
||||
break;
|
||||
}
|
||||
|
@ -1,6 +1,11 @@
|
||||
declare module org {
|
||||
module nativescript {
|
||||
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 class CompleteCallback {
|
||||
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 {
|
||||
constructor(density: number);
|
||||
constructor(density: number, id: string);
|
||||
@ -173,12 +184,6 @@
|
||||
public verticalAlignment: VerticalAlignment;
|
||||
}
|
||||
|
||||
export class FragmentBase extends androidx.fragment.app.Fragment {
|
||||
constructor();
|
||||
|
||||
public getRemovingParentFragment(): androidx.fragment.app.Fragment;
|
||||
}
|
||||
|
||||
export enum Stretch {
|
||||
none,
|
||||
aspectFill,
|
||||
|
Reference in New Issue
Block a user