diff --git a/NavigationReactNative/src/NavigationStack.tsx b/NavigationReactNative/src/NavigationStack.tsx index 4745c5bae1..420a7ad743 100644 --- a/NavigationReactNative/src/NavigationStack.tsx +++ b/NavigationReactNative/src/NavigationStack.tsx @@ -4,11 +4,12 @@ import { StateNavigator, Crumb, State } from 'navigation'; import { NavigationContext } from 'navigation-react'; import PopSync from './PopSync'; import Scene from './Scene'; -type NavigationStackProps = {underlayColor: string, title: (state: State, data: any) => string, crumbStyle: any, unmountStyle: any, hidesTabBar: any, sharedElement: any, backgroundColor: any, stackInvalidatedLink: string, renderScene: (state: State, data: any) => ReactNode, children: any}; +type NavigationStackProps = {underlayColor: string, title: (state: State, data: any) => string, crumbStyle: any, unmountStyle: any, hidesTabBar: any, sharedElement: any, sharedElements: any, backgroundColor: any, stackInvalidatedLink: string, renderScene: (state: State, data: any) => ReactNode, children: any}; type NavigationStackState = {stateNavigator: StateNavigator, keys: string[], rest: boolean, counter: number, mostRecentEventCount: number}; const NavigationStack = ({underlayColor = '#000', title, crumbStyle: crumbStyleStack = () => null, unmountStyle: unmountStyleStack = () => null, - hidesTabBar: hidesTabBarStack = () => false, sharedElement: getSharedElementStack = () => null, backgroundColor: backgroundColorStack = () => null, + hidesTabBar: hidesTabBarStack = () => false, sharedElement: getSharedElementStack = () => null, sharedElements: getSharedElementsStack = () => null, + backgroundColor: backgroundColorStack = () => null, stackInvalidatedLink, renderScene, children}: NavigationStackProps) => { const resumeNavigationRef = useRef(null); const ref = useRef(null); @@ -74,32 +75,35 @@ const NavigationStack = ({underlayColor = '#000', title, crumbStyle: crumbStyleS const crumbStyle = (from, state, ...rest) => sceneProps(state)?.crumbStyle ? sceneProps(state)?.crumbStyle(from, ...rest) : crumbStyleStack(from, state, ...rest); const hidesTabBar = (state, ...rest) => sceneProps(state)?.hidesTabBar ? returnOrCall(sceneProps(state)?.hidesTabBar, ...rest) : hidesTabBarStack(state, ...rest); const getSharedElement = (state, ...rest) => sceneProps(state)?.sharedElement ? returnOrCall(sceneProps(state)?.sharedElement, ...rest) : getSharedElementStack(state, ...rest); + const getSharedElements = (state, ...rest) => sceneProps(state)?.sharedElements ? returnOrCall(sceneProps(state)?.sharedElements, ...rest) : getSharedElementsStack(state, ...rest); const backgroundColor = (state, ...rest) => sceneProps(state)?.backgroundColor ? returnOrCall(sceneProps(state)?.backgroundColor, ...rest) : backgroundColorStack(state, ...rest); const getAnimation = () => { let {state, data, oldState, oldData, oldUrl, crumbs, nextCrumb} = stateNavigator.stateContext; if (!oldState) return null; const {crumbs: oldCrumbs} = stateNavigator.parseLink(oldUrl); - let enterAnim, exitAnim, sharedElement, oldSharedElement; + let enterAnim, exitAnim, sharedElements; if (oldCrumbs.length < crumbs.length) { const {state: nextState, data: nextData} = crumbs.concat(nextCrumb)[oldCrumbs.length + 1]; enterAnim = unmountStyle(true, state, data, crumbs); exitAnim = crumbStyle(false, oldState, oldData, oldCrumbs, nextState, nextData); - sharedElement = getSharedElement(state, data, crumbs); + sharedElements = getSharedElement(state, data, crumbs) || getSharedElements(state, data, crumbs); } if (crumbs.length < oldCrumbs.length) { nextCrumb = new Crumb(oldData, oldState, null, null, false); const {state: nextState, data: nextData} = oldCrumbs.concat(nextCrumb)[crumbs.length + 1]; enterAnim = crumbStyle(true, state, data, crumbs, nextState, nextData); exitAnim = unmountStyle(false, oldState, oldData, oldCrumbs); - oldSharedElement = getSharedElement(oldState, oldData, oldCrumbs); + sharedElements = getSharedElement(oldState, oldData, oldCrumbs) || getSharedElements(oldState, oldData, oldCrumbs); } if (crumbs.length === oldCrumbs.length) { enterAnim = unmountStyle(true, state, data, crumbs); exitAnim = unmountStyle(false, oldState, oldData, oldCrumbs, state, data); } - var enterAnimOff = enterAnim === ''; - return {enterAnim, exitAnim, enterAnimOff, sharedElement, oldSharedElement}; + const containerTransform = typeof sharedElements === 'string'; + sharedElements = containerTransform && sharedElements ? [sharedElements] : sharedElements; + const enterAnimOff = enterAnim === ''; + return {enterAnim, exitAnim, enterAnimOff, sharedElements, containerTransform}; } const {stateNavigator: prevStateNavigator, keys, rest, mostRecentEventCount} = stackState; if (prevStateNavigator !== stateNavigator && stateNavigator.stateContext.state) { diff --git a/NavigationReactNative/src/NavigationStackNativeComponent.js b/NavigationReactNative/src/NavigationStackNativeComponent.js index ad70cd5718..eabe81294e 100644 --- a/NavigationReactNative/src/NavigationStackNativeComponent.js +++ b/NavigationReactNative/src/NavigationStackNativeComponent.js @@ -10,8 +10,8 @@ type NativeProps = $ReadOnly<{| enterAnim: string, exitAnim: string, enterAnimOff: boolean, - sharedElement: string, - oldSharedElement: string, + sharedElements: $ReadOnlyArray, + containerTransform: boolean, mostRecentEventCount: Int32, onNavigateToTop: DirectEventHandler, onWillNavigateBack: DirectEventHandler<$ReadOnly<{| diff --git a/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/NavigationStackManager.java b/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/NavigationStackManager.java index f813b1a264..4c36d78869 100644 --- a/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/NavigationStackManager.java +++ b/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/NavigationStackManager.java @@ -37,14 +37,14 @@ public void setExitAnim(NavigationStackView view, String exitAnim) { view.exitAnim = exitAnim; } - @ReactProp(name = "sharedElement") - public void setSharedElement(NavigationStackView view, String sharedElement) { - view.sharedElementName = sharedElement; + @ReactProp(name = "sharedElements") + public void setSharedElements(NavigationStackView view, ReadableArray sharedElements) { + view.sharedElementNames = sharedElements; } - @ReactProp(name = "oldSharedElement") - public void setOldSharedElement(NavigationStackView view, String oldSharedElement) { - view.oldSharedElementName = oldSharedElement; + @ReactProp(name = "containerTransform") + public void setContainerTransform(NavigationStackView view, boolean containerTransform) { + view.containerTransform = containerTransform; } @Nonnull diff --git a/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/NavigationStackView.java b/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/NavigationStackView.java index ecd9bdd7cc..375a0f2d67 100644 --- a/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/NavigationStackView.java +++ b/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/NavigationStackView.java @@ -35,6 +35,7 @@ import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; @@ -49,9 +50,9 @@ public class NavigationStackView extends ViewGroup implements LifecycleEventList private Activity mainActivity; protected String enterAnim; protected String exitAnim; - protected String sharedElementName; - protected String oldSharedElementName; + protected ReadableArray sharedElementNames; protected Boolean startNavigation = null; + protected boolean containerTransform = false; public NavigationStackView(Context context) { super(context); @@ -99,10 +100,10 @@ protected void onAfterUpdateTransaction() { if (crumb < currentCrumb) { FragmentManager fragmentManager = fragment.getChildFragmentManager(); SceneFragment fragment = (SceneFragment) fragmentManager.findFragmentByTag(oldKey); - Pair sharedElement = fragment != null ? getOldSharedElement(currentCrumb, crumb, fragment) : null; + Pair[] sharedElements = fragment != null ? getOldSharedElements(currentCrumb, crumb, fragment) : null; SceneFragment prevFragment = (SceneFragment) fragmentManager.findFragmentByTag(keys.getString(crumb)); - if (sharedElement != null && prevFragment != null && prevFragment.getScene() != null) - prevFragment.getScene().sharedElementMotion = new SharedElementMotion(fragment, prevFragment, oldSharedElementName); + if (sharedElements != null && prevFragment != null && prevFragment.getScene() != null) + prevFragment.getScene().sharedElementMotion = new SharedElementMotion(fragment, prevFragment, getSharedElementSet(sharedElementNames), containerTransform); fragmentManager.popBackStack(String.valueOf(crumb), 0); } if (crumb > currentCrumb) { @@ -120,18 +121,21 @@ protected void onAfterUpdateTransaction() { int popExit = getAnimationResourceId(currentActivity, scene.exitAnim, android.R.attr.activityCloseExitAnimation); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.setReorderingAllowed(true); - Pair sharedElement = null; + Pair[] sharedElements = null; if (nextCrumb > 0) { String prevKey = keys.getString(nextCrumb - 1); SceneFragment prevFragment = (SceneFragment) fragmentManager.findFragmentByTag(prevKey); if (prevFragment != null) - sharedElement = getSharedElement(currentCrumb, crumb, prevFragment); + sharedElements = getSharedElements(currentCrumb, crumb, prevFragment); } - if (sharedElement != null) { - fragmentTransaction.addSharedElement(sharedElement.first, sharedElement.second); + if (sharedElements != null) { + for(Pair sharedElement : sharedElements) { + SharedElementView sharedEl = (SharedElementView) sharedElement.first; + fragmentTransaction.addSharedElement(containerTransform ? sharedEl : sharedEl.getChildAt(0), (containerTransform ? "" : "element__") + sharedElement.second); + } } - fragmentTransaction.setCustomAnimations(oldCrumb != -1 ? enter : 0, exit, popEnter, popExit); - SceneFragment fragment = new SceneFragment(scene, sharedElementName); + fragmentTransaction.setCustomAnimations(oldCrumb != -1 && sharedElements == null ? enter : 0, exit, sharedElements == null ? popEnter : 0, popExit); + SceneFragment fragment = new SceneFragment(scene, getSharedElementSet(sharedElementNames), containerTransform); fragmentTransaction.replace(getId(), fragment, key); fragmentTransaction.addToBackStack(String.valueOf(nextCrumb)); fragmentTransaction.commit(); @@ -149,7 +153,7 @@ protected void onAfterUpdateTransaction() { FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.setReorderingAllowed(true); fragmentTransaction.setCustomAnimations(enter, exit, popEnter, popExit); - fragmentTransaction.replace(getId(), new SceneFragment(scene, null), key); + fragmentTransaction.replace(getId(), new SceneFragment(scene, null, containerTransform), key); fragmentTransaction.addToBackStack(String.valueOf(crumb)); fragmentTransaction.commit(); } @@ -166,6 +170,16 @@ int getAnimationResourceId(Context context, String animationName, int defaultId) return context.getResources().getIdentifier(animationName, "anim", packageName); } + HashSet getSharedElementSet(ReadableArray sharedElementNames) { + if (sharedElementNames == null) + return null; + HashSet sharedElementSet = new HashSet<>(); + for(int i = 0; i < sharedElementNames.size(); i++) { + sharedElementSet.add(sharedElementNames.getString(i)); + } + return sharedElementSet; + } + HashMap getSharedElementMap(SceneView scene) { HashMap sharedElementMap = new HashMap<>(); for(SharedElementView sharedElement : scene.sharedElements) { @@ -174,45 +188,58 @@ HashMap getSharedElementMap(SceneView scene) { return sharedElementMap; } - Pair getSharedElement(HashMap sharedElementMap, String sharedElementName) { - if (sharedElementMap == null || sharedElementName == null) + Pair[] getSharedElements(HashMap sharedElementMap, ReadableArray sharedElementNames) { + if (sharedElementMap == null || sharedElementNames == null) return null; - if (sharedElementMap.containsKey(sharedElementName)) - return Pair.create(sharedElementMap.get(sharedElementName), sharedElementName); - return null; + ArrayList sharedElementPairs = new ArrayList<>(); + for(int i = 0; i < sharedElementNames.size(); i++) { + String name = sharedElementNames.getString(i); + if (sharedElementMap.containsKey(name)) + sharedElementPairs.add(Pair.create(sharedElementMap.get(name), name)); + } + return sharedElementPairs.toArray(new Pair[0]); } - private Pair getOldSharedElement(int currentCrumb, int crumb, SceneFragment sceneFragment) { + private Pair[] getOldSharedElements(int currentCrumb, int crumb, SceneFragment sceneFragment) { final HashMap oldSharedElementsMap = getSharedElementMap(sceneFragment.getScene()); - final Pair oldSharedElement = currentCrumb - crumb == 1 ? getSharedElement(oldSharedElementsMap, oldSharedElementName) : null; - if (oldSharedElement != null) { + final Pair[] oldSharedElements = currentCrumb - crumb == 1 ? getSharedElements(oldSharedElementsMap, sharedElementNames) : null; + if (oldSharedElements != null && oldSharedElements.length != 0) { sceneFragment.setEnterSharedElementCallback(new SharedElementCallback() { @Override public void onMapSharedElements(List names, Map elements) { - if (oldSharedElementsMap.containsKey(oldSharedElementName)) { - View oldSharedElement = oldSharedElementsMap.get(oldSharedElementName); - elements.put(names.get(0), oldSharedElement); + for(int i = 0; i < sharedElementNames.size(); i++) { + String name = sharedElementNames.getString(i); + if (oldSharedElementsMap.containsKey(name)) { + SharedElementView oldSharedElement = oldSharedElementsMap.get(name); + elements.put(names.get(i), containerTransform ? oldSharedElement : oldSharedElement.getChildAt(0)); + } } } }); - return oldSharedElement; + return oldSharedElements; } return null; } - private Pair getSharedElement(int currentCrumb, int crumb, SceneFragment sceneFragment) { + private Pair[] getSharedElements(int currentCrumb, int crumb, SceneFragment sceneFragment) { final HashMap sharedElementsMap = getSharedElementMap(sceneFragment.getScene()); - final Pair sharedElement = crumb - currentCrumb == 1 ? getSharedElement(sharedElementsMap, sharedElementName) : null; - if (sharedElement != null) { + final Pair[] sharedElements = crumb - currentCrumb == 1 ? getSharedElements(sharedElementsMap, sharedElementNames) : null; + if (sharedElements != null && sharedElements.length != 0) { sceneFragment.setExitSharedElementCallback(new SharedElementCallback() { @Override public void onMapSharedElements(List names, Map elements) { - String mappedName = oldSharedElementName != null ? oldSharedElementName : names.get(0); - if (sharedElementsMap.containsKey(mappedName)) - elements.put(names.get(0), sharedElementsMap.get(mappedName)); + for(int i = 0; i < names.size(); i++) { + String mappedName = names.get(i); + if (sharedElementNames != null && sharedElementNames.size() > i) + mappedName = sharedElementNames.getString(i); + if (sharedElementsMap.containsKey(mappedName)) { + SharedElementView sharedElement = sharedElementsMap.get(mappedName); + elements.put(names.get(i), containerTransform ? sharedElement : sharedElement.getChildAt(0)); + } + } } }); - return sharedElement; + return sharedElements; } return null; } diff --git a/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/NavigationStackViewManager.java b/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/NavigationStackViewManager.java index 7c5572a980..54b57ec11f 100644 --- a/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/NavigationStackViewManager.java +++ b/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/NavigationStackViewManager.java @@ -55,14 +55,14 @@ public void setExitAnim(NavigationStackView view, String exitAnim) { view.exitAnim = exitAnim; } - @ReactProp(name = "sharedElement") - public void setSharedElement(NavigationStackView view, String sharedElement) { - view.sharedElementName = sharedElement; + @ReactProp(name = "sharedElements") + public void setSharedElements(NavigationStackView view, ReadableArray sharedElements) { + view.sharedElementNames = sharedElements; } - @ReactProp(name = "oldSharedElement") - public void setOldSharedElement(NavigationStackView view, String oldSharedElement) { - view.oldSharedElementName = oldSharedElement; + @ReactProp(name = "containerTransform") + public void setContainerTransform(NavigationStackView view, boolean containerTransform) { + view.containerTransform = containerTransform; } @ReactProp(name = "mostRecentEventCount") diff --git a/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/SceneFragment.java b/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/SceneFragment.java index 4aa5f7571e..7df1d21360 100644 --- a/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/SceneFragment.java +++ b/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/SceneFragment.java @@ -12,6 +12,9 @@ import androidx.fragment.app.Fragment; import androidx.lifecycle.Lifecycle; +import java.util.HashSet; +import java.util.concurrent.TimeUnit; + public class SceneFragment extends Fragment { private SceneView scene; @@ -19,11 +22,11 @@ public SceneFragment() { super(); } - SceneFragment(SceneView scene, String sharedElement) { + SceneFragment(SceneView scene, HashSet sharedElements, boolean containerTransform) { super(); this.scene = scene; - if (sharedElement != null ) - scene.sharedElementMotion = new SharedElementMotion(this, this, sharedElement); + if (sharedElements != null ) + scene.sharedElementMotion = new SharedElementMotion(this, this, sharedElements, containerTransform); } @Nullable @@ -33,7 +36,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c if (scene.getParent() != null) ((ViewGroup) scene.getParent()).endViewTransition(scene); if (scene.sharedElementMotion != null) - postponeEnterTransition(); + postponeEnterTransition(300, TimeUnit.MILLISECONDS); return scene; } return new View(getContext()); diff --git a/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/SharedElementManager.java b/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/SharedElementManager.java index bdc01b2596..7cd30a3f22 100644 --- a/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/SharedElementManager.java +++ b/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/SharedElementManager.java @@ -24,19 +24,21 @@ protected SharedElementView createViewInstance(@Nonnull ThemedReactContext react @ReactProp(name = "name") public void setName(SharedElementView view, String name) { view.setTransitionName(name); + if (view.getChildCount() > 0) + view.getChildAt(0).setTransitionName("element__" + name); } @ReactProp(name = "duration", defaultInt = -1) public void setDuration(SharedElementView view, int duration) { - view.transition.setDuration(duration != -1 ? duration : view.defaultDuration); + view.duration = duration; } @ReactProp(name = "fadeMode") public void setFadeMode(SharedElementView view, String fadeMode) { - if (fadeMode == null) view.transition.setFadeMode(view.defaultFadeMode); - if (("in").equals(fadeMode)) view.transition.setFadeMode(MaterialContainerTransform.FADE_MODE_IN); - if (("out").equals(fadeMode)) view.transition.setFadeMode(MaterialContainerTransform.FADE_MODE_OUT); - if (("cross").equals(fadeMode)) view.transition.setFadeMode(MaterialContainerTransform.FADE_MODE_CROSS); - if (("through").equals(fadeMode)) view.transition.setFadeMode(MaterialContainerTransform.FADE_MODE_THROUGH); + if (fadeMode == null) view.fadeMode = MaterialContainerTransform.FADE_MODE_IN; + if (("in").equals(fadeMode)) view.fadeMode = MaterialContainerTransform.FADE_MODE_IN; + if (("out").equals(fadeMode)) view.fadeMode = MaterialContainerTransform.FADE_MODE_OUT; + if (("cross").equals(fadeMode)) view.fadeMode = MaterialContainerTransform.FADE_MODE_CROSS; + if (("through").equals(fadeMode)) view.fadeMode = MaterialContainerTransform.FADE_MODE_THROUGH; } } diff --git a/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/SharedElementMotion.java b/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/SharedElementMotion.java index 0d2117883c..f5c62dc0c7 100644 --- a/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/SharedElementMotion.java +++ b/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/SharedElementMotion.java @@ -2,29 +2,36 @@ import android.graphics.Color; +import androidx.transition.Transition; + import com.google.android.material.transition.MaterialContainerTransform; +import java.util.HashSet; + class SharedElementMotion { private final SceneFragment enterScene; private final SceneFragment scene; - private final String sharedElement; + private HashSet sharedElements; + private HashSet loadedSharedElements = new HashSet<>(); + private boolean containerTransform = false; - SharedElementMotion(SceneFragment enterScene, SceneFragment scene, String sharedElement) { - this.sharedElement = sharedElement; + SharedElementMotion(SceneFragment enterScene, SceneFragment scene, HashSet sharedElements, boolean containerTransform) { + this.sharedElements = sharedElements; this.enterScene = enterScene; this.scene = scene; + this.containerTransform = containerTransform; } void load(SharedElementView sharedElementView) { - if (sharedElement.equals(sharedElementView.getTransitionName())) { - MaterialContainerTransform transition = sharedElementView.transition; - transition.setTransitionDirection(enterScene == scene ? MaterialContainerTransform.TRANSITION_DIRECTION_ENTER : MaterialContainerTransform.TRANSITION_DIRECTION_RETURN); - transition.addTarget(sharedElementView.getTransitionName()); - transition.setScrimColor(Color.TRANSPARENT); - enterScene.setSharedElementEnterTransition(transition); - enterScene.setSharedElementReturnTransition(transition); - scene.startPostponedEnterTransition(); - scene.getScene().sharedElementMotion = null; + if (sharedElements.contains(sharedElementView.getTransitionName()) && !loadedSharedElements.contains(sharedElementView.getTransitionName())) { + loadedSharedElements.add(sharedElementView.getTransitionName()); + if(sharedElements.size() == loadedSharedElements.size()) { + Transition transition = sharedElementView.getTransition(containerTransform, enterScene == scene); + enterScene.setSharedElementEnterTransition(transition); + enterScene.setSharedElementReturnTransition(transition); + scene.startPostponedEnterTransition(); + scene.getScene().sharedElementMotion = null; + } } } } diff --git a/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/SharedElementView.java b/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/SharedElementView.java index 3332027bd7..3c3771ec8f 100644 --- a/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/SharedElementView.java +++ b/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/SharedElementView.java @@ -1,22 +1,49 @@ package com.navigation.reactnative; import android.content.Context; +import android.graphics.Color; +import android.os.Build; +import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.view.ViewTreeObserver; +import androidx.transition.ChangeBounds; +import androidx.transition.ChangeClipBounds; +import androidx.transition.ChangeImageTransform; +import androidx.transition.ChangeTransform; +import androidx.transition.Transition; +import androidx.transition.TransitionSet; + import com.google.android.material.transition.MaterialContainerTransform; public class SharedElementView extends ViewGroup { - final MaterialContainerTransform transition; - final long defaultDuration; - final int defaultFadeMode; + long duration = -1; + int fadeMode = MaterialContainerTransform.FADE_MODE_IN; public SharedElementView(Context context) { super(context); - transition = new MaterialContainerTransform(context, false); - defaultDuration = transition.getDuration(); - defaultFadeMode = transition.getFadeMode(); + } + + @Override + public void addView(View child, int index) { + if (index == 0 && getChildAt(0) != null) getChildAt(0).setTransitionName(null); + super.addView(child, index); + child.setTransitionName(index == 0 ? "element__" + this.getTransitionName() : null); + } + + @Override + public void removeViewAt(int index) { + super.removeViewAt(index); + if (getChildAt(0) != null) getChildAt(0).setTransitionName("element__" + this.getTransitionName()); + } + + @Override + public void endViewTransition(View view) { + super.endViewTransition(view); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + view.setTransitionAlpha(1); + } } @Override @@ -36,6 +63,24 @@ public boolean onPreDraw() { }); } + Transition getTransition(boolean containerTransform, boolean enter) { + if (!containerTransform) { + TransitionSet transition = new TransitionSet(); + transition.addTransition(new ChangeBounds()); + transition.addTransition(new ChangeTransform()); + transition.addTransition(new ChangeClipBounds()); + transition.addTransition(new ChangeImageTransform()); + return transition; + } else { + MaterialContainerTransform transition = new MaterialContainerTransform(); + if (duration != -1) transition.setDuration(duration); + transition.setFadeMode(fadeMode); + transition.setTransitionDirection(enter ? MaterialContainerTransform.TRANSITION_DIRECTION_ENTER : MaterialContainerTransform.TRANSITION_DIRECTION_RETURN); + transition.setScrimColor(Color.TRANSPARENT); + return transition; + } + } + @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); diff --git a/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/SharedElementViewManager.java b/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/SharedElementViewManager.java index 81eadbed09..c739e3e552 100644 --- a/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/SharedElementViewManager.java +++ b/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/SharedElementViewManager.java @@ -40,19 +40,21 @@ protected SharedElementView createViewInstance(@Nonnull ThemedReactContext react @ReactProp(name = "name") public void setName(SharedElementView view, String name) { view.setTransitionName(name); + if (view.getChildCount() > 0) + view.getChildAt(0).setTransitionName("element__" + name); } @ReactProp(name = "duration") public void setDuration(SharedElementView view, int duration) { - view.transition.setDuration(duration != -1 ? duration : view.defaultDuration); + view.duration = duration; } @ReactProp(name = "fadeMode") public void setFadeMode(SharedElementView view, String fadeMode) { - if (fadeMode == null) view.transition.setFadeMode(view.defaultFadeMode); - if (("in").equals(fadeMode)) view.transition.setFadeMode(MaterialContainerTransform.FADE_MODE_IN); - if (("out").equals(fadeMode)) view.transition.setFadeMode(MaterialContainerTransform.FADE_MODE_OUT); - if (("cross").equals(fadeMode)) view.transition.setFadeMode(MaterialContainerTransform.FADE_MODE_CROSS); - if (("through").equals(fadeMode)) view.transition.setFadeMode(MaterialContainerTransform.FADE_MODE_THROUGH); + if (fadeMode == null) view.fadeMode = MaterialContainerTransform.FADE_MODE_IN; + if (("in").equals(fadeMode)) view.fadeMode = MaterialContainerTransform.FADE_MODE_IN; + if (("out").equals(fadeMode)) view.fadeMode = MaterialContainerTransform.FADE_MODE_OUT; + if (("cross").equals(fadeMode)) view.fadeMode = MaterialContainerTransform.FADE_MODE_CROSS; + if (("through").equals(fadeMode)) view.fadeMode = MaterialContainerTransform.FADE_MODE_THROUGH; } } diff --git a/build/npm/navigation-react-native/navigation.react.native.d.ts b/build/npm/navigation-react-native/navigation.react.native.d.ts index 3bf945f319..590f461b79 100644 --- a/build/npm/navigation-react-native/navigation.react.native.d.ts +++ b/build/npm/navigation-react-native/navigation.react.native.d.ts @@ -38,6 +38,10 @@ export interface NavigationStackProps { * The Scene's shared element */ sharedElement?: (state: State, data: any, crumbs: Crumb[]) => string; + /** + * The Scene's shared elements + */ + sharedElements?: (state: State, data: any, crumbs: Crumb[]) => string | string[]; /** * The color of the Scene's background */ @@ -530,7 +534,7 @@ export class StatusBar extends Component {} */ export interface SharedElementProps { /** - * The name shared across Scenes by the two views + * The name shared across Scenes by the two elements */ name: string; /** @@ -538,7 +542,7 @@ export interface SharedElementProps { */ duration?: number; /** - * The fade mode used to swap the content of the two views + * The fade mode used to swap the content of the two elements */ fadeMode?: 'in' | 'out' | 'cross' | 'through'; /** diff --git a/types/navigation-react-native.d.ts b/types/navigation-react-native.d.ts index 3bf945f319..590f461b79 100644 --- a/types/navigation-react-native.d.ts +++ b/types/navigation-react-native.d.ts @@ -38,6 +38,10 @@ export interface NavigationStackProps { * The Scene's shared element */ sharedElement?: (state: State, data: any, crumbs: Crumb[]) => string; + /** + * The Scene's shared elements + */ + sharedElements?: (state: State, data: any, crumbs: Crumb[]) => string | string[]; /** * The color of the Scene's background */ @@ -530,7 +534,7 @@ export class StatusBar extends Component {} */ export interface SharedElementProps { /** - * The name shared across Scenes by the two views + * The name shared across Scenes by the two elements */ name: string; /** @@ -538,7 +542,7 @@ export interface SharedElementProps { */ duration?: number; /** - * The fade mode used to swap the content of the two views + * The fade mode used to swap the content of the two elements */ fadeMode?: 'in' | 'out' | 'cross' | 'through'; /**