NativeScript / NativeScript

⚡ Empowering JavaScript with native platform APIs. ✨ Best of all worlds (TypeScript, Swift, Objective C, Kotlin, Java, Dart). Use what you love ❤️ Angular, Capacitor, Ionic, React, Solid, Svelte, Vue with: iOS (UIKit, SwiftUI), Android (View, Jetpack Compose), Dart (Flutter) and you name it compatible.
https://nativescript.org
MIT License
24.06k stars 1.64k forks source link

Navigation Transition Exit/Enter specific option #7700

Open bradmartin opened 5 years ago

bradmartin commented 5 years ago

Is your feature request related to a problem? Please describe. For years I've avoided the slide transition on Android because I've never used an app on android (in many years) that does slide transitions that look how NS does. So it looked odd to me to have a weird transition that most android users of native apps have never seen, a bit gimmicky at times. A lot of Android apps use the slideTop style transition for the new activity/fragment, but the previous activity/fragment does not ALSO get the same slideTop for its exit transition. In NS, if you specify slideTop then the new fragment uses it for enter and the current fragment uses it or exit transition. The current fragment having that same transition applied is why I've never used the slide transition personally.

Describe the solution you'd like

  1. Introduce a new flag into the NavigationEntry (maybe even transitionAndroid) that allows the user to DISABLE the current fragment having the exit/reenter transition applied to it. --- I prefer option 1 since it's not breaking and provides a very nice feature to improve NS Android.

  2. Open the API up for navigate to specify the enter/exit transitions.

Describe alternatives you've considered Hacking this in my core-modules with a custom fork 😄.

Additional context

For some context.

Notice in the Settings app, the new fragment/activity is using a slide transition to enter. It is also fading but that's more work involved and outside the scope of this request. Notice how the current main fragment/activity does not have the same transition applied to it. Which is good on back nav because the previous page is visible to the user and they aren't watching some "reverse" transition.

settingsapp

Here is how NS currently does the slideTop which has never been pleasing to me personally as I've never seen this on any Android app I use. notsmooth

I've just hacked core-modules a bit and applied fade on the current fragment to get this: slideandfade

Again, not perfect, and I might even prefer disabling the exit transition of the current fragment even more so the new one just slides on top instead of 'pushes' it out of the way.

bradmartin commented 5 years ago

After thinking about this and my original idea of a new flag.

Possibly something as simple as adding: skipCurrentModuleTransition?: boolean to the NavigationTransition interface would work.

When this is true, the frame code can skip the setupCurrentFragment...() methods that apply the exit/reenter transitions.

UPDATE: In trying this out, I'm not 100% certain but skipping the steps to apply a current fragment transition ends up not working through the transition logic. I'm sure there is some other stuff happening that I've not debugged yet. So perhaps, a simpler idea is to use fade when skipCurrentModuleTransition: true.? Of course, someone more familiar with the frame transitions might know why it's behaving odd when I don't apply the current frame with a transition. Ideally, if that didn't have any negative impact on the stack then it seems like a simple "win" in terms of improving the UI/UX of NS Android apps.

I'm not sure on iOS ATM, I believe the slide transitions look and behave like they should on majority of iOS apps so this might end up being an android only transition property.

Greene48 commented 4 years ago

I'm looking to achieve the same transition. I tried to create it using a custom transition, which works decently well. I'm using Nativescript Vue, but it should be similar for other versions of Nativescript as well.

I created the file custom-transition.android.js:

import { Transition, AndroidTransitionType } from "tns-core-modules/ui/transition";
import * as platform from "tns-core-modules/platform";
import lazy from "tns-core-modules/utils/lazy";

const screenWidth = lazy(() => platform.screen.mainScreen.widthPixels);
const screenHeight = lazy(() => platform.screen.mainScreen.heightPixels);

export default class CustomTransition extends Transition {
    createAndroidAnimator(transitionType) {
        const scaleValues = Array.create("float", 2);
        const alphaValues = Array.create("float", 2);
        switch (transitionType) {
            case AndroidTransitionType.exit:
                scaleValues[0] = 0;
                scaleValues[1] = -200;
                alphaValues[0] = 1;
                alphaValues[1] = 0;
                break;
            case AndroidTransitionType.enter:
                scaleValues[0] = screenHeight() / 2;
                scaleValues[1] = 0;
                alphaValues[0] = 0;
                alphaValues[1] = 1;
                break;
            case AndroidTransitionType.popEnter:
                scaleValues[0] = 0;
                scaleValues[1] = 0;
                alphaValues[0] = 0;
                alphaValues[1] = 1;
                break;
            case AndroidTransitionType.popExit:
                scaleValues[0] = 0;
                scaleValues[1] = screenHeight();
                alphaValues[0] = 1;
                alphaValues[1] = 0;
                break;
        }
        const objectAnimators = Array.create(android.animation.Animator, 2);
        objectAnimators[0] = android.animation.ObjectAnimator.ofFloat(null, "translationY", scaleValues);
        objectAnimators[1] = android.animation.ObjectAnimator.ofFloat(null, "alpha", alphaValues);

        const animatorSet = new android.animation.AnimatorSet();
        animatorSet.playTogether(objectAnimators);

        animatorSet.setDuration(300);

        animatorSet.setInterpolator(this.getCurve());
        return animatorSet;
    }
}

Then I added the following code to my app.js file:

import CustomTransition from "./custom-transition"
const customTransition = new CustomTransition()
Vue.prototype.$customTransition = customTransition

Then call the transition in a .vue component like this:

this.$navigateTo(Item, {
    transitionAndroid: {
        instance: this.$customTransition
    }
});

Which gives the following transition:

transition

The only issue I'm having is that the AndroidTransitionType.enter always appears below the AndroidTransitionType.exit page. So unless I set alpha of the current page/fragment to 0, you don't see the new page/fragment transitioning in. Which isn't a deal breaker but a little annoying because I'd rather the header of the current page/fragment not turn white as the new page transitions in. I tried applying setZadjustment in a few different ways but couldn't get anything to work.