chore: improve android and other cleanups

This commit is contained in:
Nathan Walker
2023-03-24 14:34:57 -07:00
parent 5ba3c89e0a
commit 290f848d68
6 changed files with 155 additions and 13 deletions

View File

@@ -22,6 +22,7 @@ import { profile } from '../../profiling';
import { setSuspended } from '../../application/application-common';
import { ad } from '../../utils/native-helper';
import type { ExpandedEntry } from './fragment.transitions.android';
import { SharedTransition, SharedTransitionAnimationType } from '../transition/shared-transition';
export * from './frame-common';
@@ -467,6 +468,12 @@ export class Frame extends FrameBase {
navigationTransition?.instance?.androidFragmentTransactionCallback?.(transaction, currentEntry, newEntry);
transaction.commitAllowingStateLoss();
if (navigationTransition?.instance) {
SharedTransition.updateState(navigationTransition?.instance?.id, {
activeType: SharedTransitionAnimationType.dismiss,
});
}
}
public _goBackCore(backstackEntry: BackstackEntry & ExpandedEntry) {
@@ -492,6 +499,10 @@ export class Frame extends FrameBase {
backstackEntry.transition?.androidFragmentTransactionCallback?.(transaction, this._currentEntry, backstackEntry);
transaction.commitAllowingStateLoss();
if (backstackEntry?.transition) {
SharedTransition.finishState(backstackEntry.transition.id);
}
}
public _removeEntry(removed: BackstackEntry): void {

View File

@@ -27,6 +27,10 @@ export class Transition implements TransitionType {
return this._duration;
}
public setDuration(value: number) {
this._duration = value;
}
public getCurve(): android.view.animation.Interpolator {
return this._interpolator;
}

View File

@@ -24,6 +24,7 @@ export declare class Transition {
static AndroidTransitionType?: { enter?: string; exit?: string; popEnter?: string; popExit?: string };
constructor(duration?: number, nativeCurve?: any /* UIViewAnimationCurve | string | CubicBezierAnimationCurve | android.view.animation.Interpolator | android.view.animation.LinearInterpolator */);
getDuration(): number;
setDuration(value: number): void;
getCurve(): any;
animateIOSTransition(transitionContext: any /*UIViewControllerContextTransitioning */, fromViewCtrl: any /* UIViewController */, toViewCtrl: any /* UIViewController */, operation: any /* UINavigationControllerOperation */): void;
createAndroidAnimator(transitionType: string): any;

View File

@@ -1,14 +1,57 @@
import type { View } from '../core/view';
import { ViewBase } from '../core/view-base';
import { BackstackEntry } from '../frame';
import { isNumber } from '../../utils/types';
import { FadeTransition } from './fade-transition';
import { SharedTransition } from './shared-transition';
import { SharedTransition, SharedTransitionAnimationType } from './shared-transition';
import { ImageSource } from '../../image-source';
import { ContentView } from '../content-view';
import { GridLayout } from '../layouts/grid-layout';
import { ad } from '../../utils';
// import { Image } from '../image';
@NativeClass
class CustomInterpolator extends android.view.animation.AnticipateOvershootInterpolator {
class SnapshotViewGroup extends android.view.ViewGroup {
constructor(context: android.content.Context) {
super(context);
return global.__native(this);
}
public onMeasure(): void {
this.setMeasuredDimension(0, 0);
}
public onLayout(): void {
//
}
}
class ContentViewSnapshot extends ContentView {
createNativeView() {
return new SnapshotViewGroup(this._context);
}
}
@NativeClass
class CustomSpringInterpolator extends android.view.animation.AnticipateOvershootInterpolator {
getInterpolation(input: number) {
// HACK: we speed up the interpolation by 10% to fix the issue with the transition not being finished
// Note: we speed up the interpolation by 10% to fix the issue with the transition not being finished
// and the views shifting from their intended final position...
// this is really just a hack and should be fixed properly once we
// this is really just a workaround and should be fixed properly once we
// can figure out the root cause of the issue.
const res = super.getInterpolation(input) * 1.1;
if (res > 1) {
return float(1);
}
return float(res);
}
}
@NativeClass
class CustomLinearInterpolator extends android.view.animation.LinearInterpolator {
getInterpolation(input: number) {
// Note: we speed up the interpolation by 10% to fix the issue with the transition not being finished
// and the views shifting from their intended final position...
// this is really just a workaround and should be fixed properly once we
// can figure out the root cause of the issue.
const res = super.getInterpolation(input) * 1.1;
@@ -21,7 +64,7 @@ class CustomInterpolator extends android.view.animation.AnticipateOvershootInter
}
function setTransitionName(view: ViewBase) {
if (!view.sharedTransitionTag) {
if (!view?.sharedTransitionTag) {
return;
}
try {
@@ -44,26 +87,77 @@ export class PageTransition extends FadeTransition {
const fromPage = currentEntry.resolvedPage;
const toPage = newEntry.resolvedPage;
const newFragment: androidx.fragment.app.Fragment = newEntry.fragment;
const state = SharedTransition.getState(this.id);
const pageEnd = state.pageEnd;
const { presented, presenting } = SharedTransition.getSharedElements(fromPage, toPage);
const { sharedElements, presented, presenting } = SharedTransition.getSharedElements(fromPage, toPage);
const sharedElementTags = sharedElements.map((v) => v.sharedTransitionTag);
if (SharedTransition.DEBUG) {
console.log(` Page: ${state.activeType === SharedTransitionAnimationType.present ? 'Present' : 'Dismiss'}`);
console.log(`1. Found sharedTransitionTags to animate:`, sharedElementTags);
}
// Note: we can enhance android more over time with element targeting across different screens
// const pageStart = state.pageStart;
// const pageEndIndependentTags = Object.keys(pageEnd?.sharedTransitionTags || {});
// console.log('pageEndIndependentTags:', pageEndIndependentTags);
// for (const tag of pageEndIndependentTags) {
// // only consider start when there's a matching end
// const pageStartIndependentProps = pageStart?.sharedTransitionTags[tag];
// if (pageStartIndependentProps) {
// console.log('pageStartIndependentProps:', tag, pageStartIndependentProps);
// }
// const pageEndIndependentProps = pageEnd?.sharedTransitionTags[tag];
// let independentView = presenting.find((v) => v.sharedTransitionTag === tag);
// let isPresented = false;
// if (!independentView) {
// independentView = presented.find((v) => v.sharedTransitionTag === tag);
// if (!independentView) {
// break;
// }
// isPresented = true;
// }
// if (independentView) {
// console.log('independentView:', independentView);
// const imageSource = renderToImageSource(independentView);
// const image = new Image();
// image.src = imageSource;
// const { hostView } = loadViewInBackground(image);
// (<any>fromPage).addChild(hostView);
// independentView.opacity = 0;
// }
// }
toPage.once('loaded', () => {
presented.forEach(setTransitionName);
presented.filter((v) => sharedElementTags.includes(v.sharedTransitionTag)).forEach(setTransitionName);
newFragment.startPostponedEnterTransition();
});
presenting.forEach((v) => {
sharedElements.forEach((v) => {
setTransitionName(v);
fragmentTransaction.addSharedElement(v.nativeView, v.sharedTransitionTag);
});
fragmentTransaction.setReorderingAllowed(true);
let customDuration = -1;
if (state.activeType === SharedTransitionAnimationType.present && isNumber(pageEnd?.duration)) {
customDuration = pageEnd.duration;
} else if (isNumber(state.pageReturn?.duration)) {
customDuration = state.pageReturn.duration;
}
const transitionSet = new androidx.transition.TransitionSet();
transitionSet.setDuration(this.getDuration());
transitionSet.setDuration(customDuration || this.getDuration());
transitionSet.addTransition(new androidx.transition.ChangeBounds());
transitionSet.addTransition(new androidx.transition.ChangeTransform());
transitionSet.setInterpolator(new CustomInterpolator());
if (customDuration) {
// duration always overrides default spring
transitionSet.setInterpolator(new CustomLinearInterpolator());
} else {
transitionSet.setInterpolator(new CustomSpringInterpolator());
}
// postpone enter until we call "loaded" on the new page
newFragment.postponeEnterTransition();
@@ -71,3 +165,29 @@ export class PageTransition extends FadeTransition {
newFragment.setSharedElementReturnTransition(transitionSet);
}
}
function renderToImageSource(hostView: View): ImageSource {
const bitmap = android.graphics.Bitmap.createBitmap(hostView.android.getWidth(), hostView.android.getHeight(), android.graphics.Bitmap.Config.ARGB_8888);
const canvas = new android.graphics.Canvas(bitmap);
// ensure we start with a blank transparent canvas
canvas.drawARGB(0, 0, 0, 0);
hostView.android.draw(canvas);
return new ImageSource(bitmap);
}
function loadViewInBackground(view: View) {
const hiddenHost = new ContentViewSnapshot();
const hostView = new GridLayout(); // use a host view to ensure margins are respected
hiddenHost.content = hostView;
hiddenHost.visibility = 'collapse';
hostView.addChild(view);
hiddenHost._setupAsRootView(ad.getApplicationContext());
hiddenHost.callLoaded();
ad.getCurrentActivity().addContentView(hiddenHost.android, new android.view.ViewGroup.LayoutParams(0, 0));
return {
hiddenHost,
hostView,
};
}

View File

@@ -1,10 +1,8 @@
import type { View } from '../core/view';
import { SharedElementSettings, SharedInteractiveState, Transition } from '.';
import { Screen } from '../../platform';
import { iOSNativeHelper } from '../../utils/native-helper';
import { isNumber } from '../../utils/types';
import { PanGestureEventData, GestureStateTypes } from '../gestures';
import { SharedTransition, SharedTransitionAnimationType, DEFAULT_DURATION, DEFAULT_SPRING, getRectFromProps, getPageStartDefaultsForType } from './shared-transition';
import { SharedTransition, DEFAULT_DURATION } from './shared-transition';
import { SharedTransitionHelper } from './shared-transition-helper';
export class PageTransition extends Transition {

View File

@@ -89,6 +89,10 @@ type SharedTransitionPageProperties = SharedProperties & {
* Note: When this is defined, it will override spring options and use only linear animation.
*/
duration?: number;
/**
* (iOS Only) Allow "independent" elements found only on one of the screens to take part in the animation.
* Note: This feature will be brought to Android in a future release.
*/
sharedTransitionTags?: { [key: string]: SharedProperties };
/**
* Spring animation settings.
@@ -116,6 +120,10 @@ export class SharedTransition {
instance: transition,
activeType: SharedTransitionAnimationType.present,
});
const pageEnd = options.pageEnd;
if (isNumber(pageEnd?.duration)) {
transition.setDuration(pageEnd?.duration);
}
return { instance: transition };
}
static events() {