feat(android): migrate to support library apis (#6129)

Switch Activity / Fragment / FragmentManager implementation from native framework to support library APIs

BREAKING CHANGE:


NativeScript core framework now extends support library APIs versus native framework classes as per Google's latest guidelines:
- NativeScript activities now extend `android.support.v7.app.AppCompatActivity` (vs android.app.Activity)
- NativeScript fragments now extend `android.support.v4.app.Fragment` (vs android.app.Fragment)
- NativeScript now works internally with `android.support.v4.app.FragmentManager` (vs android.app.FragmentManager) 

The implications of these changes should be mostly transparent to the developer except for the fact that the support library Fragment / FragmentManager work with Animation APIs versus Animator APIs.

For Android API Levels lower than 28 the new Fragment API uses a different fragment enter animation by default. You can customise the transition per navigation entry or globally via the [navigation transitions API](https://docs.nativescript.org/core-concepts/navigation#navigation-transitions)
Before:
Default fragment enter animation was fade animation

After:
Default fragment enter animation for API levels lower than 28 is now a fast "push fade" animation; default fragment enter animation for API levels equal to or greater than 28 remains fade animation

Before:
AndroidFragmentCallbacks interface exposed the following `onCreateAnimator(...)` method
``` ts
export interface AndroidFragmentCallbacks {
    onCreateAnimator(fragment: any, transit: number, enter: boolean, nextAnim: number, superFunc: Function): any;
    // ...
}
```

After:
AndroidFragmentCallbacks interface now exposes the following `onCreateAnimation(...)` method instead (and `onCreateAnimator(...)` is now removed)
``` ts
export interface AndroidFragmentCallbacks {
    onCreateAnimation(fragment: any, transit: number, enter: boolean, nextAnim: number, superFunc: Function): any;
    // ...
}
```

Before:
Transition class exposed the following abstract `createAndroidAnimator(...)` method
``` ts
export class Transition {
    public createAndroidAnimator(transitionType: string): any;
    // ...
}
```

After:
Transition class now exposes the following abstract `createAndroidAnimation(...)` method instead (and `createAndroidAnimation(...) is now removed)
``` ts
export class Transition {
    public createAndroidAnimation(transitionType: string): any;
    // ...
}
```

To migrate the code of your custom transitions follow the example below:

Before:
``` ts
import * as transition from "tns-core-modules/ui/transition";

export class CustomTransition extends transition.Transition {
    constructor(duration: number, curve: any) {
        super(duration, curve);
    }

    public createAndroidAnimator(transitionType: string): android.animation.Animator {
        var scaleValues = Array.create("float", 2);
        switch (transitionType) {
            case transition.AndroidTransitionType.enter:
            case transition.AndroidTransitionType.popEnter:
                scaleValues[0] = 0;
                scaleValues[1] = 1;
                break;
            case transition.AndroidTransitionType.exit:
            case transition.AndroidTransitionType.popExit:
                scaleValues[0] = 1;
                scaleValues[1] = 0;
                break;
        }
        var objectAnimators = Array.create(android.animation.Animator, 2);
        objectAnimators[0] = android.animation.ObjectAnimator.ofFloat(null, "scaleX", scaleValues);
        objectAnimators[1] = android.animation.ObjectAnimator.ofFloat(null, "scaleY", scaleValues);
        var animatorSet = new android.animation.AnimatorSet();
        animatorSet.playTogether(objectAnimators);

        var duration = this.getDuration();
        if (duration !== undefined) {
            animatorSet.setDuration(duration);
        }
        animatorSet.setInterpolator(this.getCurve());

        return animatorSet;
    }
}
```

After:
``` ts
import * as transition from "tns-core-modules/ui/transition";

export class CustomTransition extends transition.Transition {
    constructor(duration: number, curve: any) {
        super(duration, curve);
    }

    public createAndroidAnimation(transitionType: string): android.view.animation.Animation {
        const scaleValues = [];

        switch (transitionType) {
            case transition.AndroidTransitionType.enter:
            case transition.AndroidTransitionType.popEnter:
                scaleValues[0] = 0;
                scaleValues[1] = 1;
                break;
            case transition.AndroidTransitionType.exit:
            case transition.AndroidTransitionType.popExit:
                scaleValues[0] = 1;
                scaleValues[1] = 0;
                break;
        }
            
        const animationSet = new android.view.animation.AnimationSet(false);
        const duration = this.getDuration();
        if (duration !== undefined) {
            animationSet.setDuration(duration);
        }

        animationSet.setInterpolator(this.getCurve());
        animationSet.addAnimation(
            new android.view.animation.ScaleAnimation(
                scaleValues[0], 
                scaleValues[1], 
                scaleValues[0], 
                scaleValues[1]
            ));

        return animationSet;
    }
}
```
This commit is contained in:
Manol Donev
2018-07-31 18:48:34 +03:00
committed by GitHub
parent 5db3e67915
commit cf034dd04d
21 changed files with 339 additions and 323 deletions

View File

@@ -42,8 +42,8 @@ export class AndroidApplication extends Observable implements AndroidApplication
public paused: boolean;
public nativeApp: android.app.Application;
public context: android.content.Context;
public foregroundActivity: android.app.Activity;
public startActivity: android.app.Activity;
public foregroundActivity: android.support.v7.app.AppCompatActivity;
public startActivity: android.support.v7.app.AppCompatActivity;
public packageName: string;
// we are using these property to store the callbacks to avoid early GC collection which would trigger MarkReachableObjects
private callbacks: any = {};
@@ -221,7 +221,7 @@ global.__onLiveSync = function () {
};
function initLifecycleCallbacks() {
const setThemeOnLaunch = profile("setThemeOnLaunch", (activity: android.app.Activity) => {
const setThemeOnLaunch = profile("setThemeOnLaunch", (activity: android.support.v7.app.AppCompatActivity) => {
// Set app theme after launch screen was used during startup
const activityInfo = activity.getPackageManager().getActivityInfo(activity.getComponentName(), android.content.pm.PackageManager.GET_META_DATA);
if (activityInfo.metaData) {
@@ -232,11 +232,11 @@ function initLifecycleCallbacks() {
}
});
const notifyActivityCreated = profile("notifyActivityCreated", function (activity: android.app.Activity, savedInstanceState: android.os.Bundle) {
const notifyActivityCreated = profile("notifyActivityCreated", function (activity: android.support.v7.app.AppCompatActivity, savedInstanceState: android.os.Bundle) {
androidApp.notify(<AndroidActivityBundleEventData>{ eventName: ActivityCreated, object: androidApp, activity, bundle: savedInstanceState });
});
const subscribeForGlobalLayout = profile("subscribeForGlobalLayout", function (activity: android.app.Activity) {
const subscribeForGlobalLayout = profile("subscribeForGlobalLayout", function (activity: android.support.v7.app.AppCompatActivity) {
const rootView = activity.getWindow().getDecorView().getRootView();
// store the listener not to trigger GC collection before collecting the method
this.onGlobalLayoutListener = new android.view.ViewTreeObserver.OnGlobalLayoutListener({
@@ -250,7 +250,7 @@ function initLifecycleCallbacks() {
});
const lifecycleCallbacks = new android.app.Application.ActivityLifecycleCallbacks({
onActivityCreated: profile("onActivityCreated", function (activity: android.app.Activity, savedInstanceState: android.os.Bundle) {
onActivityCreated: profile("onActivityCreated", function (activity: android.support.v7.app.AppCompatActivity, savedInstanceState: android.os.Bundle) {
setThemeOnLaunch(activity);
if (!androidApp.startActivity) {
@@ -264,7 +264,7 @@ function initLifecycleCallbacks() {
}
}),
onActivityDestroyed: profile("onActivityDestroyed", function (activity: android.app.Activity) {
onActivityDestroyed: profile("onActivityDestroyed", function (activity: android.support.v7.app.AppCompatActivity) {
if (activity === androidApp.foregroundActivity) {
androidApp.foregroundActivity = undefined;
}
@@ -278,7 +278,7 @@ function initLifecycleCallbacks() {
gc();
}),
onActivityPaused: profile("onActivityPaused", function (activity: android.app.Activity) {
onActivityPaused: profile("onActivityPaused", function (activity: android.support.v7.app.AppCompatActivity) {
if ((<any>activity).isNativeScriptActivity) {
androidApp.paused = true;
notify(<ApplicationEventData>{ eventName: suspendEvent, object: androidApp, android: activity });
@@ -287,7 +287,7 @@ function initLifecycleCallbacks() {
androidApp.notify(<AndroidActivityEventData>{ eventName: ActivityPaused, object: androidApp, activity: activity });
}),
onActivityResumed: profile("onActivityResumed", function (activity: android.app.Activity) {
onActivityResumed: profile("onActivityResumed", function (activity: android.support.v7.app.AppCompatActivity) {
androidApp.foregroundActivity = activity;
if ((<any>activity).isNativeScriptActivity) {
@@ -298,15 +298,15 @@ function initLifecycleCallbacks() {
androidApp.notify(<AndroidActivityEventData>{ eventName: ActivityResumed, object: androidApp, activity: activity });
}),
onActivitySaveInstanceState: profile("onActivityResumed", function (activity: android.app.Activity, outState: android.os.Bundle) {
onActivitySaveInstanceState: profile("onActivityResumed", function (activity: android.support.v7.app.AppCompatActivity, outState: android.os.Bundle) {
androidApp.notify(<AndroidActivityBundleEventData>{ eventName: SaveActivityState, object: androidApp, activity: activity, bundle: outState });
}),
onActivityStarted: profile("onActivityStarted", function (activity: android.app.Activity) {
onActivityStarted: profile("onActivityStarted", function (activity: android.support.v7.app.AppCompatActivity) {
androidApp.notify(<AndroidActivityEventData>{ eventName: ActivityStarted, object: androidApp, activity: activity });
}),
onActivityStopped: profile("onActivityStopped", function (activity: android.app.Activity) {
onActivityStopped: profile("onActivityStopped", function (activity: android.support.v7.app.AppCompatActivity) {
androidApp.notify(<AndroidActivityEventData>{ eventName: ActivityStopped, object: androidApp, activity: activity });
})
});

View File

@@ -288,7 +288,7 @@ export interface AndroidActivityEventData {
/**
* The activity.
*/
activity: any /* android.app.Activity */;
activity: any /* android.support.v7.app.AppCompatActivity */;
/**
* The name of the event.
@@ -378,7 +378,7 @@ export class AndroidApplication extends Observable {
/**
* The currently active (loaded) [android Activity](http://developer.android.com/reference/android/app/Activity.html). This property is automatically updated upon Activity events.
*/
foregroundActivity: any /* android.app.Activity */;
foregroundActivity: any /* android.support.v7.app.AppCompatActivity */;
/**
* Deprecated. Please use startActivity, foregroundActivity or context property.
@@ -388,7 +388,7 @@ export class AndroidApplication extends Observable {
/**
* The main (start) Activity for the application.
*/
startActivity: any /* android.app.Activity */;
startActivity: any /* android.support.v7.app.AppCompatActivity */;
/**
* The name of the application package.