mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-03-29 00:38:26 +08:00
Compare commits
16 Commits
2.0.0-alph
...
2.0.0-alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1958cf37ea | ||
|
|
75fb558cd3 | ||
|
|
2b55d60780 | ||
|
|
744b37fbc3 | ||
|
|
84d684b52d | ||
|
|
6f9c504627 | ||
|
|
953763f7d9 | ||
|
|
7c351df14d | ||
|
|
da9426b4b9 | ||
|
|
4169fad8b3 | ||
|
|
4f8efd2873 | ||
|
|
cbc86bb6d8 | ||
|
|
0927e03687 | ||
|
|
efaf0cd125 | ||
|
|
21e6a9732a | ||
|
|
31192250e1 |
@@ -23,6 +23,7 @@ public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
|
||||
protected final ArrayList<T> mScreenFragments = new ArrayList<>();
|
||||
private final Set<ScreenFragment> mActiveScreenFragments = new HashSet<>();
|
||||
|
||||
private @Nullable FragmentManager mFragmentManager;
|
||||
private @Nullable FragmentTransaction mCurrentTransaction;
|
||||
private boolean mNeedUpdate;
|
||||
private boolean mIsAttached;
|
||||
@@ -108,11 +109,18 @@ public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
|
||||
return mScreenFragments.get(index).getScreen();
|
||||
}
|
||||
|
||||
protected final FragmentActivity findRootFragmentActivity() {
|
||||
private FragmentManager findFragmentManager() {
|
||||
ViewParent parent = this;
|
||||
while (!(parent instanceof ReactRootView) && parent.getParent() != null) {
|
||||
// We traverse view hierarchy up until we find screen parent or a root view
|
||||
while (!(parent instanceof ReactRootView || parent instanceof Screen) && parent.getParent() != null) {
|
||||
parent = parent.getParent();
|
||||
}
|
||||
// If parent is of type Screen it means we are inside a nested fragment structure.
|
||||
// Otherwise we expect to connect directly with root view and get root fragment manager
|
||||
if (parent instanceof Screen) {
|
||||
return ((Screen) parent).getFragment().getChildFragmentManager();
|
||||
}
|
||||
|
||||
// we expect top level view to be of type ReactRootView, this isn't really necessary but in order
|
||||
// to find root view we test if parent is null. This could potentially happen also when the view
|
||||
// is detached from the hierarchy and that test would not correctly indicate the root view. So
|
||||
@@ -131,11 +139,14 @@ public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
|
||||
throw new IllegalStateException(
|
||||
"In order to use RNScreens components your app's activity need to extend ReactFragmentActivity or ReactCompatActivity");
|
||||
}
|
||||
return (FragmentActivity) context;
|
||||
return ((FragmentActivity) context).getSupportFragmentManager();
|
||||
}
|
||||
|
||||
protected FragmentManager getFragmentManager() {
|
||||
return findRootFragmentActivity().getSupportFragmentManager();
|
||||
protected final FragmentManager getFragmentManager() {
|
||||
if (mFragmentManager == null) {
|
||||
mFragmentManager = findFragmentManager();
|
||||
}
|
||||
return mFragmentManager;
|
||||
}
|
||||
|
||||
protected FragmentTransaction getOrCreateTransaction() {
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.swmansion.rnscreens;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
@@ -30,6 +31,15 @@ public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
|
||||
}
|
||||
};
|
||||
|
||||
private final FragmentManager.FragmentLifecycleCallbacks mLifecycleCallbacks = new FragmentManager.FragmentLifecycleCallbacks() {
|
||||
@Override
|
||||
public void onFragmentResumed(FragmentManager fm, Fragment f) {
|
||||
if (mTopScreen == f) {
|
||||
setupBackHandlerIfNeeded(mTopScreen);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public ScreenStack(Context context) {
|
||||
super(context);
|
||||
}
|
||||
@@ -61,16 +71,23 @@ public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
getFragmentManager().removeOnBackStackChangedListener(mBackStackListener);
|
||||
getFragmentManager().popBackStack(BACK_STACK_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||
FragmentManager fm = getFragmentManager();
|
||||
fm.removeOnBackStackChangedListener(mBackStackListener);
|
||||
getFragmentManager().unregisterFragmentLifecycleCallbacks(mLifecycleCallbacks);
|
||||
if (!fm.isStateSaved()) {
|
||||
// state save means that the container where fragment manager was installed has been unmounted.
|
||||
// This could happen as a result of dismissing nested stack. In such a case we don't need to
|
||||
// reset back stack as it'd result in a crash caused by the fact the fragment manager is no
|
||||
// longer attached.
|
||||
fm.popBackStack(BACK_STACK_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
if (mTopScreen != null) {
|
||||
setupBackHandlerIfNeeded(mTopScreen);
|
||||
}
|
||||
getFragmentManager().registerFragmentLifecycleCallbacks(mLifecycleCallbacks, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,7 +105,11 @@ public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
|
||||
getOrCreateTransaction().remove(screen);
|
||||
}
|
||||
}
|
||||
ScreenStackFragment newTop = null;
|
||||
|
||||
// When going back from a nested stack with a single screen on it, we may hit an edge case
|
||||
// when all screens are dismissed and no screen is to be displayed on top. We need to gracefully
|
||||
// handle the case of newTop being NULL, which happens in several places below
|
||||
ScreenStackFragment newTop = null; // newTop is nullable, see the above comment ^
|
||||
ScreenStackFragment belowTop = null; // this is only set if newTop has TRANSPARENT_MODAL presentation mode
|
||||
|
||||
for (int i = mScreenFragments.size() - 1; i >= 0; i--) {
|
||||
@@ -106,7 +127,6 @@ public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (ScreenStackFragment screen : mScreenFragments) {
|
||||
// add all new views that weren't on stack before
|
||||
if (!mStack.contains(screen) && !mDismissed.contains(screen)) {
|
||||
@@ -127,7 +147,10 @@ public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
|
||||
}
|
||||
});
|
||||
}
|
||||
getOrCreateTransaction().show(newTop);
|
||||
|
||||
if (newTop != null) {
|
||||
getOrCreateTransaction().show(newTop);
|
||||
}
|
||||
|
||||
if (!mStack.contains(newTop)) {
|
||||
// if new top screen wasn't on stack we do "open animation" so long it is not the very first screen on stack
|
||||
@@ -165,7 +188,9 @@ public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
|
||||
|
||||
tryCommitTransaction();
|
||||
|
||||
setupBackHandlerIfNeeded(mTopScreen);
|
||||
if (mTopScreen != null) {
|
||||
setupBackHandlerIfNeeded(mTopScreen);
|
||||
}
|
||||
|
||||
for (ScreenStackFragment screen : mStack) {
|
||||
screen.onStackUpdate();
|
||||
@@ -192,6 +217,12 @@ public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
|
||||
* that case we want the parent navigator or activity handler to take over.
|
||||
*/
|
||||
private void setupBackHandlerIfNeeded(ScreenStackFragment topScreen) {
|
||||
if (!mTopScreen.isResumed()) {
|
||||
// if the top fragment is not in a resumed state, adding back stack transaction would throw.
|
||||
// In such a case we skip installing back handler and use FragmentLifecycleCallbacks to get
|
||||
// notified when it gets resumed so that we can install the handler.
|
||||
return;
|
||||
}
|
||||
getFragmentManager().removeOnBackStackChangedListener(mBackStackListener);
|
||||
getFragmentManager().popBackStack(BACK_STACK_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||
ScreenStackFragment firstScreen = null;
|
||||
@@ -208,6 +239,7 @@ public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
|
||||
.hide(topScreen)
|
||||
.show(topScreen)
|
||||
.addToBackStack(BACK_STACK_TAG)
|
||||
.setPrimaryNavigationFragment(topScreen)
|
||||
.commitAllowingStateLoss();
|
||||
getFragmentManager().addOnBackStackChangedListener(mBackStackListener);
|
||||
}
|
||||
|
||||
@@ -17,14 +17,15 @@ import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.facebook.react.views.text.ReactFontManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
|
||||
private final ScreenStackHeaderSubview mConfigSubviews[] = new ScreenStackHeaderSubview[3];
|
||||
private int mSubviewsCount = 0;
|
||||
private final ArrayList<ScreenStackHeaderSubview> mConfigSubviews = new ArrayList<>(3);
|
||||
private String mTitle;
|
||||
private int mTitleColor;
|
||||
private String mTitleFontFamily;
|
||||
private int mTitleFontSize;
|
||||
private float mTitleFontSize;
|
||||
private int mBackgroundColor;
|
||||
private boolean mIsHidden;
|
||||
private boolean mGestureEnabled = true;
|
||||
@@ -174,8 +175,13 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
}
|
||||
|
||||
// subviews
|
||||
for (int i = 0; i < mSubviewsCount; i++) {
|
||||
ScreenStackHeaderSubview view = mConfigSubviews[i];
|
||||
for (int i = mToolbar.getChildCount() - 1; i >= 0; i--) {
|
||||
if (mToolbar.getChildAt(i) instanceof ScreenStackHeaderSubview) {
|
||||
mToolbar.removeViewAt(i);
|
||||
}
|
||||
}
|
||||
for (int i = 0, size = mConfigSubviews.size(); i < size; i++) {
|
||||
ScreenStackHeaderSubview view = mConfigSubviews.get(i);
|
||||
ScreenStackHeaderSubview.Type type = view.getType();
|
||||
|
||||
Toolbar.LayoutParams params =
|
||||
@@ -201,9 +207,7 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
}
|
||||
|
||||
view.setLayoutParams(params);
|
||||
if (view.getParent() == null) {
|
||||
mToolbar.addView(view);
|
||||
}
|
||||
mToolbar.addView(view);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,26 +218,20 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
}
|
||||
|
||||
public ScreenStackHeaderSubview getConfigSubview(int index) {
|
||||
return mConfigSubviews[index];
|
||||
return mConfigSubviews.get(index);
|
||||
}
|
||||
|
||||
public int getConfigSubviewsCount() {
|
||||
return mSubviewsCount;
|
||||
return mConfigSubviews.size();
|
||||
}
|
||||
|
||||
public void removeConfigSubview(int index) {
|
||||
if (mConfigSubviews[index] != null) {
|
||||
mSubviewsCount--;
|
||||
}
|
||||
mConfigSubviews[index] = null;
|
||||
mConfigSubviews.remove(index);
|
||||
maybeUpdate();
|
||||
}
|
||||
|
||||
public void addConfigSubview(ScreenStackHeaderSubview child, int index) {
|
||||
if (mConfigSubviews[index] == null) {
|
||||
mSubviewsCount++;
|
||||
}
|
||||
mConfigSubviews[index] = child;
|
||||
mConfigSubviews.add(index, child);
|
||||
maybeUpdate();
|
||||
}
|
||||
|
||||
@@ -258,7 +256,7 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
mTitleFontFamily = titleFontFamily;
|
||||
}
|
||||
|
||||
public void setTitleFontSize(int titleFontSize) {
|
||||
public void setTitleFontSize(float titleFontSize) {
|
||||
mTitleFontSize = titleFontSize;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,8 +69,8 @@ public class ScreenStackHeaderConfigViewManager extends ViewGroupManager<ScreenS
|
||||
}
|
||||
|
||||
@ReactProp(name = "titleFontSize")
|
||||
public void setTitleFontSize(ScreenStackHeaderConfig config, double titleFontSizeSP) {
|
||||
config.setTitleFontSize((int) PixelUtil.toPixelFromSP(titleFontSizeSP));
|
||||
public void setTitleFontSize(ScreenStackHeaderConfig config, float titleFontSize) {
|
||||
config.setTitleFontSize(titleFontSize);
|
||||
}
|
||||
|
||||
@ReactProp(name = "titleColor", customType = "Color")
|
||||
|
||||
@@ -48,6 +48,7 @@ class StackView extends React.Component {
|
||||
headerTitleStyle,
|
||||
headerBackTitleStyle,
|
||||
headerBackTitle,
|
||||
headerBackTitleVisible,
|
||||
headerTintColor,
|
||||
gestureEnabled,
|
||||
largeTitle,
|
||||
@@ -69,7 +70,7 @@ class StackView extends React.Component {
|
||||
titleFontFamily: headerTitleStyle && headerTitleStyle.fontFamily,
|
||||
titleColor: headerTintColor,
|
||||
titleFontSize: headerTitleStyle && headerTitleStyle.fontSize,
|
||||
backTitle: headerBackTitle,
|
||||
backTitle: headerBackTitleVisible === false ? '' : headerBackTitle,
|
||||
backTitleFontFamily:
|
||||
headerBackTitleStyle && headerBackTitleStyle.fontFamily,
|
||||
backTitleFontSize: headerBackTitleStyle && headerBackTitleStyle.fontSize,
|
||||
@@ -154,20 +155,27 @@ class StackView extends React.Component {
|
||||
};
|
||||
|
||||
_renderScene = (index, route, descriptor) => {
|
||||
const { navigation, getComponent } = descriptor;
|
||||
const { navigation, getComponent, options } = descriptor;
|
||||
const { mode, transparentCard } = this.props.navigationConfig;
|
||||
const SceneComponent = getComponent();
|
||||
|
||||
let stackPresentation = 'push';
|
||||
if (mode === 'modal') {
|
||||
stackPresentation = transparentCard ? 'transparentModal' : 'modal';
|
||||
if (mode === 'modal' || mode === 'containedModal') {
|
||||
stackPresentation =
|
||||
transparentCard || options.cardTransparent ? 'transparentModal' : mode;
|
||||
}
|
||||
|
||||
let stackAnimation = undefined;
|
||||
if (options.animationEnabled === false) {
|
||||
stackAnimation = 'none';
|
||||
}
|
||||
|
||||
const { screenProps } = this.props;
|
||||
return (
|
||||
<Screen
|
||||
key={`screen_${route.key}`}
|
||||
style={StyleSheet.absoluteFill}
|
||||
style={options.cardStyle}
|
||||
stackAnimation={stackAnimation}
|
||||
stackPresentation={stackPresentation}
|
||||
onDismissed={() => this._removeScene(route)}>
|
||||
{this._renderHeaderConfig(index, route, descriptor)}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#import <React/RCTUIManagerObserverCoordinator.h>
|
||||
#import "RNSScreenContainer.h"
|
||||
|
||||
@interface RNSScreenStackView : UIView <RNSScreenContainerDelegate>
|
||||
@interface RNSScreenStackView : UIView <RNSScreenContainerDelegate, RCTInvalidating>
|
||||
|
||||
- (void)markChildUpdated;
|
||||
- (void)didUpdateChildren;
|
||||
|
||||
@@ -143,31 +143,48 @@
|
||||
NSMutableArray<UIViewController *> *controllersToRemove = [NSMutableArray arrayWithArray:_presentedModals];
|
||||
[controllersToRemove removeObjectsInArray:controllers];
|
||||
|
||||
// presenting new controllers
|
||||
for (UIViewController *newController in newControllers) {
|
||||
[_presentedModals addObject:newController];
|
||||
if (_controller.presentedViewController != nil) {
|
||||
[_controller.presentedViewController presentViewController:newController animated:YES completion:nil];
|
||||
// find bottom-most controller that should stay on the stack for the duration of transition
|
||||
NSUInteger changeRootIndex = 0;
|
||||
UIViewController *changeRootController = _controller;
|
||||
for (NSUInteger i = 0; i < MIN(_presentedModals.count, controllers.count); i++) {
|
||||
if (_presentedModals[i] == controllers[i]) {
|
||||
changeRootController = controllers[i];
|
||||
changeRootIndex = i + 1;
|
||||
} else {
|
||||
[_controller presentViewController:newController animated:YES completion:nil];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// hiding old controllers
|
||||
for (UIViewController *controller in [controllersToRemove reverseObjectEnumerator]) {
|
||||
[_presentedModals removeObject:controller];
|
||||
if (controller.presentedViewController != nil) {
|
||||
UIViewController *restore = controller.presentedViewController;
|
||||
UIViewController *parent = controller.presentingViewController;
|
||||
[controller dismissViewControllerAnimated:NO completion:^{
|
||||
[parent dismissViewControllerAnimated:NO completion:^{
|
||||
[parent presentViewController:restore animated:NO completion:nil];
|
||||
}];
|
||||
}];
|
||||
} else {
|
||||
[controller.presentingViewController dismissViewControllerAnimated:YES completion:nil];
|
||||
// we verify that controllers added on top of changeRootIndex are all new. Unfortunately modal
|
||||
// VCs cannot be reshuffled (there are some visual glitches when we try to dismiss then show as
|
||||
// even non-animated dismissal has delay and updates the screen several times)
|
||||
for (NSUInteger i = changeRootIndex; i < controllers.count; i++) {
|
||||
if ([_presentedModals containsObject:controllers[i]]) {
|
||||
RCTAssert(false, @"Modally presented controllers are being reshuffled, this is not allowed");
|
||||
}
|
||||
}
|
||||
|
||||
void (^finish)(void) = ^{
|
||||
UIViewController *previous = changeRootController;
|
||||
for (NSUInteger i = changeRootIndex; i < controllers.count; i++) {
|
||||
UIViewController *next = controllers[i];
|
||||
[previous presentViewController:next
|
||||
animated:(i == controllers.count - 1)
|
||||
completion:nil];
|
||||
previous = next;
|
||||
}
|
||||
|
||||
[self->_presentedModals removeAllObjects];
|
||||
[self->_presentedModals addObjectsFromArray:controllers];
|
||||
};
|
||||
|
||||
if (changeRootController.presentedViewController) {
|
||||
[changeRootController
|
||||
dismissViewControllerAnimated:(changeRootIndex == controllers.count)
|
||||
completion:finish];
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setPushViewControllers:(NSArray<UIViewController *> *)controllers
|
||||
@@ -244,12 +261,18 @@
|
||||
_controller.view.frame = self.bounds;
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
for (UIViewController *controller in _presentedModals) {
|
||||
[controller dismissViewControllerAnimated:NO completion:nil];
|
||||
}
|
||||
[_presentedModals removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)dismissOnReload
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
for (UIViewController *controller in self->_presentedModals) {
|
||||
[controller dismissViewControllerAnimated:NO completion:nil];
|
||||
}
|
||||
[self invalidate];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -295,6 +295,9 @@
|
||||
navitem.scrollEdgeAppearance = appearance;
|
||||
}
|
||||
#endif
|
||||
navitem.leftBarButtonItem = nil;
|
||||
navitem.rightBarButtonItem = nil;
|
||||
navitem.titleView = nil;
|
||||
for (RNSScreenStackHeaderSubview *subview in config.reactSubviews) {
|
||||
switch (subview.type) {
|
||||
case RNSScreenStackHeaderSubviewTypeLeft: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-native-screens",
|
||||
"version": "2.0.0-alpha.12",
|
||||
"version": "2.0.0-alpha.17",
|
||||
"description": "First incomplete navigation solution for your react-native app.",
|
||||
"scripts": {
|
||||
"start": "node node_modules/react-native/local-cli/cli.js start",
|
||||
|
||||
Reference in New Issue
Block a user