mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-04-24 12:35:39 +08:00
Compare commits
20 Commits
2.0.0-alph
...
2.0.0-alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f9c504627 | ||
|
|
953763f7d9 | ||
|
|
7c351df14d | ||
|
|
da9426b4b9 | ||
|
|
4169fad8b3 | ||
|
|
4f8efd2873 | ||
|
|
cbc86bb6d8 | ||
|
|
0927e03687 | ||
|
|
efaf0cd125 | ||
|
|
21e6a9732a | ||
|
|
31192250e1 | ||
|
|
124e8acb2d | ||
|
|
2c5f95cea6 | ||
|
|
0a2336d005 | ||
|
|
58d1791d4a | ||
|
|
3d56c5d4e2 | ||
|
|
47658d4d7d | ||
|
|
258ae419de | ||
|
|
62123f16f9 | ||
|
|
a94424192b |
@@ -140,6 +140,7 @@ A callback that gets called when the current screen is dismissed by hardware bac
|
||||
Allows for the customization of how the given screen should appear/dissapear when pushed or popped at the top of the stack. The followin values are currently supported:
|
||||
- `"default"` – uses a platform default animation
|
||||
- `"fade"` – fades screen in or out
|
||||
- `"flip"` – flips the screen, requires `stackPresentation: "modal"` (iOS only)
|
||||
- `"none"` – the screen appears/dissapears without an animation
|
||||
|
||||
#### `stackPresentation`
|
||||
|
||||
@@ -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,7 +71,23 @@ public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
getFragmentManager().removeOnBackStackChangedListener(mBackStackListener);
|
||||
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();
|
||||
getFragmentManager().registerFragmentLifecycleCallbacks(mLifecycleCallbacks, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -79,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--) {
|
||||
@@ -97,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)) {
|
||||
@@ -118,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
|
||||
@@ -156,7 +188,9 @@ public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
|
||||
|
||||
tryCommitTransaction();
|
||||
|
||||
setupBackHandlerIfNeeded(mTopScreen);
|
||||
if (mTopScreen != null) {
|
||||
setupBackHandlerIfNeeded(mTopScreen);
|
||||
}
|
||||
|
||||
for (ScreenStackFragment screen : mStack) {
|
||||
screen.onStackUpdate();
|
||||
@@ -183,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;
|
||||
@@ -194,13 +234,14 @@ public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
|
||||
}
|
||||
}
|
||||
if (topScreen != firstScreen && topScreen.isDismissable()) {
|
||||
getFragmentManager().addOnBackStackChangedListener(mBackStackListener);
|
||||
getFragmentManager()
|
||||
.beginTransaction()
|
||||
.hide(topScreen)
|
||||
.show(topScreen)
|
||||
.addToBackStack(BACK_STACK_TAG)
|
||||
.commit();
|
||||
.setPrimaryNavigationFragment(topScreen)
|
||||
.commitAllowingStateLoss();
|
||||
getFragmentManager().addOnBackStackChangedListener(mBackStackListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
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;
|
||||
@@ -207,6 +207,12 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeUpdate() {
|
||||
if (getParent() != null) {
|
||||
onUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public ScreenStackHeaderSubview getConfigSubview(int index) {
|
||||
return mConfigSubviews[index];
|
||||
}
|
||||
@@ -220,6 +226,7 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
mSubviewsCount--;
|
||||
}
|
||||
mConfigSubviews[index] = null;
|
||||
maybeUpdate();
|
||||
}
|
||||
|
||||
public void addConfigSubview(ScreenStackHeaderSubview child, int index) {
|
||||
@@ -227,6 +234,7 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
mSubviewsCount++;
|
||||
}
|
||||
mConfigSubviews[index] = child;
|
||||
maybeUpdate();
|
||||
}
|
||||
|
||||
private TextView getTitleTextView() {
|
||||
@@ -250,7 +258,7 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
mTitleFontFamily = titleFontFamily;
|
||||
}
|
||||
|
||||
public void setTitleFontSize(int titleFontSize) {
|
||||
public void setTitleFontSize(float titleFontSize) {
|
||||
mTitleFontSize = titleFontSize;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,12 @@ public class ScreenStackHeaderConfigViewManager extends ViewGroupManager<ScreenS
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAfterUpdateTransaction(ScreenStackHeaderConfig parent) {
|
||||
super.onAfterUpdateTransaction(parent);
|
||||
parent.onUpdate();
|
||||
}
|
||||
|
||||
@ReactProp(name = "title")
|
||||
public void setTitle(ScreenStackHeaderConfig config, String title) {
|
||||
config.setTitle(title);
|
||||
@@ -63,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,21 @@ 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;
|
||||
}
|
||||
|
||||
const { screenProps } = this.props;
|
||||
return (
|
||||
<Screen
|
||||
key={`screen_${route.key}`}
|
||||
style={StyleSheet.absoluteFill}
|
||||
style={options.cardStyle}
|
||||
stackPresentation={stackPresentation}
|
||||
onDismissed={() => this._removeScene(route)}>
|
||||
{this._renderHeaderConfig(index, route, descriptor)}
|
||||
|
||||
@@ -17,6 +17,7 @@ typedef NS_ENUM(NSInteger, RNSScreenStackAnimation) {
|
||||
RNSScreenStackAnimationDefault,
|
||||
RNSScreenStackAnimationNone,
|
||||
RNSScreenStackAnimationFade,
|
||||
RNSScreenStackAnimationFlip,
|
||||
};
|
||||
|
||||
@interface RCTConvert (RNSScreen)
|
||||
|
||||
@@ -95,6 +95,9 @@
|
||||
case RNSScreenStackAnimationFade:
|
||||
_controller.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
|
||||
break;
|
||||
case RNSScreenStackAnimationFlip:
|
||||
_controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
|
||||
break;
|
||||
case RNSScreenStackAnimationNone:
|
||||
case RNSScreenStackAnimationDefault:
|
||||
// Default
|
||||
@@ -111,6 +114,8 @@
|
||||
{
|
||||
if (![view isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
|
||||
[super addSubview:view];
|
||||
} else {
|
||||
((RNSScreenStackHeaderConfig*) view).screenView = self;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,8 +280,10 @@ RCT_ENUM_CONVERTER(RNSScreenStackPresentation, (@{
|
||||
RCT_ENUM_CONVERTER(RNSScreenStackAnimation, (@{
|
||||
@"default": @(RNSScreenStackAnimationDefault),
|
||||
@"none": @(RNSScreenStackAnimationNone),
|
||||
@"fade": @(RNSScreenStackAnimationFade)
|
||||
@"fade": @(RNSScreenStackAnimationFade),
|
||||
@"flip": @(RNSScreenStackAnimationFlip),
|
||||
}), RNSScreenStackAnimationDefault, integerValue)
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
} else if (operation == UINavigationControllerOperationPop) {
|
||||
screen = (RNSScreenView *) fromVC.view;
|
||||
}
|
||||
if (screen != nil && screen.stackAnimation != RNSScreenStackAnimationDefault) {
|
||||
if (screen != nil && (screen.stackAnimation == RNSScreenStackAnimationFade || screen.stackAnimation == RNSScreenStackAnimationNone)) {
|
||||
return [[RNSScreenStackAnimator alloc] initWithOperation:operation];
|
||||
}
|
||||
return nil;
|
||||
@@ -185,7 +185,7 @@
|
||||
|
||||
if (firstTimePush) {
|
||||
// nothing pushed yet
|
||||
[_controller setViewControllers:@[top] animated:NO];
|
||||
[_controller setViewControllers:controllers animated:NO];
|
||||
} else if (top != lastTop) {
|
||||
if (![controllers containsObject:lastTop]) {
|
||||
// last top controller is no longer on stack
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#import <React/RCTViewManager.h>
|
||||
#import <React/RCTConvert.h>
|
||||
|
||||
#import "RNSScreen.h"
|
||||
|
||||
@interface RNSScreenStackHeaderConfig : UIView
|
||||
|
||||
@property (nonatomic, weak) RNSScreenView *screenView;
|
||||
|
||||
@property (nonatomic, retain) NSString *title;
|
||||
@property (nonatomic, retain) NSString *titleFontFamily;
|
||||
@property (nonatomic, retain) NSNumber *titleFontSize;
|
||||
|
||||
@@ -66,13 +66,36 @@
|
||||
return _reactSubviews;
|
||||
}
|
||||
|
||||
- (UIViewController*)screen
|
||||
- (UIView *)reactSuperview
|
||||
{
|
||||
UIView *superview = self.superview;
|
||||
if ([superview isKindOfClass:[RNSScreenView class]]) {
|
||||
return ((RNSScreenView *)superview).controller;
|
||||
return _screenView;
|
||||
}
|
||||
|
||||
- (void)removeFromSuperview
|
||||
{
|
||||
[super removeFromSuperview];
|
||||
_screenView = nil;
|
||||
}
|
||||
|
||||
- (void)updateViewControllerIfNeeded
|
||||
{
|
||||
UIViewController *vc = _screenView.controller;
|
||||
UINavigationController *nav = (UINavigationController*) vc.parentViewController;
|
||||
if (vc != nil && nav.visibleViewController == vc) {
|
||||
[RNSScreenStackHeaderConfig updateViewController:self.screenView.controller withConfig:self];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)didSetProps:(NSArray<NSString *> *)changedProps
|
||||
{
|
||||
[super didSetProps:changedProps];
|
||||
[self updateViewControllerIfNeeded];
|
||||
}
|
||||
|
||||
- (void)didUpdateReactSubviews
|
||||
{
|
||||
[super didUpdateReactSubviews];
|
||||
[self updateViewControllerIfNeeded];
|
||||
}
|
||||
|
||||
+ (void)setAnimatedConfig:(UIViewController *)vc withConfig:(RNSScreenStackHeaderConfig *)config
|
||||
@@ -142,6 +165,11 @@
|
||||
}
|
||||
|
||||
+ (void)willShowViewController:(UIViewController *)vc withConfig:(RNSScreenStackHeaderConfig *)config
|
||||
{
|
||||
[self updateViewController:vc withConfig:config];
|
||||
}
|
||||
|
||||
+ (void)updateViewController:(UIViewController *)vc withConfig:(RNSScreenStackHeaderConfig *)config
|
||||
{
|
||||
UINavigationItem *navitem = vc.navigationItem;
|
||||
UINavigationController *navctr = (UINavigationController *)vc.parentViewController;
|
||||
@@ -152,6 +180,15 @@
|
||||
BOOL wasHidden = navctr.navigationBarHidden;
|
||||
BOOL shouldHide = config == nil || config.hide;
|
||||
|
||||
if (!shouldHide && !config.translucent) {
|
||||
// when nav bar is not translucent we chage edgesForExtendedLayout to avoid system laying out
|
||||
// the screen underneath navigation controllers
|
||||
vc.edgesForExtendedLayout = UIRectEdgeNone;
|
||||
} else {
|
||||
// system default is UIRectEdgeAll
|
||||
vc.edgesForExtendedLayout = UIRectEdgeAll;
|
||||
}
|
||||
|
||||
[navctr setNavigationBarHidden:shouldHide animated:YES];
|
||||
navctr.interactivePopGestureRecognizer.enabled = config.gestureEnabled;
|
||||
#ifdef __IPHONE_13_0
|
||||
@@ -258,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.8",
|
||||
"version": "2.0.0-alpha.15",
|
||||
"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