mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-04-15 12:13:24 +08:00
Compare commits
4 Commits
2.0.0-alph
...
2.0.0-alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e00a08a3dc | ||
|
|
656e82de9f | ||
|
|
1958cf37ea | ||
|
|
75fb558cd3 |
@@ -0,0 +1,30 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
|
||||
public class ScreenAppearEvent extends Event<ScreenAppearEvent> {
|
||||
|
||||
public static final String EVENT_NAME = "topAppear";
|
||||
|
||||
public ScreenAppearEvent(int viewId) {
|
||||
super(viewId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return EVENT_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getCoalescingKey() {
|
||||
// All events for a given view can be coalesced.
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), Arguments.createMap());
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.swmansion.rnscreens;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
|
||||
@@ -22,11 +23,14 @@ public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
|
||||
|
||||
protected final ArrayList<T> mScreenFragments = new ArrayList<>();
|
||||
private final Set<ScreenFragment> mActiveScreenFragments = new HashSet<>();
|
||||
private final ArrayList<Runnable> mAfterTransitionRunnables = new ArrayList<>(1);
|
||||
|
||||
private @Nullable FragmentManager mFragmentManager;
|
||||
private @Nullable FragmentTransaction mCurrentTransaction;
|
||||
private @Nullable FragmentTransaction mProcessingTransaction;
|
||||
private boolean mNeedUpdate;
|
||||
private boolean mIsAttached;
|
||||
private boolean mIsTransitioning;
|
||||
private boolean mLayoutEnqueued = false;
|
||||
|
||||
private final ChoreographerCompat.FrameCallback mFrameCallback = new ChoreographerCompat.FrameCallback() {
|
||||
@@ -101,6 +105,36 @@ public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startViewTransition(View view) {
|
||||
super.startViewTransition(view);
|
||||
mIsTransitioning = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endViewTransition(View view) {
|
||||
super.endViewTransition(view);
|
||||
if (mIsTransitioning) {
|
||||
mIsTransitioning = false;
|
||||
notifyTransitionFinished();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isTransitioning() {
|
||||
return mIsTransitioning || mProcessingTransaction != null;
|
||||
}
|
||||
|
||||
public void postAfterTransition(Runnable runnable) {
|
||||
mAfterTransitionRunnables.add(runnable);
|
||||
}
|
||||
|
||||
protected void notifyTransitionFinished() {
|
||||
for (int i = 0, size = mAfterTransitionRunnables.size(); i < size; i++) {
|
||||
mAfterTransitionRunnables.get(i).run();
|
||||
}
|
||||
mAfterTransitionRunnables.clear();
|
||||
}
|
||||
|
||||
protected int getScreenCount() {
|
||||
return mScreenFragments.size();
|
||||
}
|
||||
@@ -159,6 +193,19 @@ public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
|
||||
|
||||
protected void tryCommitTransaction() {
|
||||
if (mCurrentTransaction != null) {
|
||||
final FragmentTransaction transaction = mCurrentTransaction;
|
||||
mProcessingTransaction = transaction;
|
||||
mProcessingTransaction.runOnCommit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mProcessingTransaction == transaction) {
|
||||
// we need to take into account that commit is initiated with some other transaction while
|
||||
// the previous one is still processing. In this case mProcessingTransaction gets overwritten
|
||||
// and we don't want to set it to null until the second transaction is finished.
|
||||
mProcessingTransaction = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
mCurrentTransaction.commitAllowingStateLoss();
|
||||
mCurrentTransaction = null;
|
||||
}
|
||||
@@ -184,6 +231,10 @@ public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
|
||||
return screenFragment.getScreen().isActive();
|
||||
}
|
||||
|
||||
protected boolean hasScreen(ScreenFragment screenFragment) {
|
||||
return mScreenFragments.contains(screenFragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
@@ -7,12 +7,10 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.facebook.react.uimanager.events.EventDispatcher;
|
||||
|
||||
public class ScreenFragment extends Fragment {
|
||||
|
||||
@@ -39,12 +37,39 @@ public class ScreenFragment extends Fragment {
|
||||
return mScreenView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
private void dispatchOnAppear() {
|
||||
((ReactContext) mScreenView.getContext())
|
||||
.getNativeModule(UIManagerModule.class)
|
||||
.getEventDispatcher()
|
||||
.dispatchEvent(new ScreenDismissedEvent(mScreenView.getId()));
|
||||
.dispatchEvent(new ScreenAppearEvent(mScreenView.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
ScreenContainer container = mScreenView.getContainer();
|
||||
if (container.isTransitioning()) {
|
||||
container.postAfterTransition(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
dispatchOnAppear();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
dispatchOnAppear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
ScreenContainer container = mScreenView.getContainer();
|
||||
if (container == null || !container.hasScreen(this)) {
|
||||
// we only send dismissed even when the screen has been removed from its container
|
||||
((ReactContext) mScreenView.getContext())
|
||||
.getNativeModule(UIManagerModule.class)
|
||||
.getEventDispatcher()
|
||||
.dispatchEvent(new ScreenDismissedEvent(mScreenView.getId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,11 @@ public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
|
||||
super.removeScreenAt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasScreen(ScreenFragment screenFragment) {
|
||||
return super.hasScreen(screenFragment) && !mDismissed.contains(screenFragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUpdate() {
|
||||
// remove all screens previously on stack
|
||||
@@ -128,19 +133,15 @@ 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)) {
|
||||
getOrCreateTransaction().add(getId(), screen);
|
||||
}
|
||||
// detach all screens that should not be visible
|
||||
if (screen != newTop && screen != belowTop && !mDismissed.contains(screen)) {
|
||||
getOrCreateTransaction().hide(screen);
|
||||
getOrCreateTransaction().remove(screen);
|
||||
}
|
||||
}
|
||||
// attach "below top" screen if set
|
||||
if (belowTop != null) {
|
||||
if (belowTop != null && !belowTop.isAdded()) {
|
||||
final ScreenStackFragment top = newTop;
|
||||
getOrCreateTransaction().show(belowTop).runOnCommit(new Runnable() {
|
||||
getOrCreateTransaction().add(getId(), belowTop).runOnCommit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
top.getScreen().bringToFront();
|
||||
@@ -148,8 +149,8 @@ public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
|
||||
});
|
||||
}
|
||||
|
||||
if (newTop != null) {
|
||||
getOrCreateTransaction().show(newTop);
|
||||
if (newTop != null && !newTop.isAdded()) {
|
||||
getOrCreateTransaction().add(getId(), newTop);
|
||||
}
|
||||
|
||||
if (!mStack.contains(newTop)) {
|
||||
|
||||
@@ -22,6 +22,7 @@ public class ScreenStackFragment extends ScreenFragment {
|
||||
private AppBarLayout mAppBarLayout;
|
||||
private Toolbar mToolbar;
|
||||
private boolean mShadowHidden;
|
||||
private CoordinatorLayout mScreenRootView;
|
||||
|
||||
@SuppressLint("ValidFragment")
|
||||
public ScreenStackFragment(Screen screenView) {
|
||||
@@ -59,10 +60,7 @@ public class ScreenStackFragment extends ScreenFragment {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
private CoordinatorLayout configureView() {
|
||||
CoordinatorLayout view = new CoordinatorLayout(getContext());
|
||||
CoordinatorLayout.LayoutParams params = new CoordinatorLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
|
||||
@@ -87,6 +85,17 @@ public class ScreenStackFragment extends ScreenFragment {
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
if (mScreenRootView == null) {
|
||||
mScreenRootView = configureView();
|
||||
}
|
||||
|
||||
return mScreenRootView;
|
||||
}
|
||||
|
||||
public boolean isDismissable() {
|
||||
View child = mScreenView.getChildAt(0);
|
||||
if (child instanceof ScreenStackHeaderConfig) {
|
||||
|
||||
@@ -62,6 +62,8 @@ public class ScreenViewManager extends ViewGroupManager<Screen> {
|
||||
public Map getExportedCustomDirectEventTypeConstants() {
|
||||
return MapBuilder.of(
|
||||
ScreenDismissedEvent.EVENT_NAME,
|
||||
MapBuilder.of("registrationName", "onDismissed"));
|
||||
MapBuilder.of("registrationName", "onDismissed"),
|
||||
ScreenAppearEvent.EVENT_NAME,
|
||||
MapBuilder.of("registrationName", "onAppear"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
StackRouter,
|
||||
SceneView,
|
||||
StackActions,
|
||||
NavigationActions,
|
||||
createNavigator,
|
||||
} from '@react-navigation/core';
|
||||
import { createKeyboardAwareNavigator } from '@react-navigation/native';
|
||||
@@ -27,14 +26,13 @@ function renderComponentOrThunk(componentOrThunk, props) {
|
||||
|
||||
class StackView extends React.Component {
|
||||
_removeScene = route => {
|
||||
const { navigation } = this.props;
|
||||
navigation.dispatch(
|
||||
NavigationActions.back({
|
||||
key: route.key,
|
||||
immediate: true,
|
||||
})
|
||||
this.props.navigation.dispatch(StackActions.pop({ key: route.key }));
|
||||
};
|
||||
|
||||
_onSceneFocus = route => {
|
||||
this.props.navigation.dispatch(
|
||||
StackActions.completeTransition({ toChildKey: route.key })
|
||||
);
|
||||
navigation.dispatch(StackActions.completeTransition());
|
||||
};
|
||||
|
||||
_renderHeaderConfig = (index, route, descriptor) => {
|
||||
@@ -165,7 +163,7 @@ class StackView extends React.Component {
|
||||
transparentCard || options.cardTransparent ? 'transparentModal' : mode;
|
||||
}
|
||||
|
||||
let stackAnimation = undefined;
|
||||
let stackAnimation;
|
||||
if (options.animationEnabled === false) {
|
||||
stackAnimation = 'none';
|
||||
}
|
||||
@@ -177,6 +175,7 @@ class StackView extends React.Component {
|
||||
style={options.cardStyle}
|
||||
stackAnimation={stackAnimation}
|
||||
stackPresentation={stackPresentation}
|
||||
onAppear={() => this._onSceneFocus(route)}
|
||||
onDismissed={() => this._removeScene(route)}>
|
||||
{this._renderHeaderConfig(index, route, descriptor)}
|
||||
<SceneView
|
||||
|
||||
@@ -39,6 +39,7 @@ typedef NS_ENUM(NSInteger, RNSScreenStackAnimation) {
|
||||
|
||||
@interface RNSScreenView : RCTView
|
||||
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onAppear;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onDismissed;
|
||||
@property (weak, nonatomic) UIView<RNSScreenContainerDelegate> *reactSuperview;
|
||||
@property (nonatomic, retain) UIViewController *controller;
|
||||
|
||||
@@ -135,6 +135,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)notifyAppear
|
||||
{
|
||||
if (self.onAppear) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (self.onAppear) {
|
||||
self.onAppear(nil);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isMountedUnderScreenOrReactRoot
|
||||
{
|
||||
for (UIView *parent = self.superview; parent != nil; parent = parent.superview) {
|
||||
@@ -235,6 +246,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated
|
||||
{
|
||||
[super viewDidAppear:animated];
|
||||
[((RNSScreenView *)self.view) notifyAppear];
|
||||
}
|
||||
|
||||
- (void)notifyFinishTransitioning
|
||||
{
|
||||
[_previousFirstResponder becomeFirstResponder];
|
||||
@@ -258,6 +275,7 @@ RCT_EXPORT_MODULE()
|
||||
RCT_EXPORT_VIEW_PROPERTY(active, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(stackPresentation, RNSScreenStackPresentation)
|
||||
RCT_EXPORT_VIEW_PROPERTY(stackAnimation, RNSScreenStackAnimation)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onAppear, RCTDirectEventBlock);
|
||||
RCT_EXPORT_VIEW_PROPERTY(onDismissed, RCTDirectEventBlock);
|
||||
|
||||
- (UIView *)view
|
||||
|
||||
@@ -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];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-native-screens",
|
||||
"version": "2.0.0-alpha.16",
|
||||
"version": "2.0.0-alpha.18",
|
||||
"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