UNPKG

react-native-reanimated

Version:

More powerful alternative to Animated library for React Native.

194 lines (182 loc) • 6.19 kB
import { runOnUIImmediately } from '../../threads'; import type { ProgressAnimation } from '../animationBuilder/commonTypes'; import { registerEventHandler, unregisterEventHandler } from '../../core'; import { Platform } from 'react-native'; type TransitionProgressEvent = { closing: number; goingForward: number; eventName: string; progress: number; target: number; }; export class ProgressTransitionManager { private _sharedElementCount = 0; private _eventHandler = { isRegistered: false, onTransitionProgress: -1, onAppear: -1, onDisappear: -1, onSwipeDismiss: -1, }; public addProgressAnimation( viewTag: number, progressAnimation: ProgressAnimation ) { runOnUIImmediately(() => { 'worklet'; global.ProgressTransitionRegister.addProgressAnimation( viewTag, progressAnimation ); })(); this.registerEventHandlers(); } public removeProgressAnimation(viewTag: number) { this.unregisterEventHandlers(); runOnUIImmediately(() => { 'worklet'; global.ProgressTransitionRegister.removeProgressAnimation(viewTag); })(); } private registerEventHandlers() { this._sharedElementCount++; const eventHandler = this._eventHandler; if (!eventHandler.isRegistered) { eventHandler.isRegistered = true; const eventPrefix = Platform.OS === 'android' ? 'on' : 'top'; let lastProgressValue = -1; eventHandler.onTransitionProgress = registerEventHandler( eventPrefix + 'TransitionProgress', (event: TransitionProgressEvent) => { 'worklet'; const progress = event.progress; if (progress === lastProgressValue) { // During screen transition, handler receives two events with the same progress // value for both screens, but for modals, there is only one event. To optimize // performance and avoid unnecessary worklet calls, let's skip the second event. return; } lastProgressValue = progress; global.ProgressTransitionRegister.frame(progress); } ); eventHandler.onAppear = registerEventHandler( eventPrefix + 'Appear', () => { 'worklet'; global.ProgressTransitionRegister.onTransitionEnd(); } ); if (Platform.OS === 'android') { // onFinishTransitioning event is available only on Android and // is used to handle closing modals eventHandler.onDisappear = registerEventHandler( 'onFinishTransitioning', () => { 'worklet'; global.ProgressTransitionRegister.onAndroidFinishTransitioning(); } ); } else if (Platform.OS === 'ios') { // topDisappear event is required to handle closing modals on iOS eventHandler.onDisappear = registerEventHandler('topDisappear', () => { 'worklet'; global.ProgressTransitionRegister.onTransitionEnd(true); }); eventHandler.onSwipeDismiss = registerEventHandler( 'topGestureCancel', () => { 'worklet'; global.ProgressTransitionRegister.onTransitionEnd(); } ); } } } private unregisterEventHandlers(): void { this._sharedElementCount--; if (this._sharedElementCount === 0) { const eventHandler = this._eventHandler; eventHandler.isRegistered = false; if (eventHandler.onTransitionProgress !== -1) { unregisterEventHandler(eventHandler.onTransitionProgress); eventHandler.onTransitionProgress = -1; } if (eventHandler.onAppear !== -1) { unregisterEventHandler(eventHandler.onAppear); eventHandler.onAppear = -1; } if (eventHandler.onDisappear !== -1) { unregisterEventHandler(eventHandler.onDisappear); eventHandler.onDisappear = -1; } if (eventHandler.onSwipeDismiss !== -1) { unregisterEventHandler(eventHandler.onSwipeDismiss); eventHandler.onSwipeDismiss = -1; } } } } function createProgressTransitionRegister() { 'worklet'; const progressAnimations = new Map<number, ProgressAnimation>(); const snapshots = new Map<number, any>(); const currentTransitions = new Set<number>(); const toRemove = new Set<number>(); const progressTransitionManager = { addProgressAnimation: ( viewTag: number, progressAnimation: ProgressAnimation ) => { progressAnimations.set(viewTag, progressAnimation); }, removeProgressAnimation: (viewTag: number) => { if (progressAnimations.size > 1) { // Remove the animation config after the transition is finished toRemove.add(viewTag); } else { progressAnimations.delete(viewTag); } }, onTransitionStart: (viewTag: number, snapshot: any) => { snapshots.set(viewTag, snapshot); currentTransitions.add(viewTag); // set initial style for re-parented components progressTransitionManager.frame(0); }, frame: (progress: number) => { for (const viewTag of currentTransitions) { const progressAnimation = progressAnimations.get(viewTag); const snapshot = snapshots.get(viewTag); progressAnimation!(viewTag, snapshot, progress); } }, onAndroidFinishTransitioning: () => { if (toRemove.size > 0) { // it should be ran only on modal closing progressTransitionManager.onTransitionEnd(); } }, onTransitionEnd: (removeViews = false) => { for (const viewTag of currentTransitions) { _notifyAboutEnd(viewTag, removeViews); } currentTransitions.clear(); snapshots.clear(); if (toRemove.size > 0) { for (const viewTag of toRemove) { progressAnimations.delete(viewTag); } toRemove.clear(); } }, }; return progressTransitionManager; } runOnUIImmediately(() => { 'worklet'; global.ProgressTransitionRegister = createProgressTransitionRegister(); })(); export type ProgressTransitionRegister = ReturnType< typeof createProgressTransitionRegister >;