mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-24 21:08:15 +08:00
Compare commits
13 Commits
2.0.0-beta
...
2.0.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1aeba7faa1 | ||
|
|
e6ed4176cd | ||
|
|
4f792b4281 | ||
|
|
d35c523c37 | ||
|
|
67806cbbb5 | ||
|
|
6212847218 | ||
|
|
a87faf443f | ||
|
|
bdf610bcad | ||
|
|
102880c18b | ||
|
|
24b70abd64 | ||
|
|
f7b6c22591 | ||
|
|
ed997ef4ec | ||
|
|
10a0badee2 |
@@ -49,8 +49,9 @@ repositories {
|
||||
dependencies {
|
||||
implementation 'com.facebook.react:react-native:+'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.0.0'
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
implementation 'androidx.fragment:fragment:1.2.1'
|
||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
|
||||
implementation 'com.google.android.material:material:1.1.0'
|
||||
}
|
||||
|
||||
def configureReactNativePom(def pom) {
|
||||
|
||||
@@ -2,18 +2,18 @@ package com.swmansion.rnscreens;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Paint;
|
||||
import android.os.Parcelable;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.facebook.react.bridge.GuardedRunnable;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.uimanager.PointerEvents;
|
||||
import com.facebook.react.uimanager.ReactPointerEventsView;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
|
||||
public class Screen extends ViewGroup {
|
||||
@@ -46,7 +46,7 @@ public class Screen extends ViewGroup {
|
||||
}
|
||||
};
|
||||
|
||||
private @Nullable Fragment mFragment;
|
||||
private @Nullable ScreenFragment mFragment;
|
||||
private @Nullable ScreenContainer mContainer;
|
||||
private boolean mActive;
|
||||
private boolean mTransitioning;
|
||||
@@ -54,8 +54,39 @@ public class Screen extends ViewGroup {
|
||||
private StackAnimation mStackAnimation = StackAnimation.DEFAULT;
|
||||
private boolean mGestureEnabled = true;
|
||||
|
||||
@Override
|
||||
protected void onAnimationEnd() {
|
||||
super.onAnimationEnd();
|
||||
if (mFragment != null) {
|
||||
mFragment.onViewAnimationEnd();
|
||||
}
|
||||
}
|
||||
|
||||
public Screen(ReactContext context) {
|
||||
super(context);
|
||||
// we set layout params as WindowManager.LayoutParams to workaround the issue with TextInputs
|
||||
// not displaying modal menus (e.g., copy/paste or selection). The missing menus are due to the
|
||||
// fact that TextView implementation is expected to be attached to window when layout happens.
|
||||
// Then, at the moment of layout it checks whether window type is in a reasonable range to tell
|
||||
// whether it should enable selection controlls (see Editor.java#prepareCursorControllers).
|
||||
// With screens, however, the text input component can be laid out before it is attached, in that
|
||||
// case TextView tries to get window type property from the oldest existing parent, which in this
|
||||
// case is a Screen class, as it is the root of the screen that is about to be attached. Setting
|
||||
// params this way is not the most elegant way to solve this problem but workarounds it for the
|
||||
// time being
|
||||
setLayoutParams(new WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_APPLICATION));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
|
||||
// do nothing, react native will keep the view hierarchy so no need to serialize/deserialize
|
||||
// view's states. The side effect of restoring is that TextInput components would trigger set-text
|
||||
// events which may confuse text input handling.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
|
||||
// ignore restoring instance state too as we are not saving anything anyways.
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,11 +175,11 @@ public class Screen extends ViewGroup {
|
||||
mContainer = container;
|
||||
}
|
||||
|
||||
protected void setFragment(Fragment fragment) {
|
||||
protected void setFragment(ScreenFragment fragment) {
|
||||
mFragment = fragment;
|
||||
}
|
||||
|
||||
protected @Nullable Fragment getFragment() {
|
||||
protected @Nullable ScreenFragment getFragment() {
|
||||
return mFragment;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
@@ -22,15 +23,12 @@ import java.util.Set;
|
||||
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);
|
||||
|
||||
protected @Nullable FragmentManager mFragmentManager;
|
||||
private @Nullable FragmentTransaction mCurrentTransaction;
|
||||
private @Nullable FragmentTransaction mProcessingTransaction;
|
||||
private boolean mNeedUpdate;
|
||||
private boolean mIsAttached;
|
||||
private boolean mIsTransitioning;
|
||||
private boolean mLayoutEnqueued = false;
|
||||
|
||||
|
||||
@@ -118,36 +116,6 @@ 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();
|
||||
}
|
||||
@@ -219,7 +187,6 @@ public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
|
||||
|
||||
private void attachScreen(ScreenFragment screenFragment) {
|
||||
getOrCreateTransaction().add(getId(), screenFragment);
|
||||
mActiveScreenFragments.add(screenFragment);
|
||||
}
|
||||
|
||||
private void moveToFront(ScreenFragment screenFragment) {
|
||||
@@ -230,7 +197,6 @@ public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
|
||||
|
||||
private void detachScreen(ScreenFragment screenFragment) {
|
||||
getOrCreateTransaction().remove(screenFragment);
|
||||
mActiveScreenFragments.remove(screenFragment);
|
||||
}
|
||||
|
||||
protected boolean isScreenActive(ScreenFragment screenFragment) {
|
||||
@@ -254,14 +220,6 @@ public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
mIsAttached = false;
|
||||
|
||||
// fragment manager is destroyed so we can't do anything with it anymore
|
||||
mFragmentManager = null;
|
||||
// so we don't add the same screen twice after re-attach
|
||||
removeAllViews();
|
||||
mActiveScreenFragments.clear();
|
||||
// after re-attach we'll update the screen and add views again
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -282,11 +240,11 @@ public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
|
||||
|
||||
protected void onUpdate() {
|
||||
// detach screens that are no longer active
|
||||
Set<ScreenFragment> orphaned = new HashSet<>(mActiveScreenFragments);
|
||||
Set<Fragment> orphaned = new HashSet<>(mFragmentManager.getFragments());
|
||||
for (int i = 0, size = mScreenFragments.size(); i < size; i++) {
|
||||
ScreenFragment screenFragment = mScreenFragments.get(i);
|
||||
boolean isActive = isScreenActive(screenFragment);
|
||||
if (!isActive && mActiveScreenFragments.contains(screenFragment)) {
|
||||
if (!isActive && screenFragment.isAdded()) {
|
||||
detachScreen(screenFragment);
|
||||
}
|
||||
orphaned.remove(screenFragment);
|
||||
@@ -312,7 +270,7 @@ public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
|
||||
for (int i = 0, size = mScreenFragments.size(); i < size; i++) {
|
||||
ScreenFragment screenFragment = mScreenFragments.get(i);
|
||||
boolean isActive = isScreenActive(screenFragment);
|
||||
if (isActive && !mActiveScreenFragments.contains(screenFragment)) {
|
||||
if (isActive && !screenFragment.isAdded()) {
|
||||
addedBefore = true;
|
||||
attachScreen(screenFragment);
|
||||
} else if (isActive && addedBefore) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
@@ -14,6 +15,17 @@ import com.facebook.react.uimanager.UIManagerModule;
|
||||
|
||||
public class ScreenFragment extends Fragment {
|
||||
|
||||
protected static View recycleView(View view) {
|
||||
// screen fragments reuse view instances instead of creating new ones. In order to reuse a given
|
||||
// view it needs to be detached from the view hierarchy to allow the fragment to attach it back.
|
||||
ViewParent parent = view.getParent();
|
||||
if (parent != null) {
|
||||
((ViewGroup) parent).endViewTransition(view);
|
||||
((ViewGroup) parent).removeView(view);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
protected Screen mScreenView;
|
||||
|
||||
public ScreenFragment() {
|
||||
@@ -30,7 +42,7 @@ public class ScreenFragment extends Fragment {
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
return mScreenView;
|
||||
return recycleView(mScreenView);
|
||||
}
|
||||
|
||||
public Screen getScreen() {
|
||||
@@ -45,19 +57,21 @@ public class ScreenFragment extends Fragment {
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
public void onViewAnimationEnd() {
|
||||
// onViewAnimationEnd is triggered from View#onAnimationEnd method of the fragment's root view.
|
||||
// We override Screen#onAnimationEnd and an appropriate method of the StackFragment's root view
|
||||
// in order to achieve this.
|
||||
dispatchOnAppear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
recycleView(getView());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@@ -18,6 +22,7 @@ public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
|
||||
private final Set<ScreenStackFragment> mDismissed = new HashSet<>();
|
||||
|
||||
private ScreenStackFragment mTopScreen = null;
|
||||
private boolean mRemovalTransitionStarted = false;
|
||||
|
||||
private final FragmentManager.OnBackStackChangedListener mBackStackListener = new FragmentManager.OnBackStackChangedListener() {
|
||||
@Override
|
||||
@@ -50,7 +55,7 @@ public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
|
||||
}
|
||||
|
||||
public Screen getTopScreen() {
|
||||
return mTopScreen.getScreen();
|
||||
return mTopScreen != null ? mTopScreen.getScreen() : null;
|
||||
}
|
||||
|
||||
public Screen getRootScreen() {
|
||||
@@ -90,6 +95,34 @@ public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
|
||||
mFragmentManager.registerFragmentLifecycleCallbacks(mLifecycleCallbacks, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startViewTransition(View view) {
|
||||
super.startViewTransition(view);
|
||||
mRemovalTransitionStarted = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endViewTransition(View view) {
|
||||
super.endViewTransition(view);
|
||||
if (mRemovalTransitionStarted) {
|
||||
mRemovalTransitionStarted = false;
|
||||
dispatchOnFinishTransitioning();
|
||||
}
|
||||
}
|
||||
|
||||
public void onViewAppearTransitionEnd() {
|
||||
if (!mRemovalTransitionStarted) {
|
||||
dispatchOnFinishTransitioning();
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchOnFinishTransitioning() {
|
||||
((ReactContext) getContext())
|
||||
.getNativeModule(UIManagerModule.class)
|
||||
.getEventDispatcher()
|
||||
.dispatchEvent(new StackFinishTransitioningEvent(getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeScreenAt(int index) {
|
||||
Screen toBeRemoved = getScreenAt(index);
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package com.swmansion.rnscreens;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.view.animation.Animation;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
@@ -18,6 +22,27 @@ import com.google.android.material.appbar.AppBarLayout;
|
||||
|
||||
public class ScreenStackFragment extends ScreenFragment {
|
||||
|
||||
private static class NotifyingCoordinatorLayout extends CoordinatorLayout {
|
||||
|
||||
private final ScreenFragment mFragment;
|
||||
|
||||
public NotifyingCoordinatorLayout(@NonNull Context context, ScreenFragment fragment) {
|
||||
super(context);
|
||||
mFragment = fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startAnimation(Animation animation) {
|
||||
super.startAnimation(animation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAnimationEnd() {
|
||||
super.onAnimationEnd();
|
||||
mFragment.onViewAnimationEnd();
|
||||
}
|
||||
}
|
||||
|
||||
private static final float TOOLBAR_ELEVATION = PixelUtil.toPixelFromDIP(4);
|
||||
|
||||
private AppBarLayout mAppBarLayout;
|
||||
@@ -62,7 +87,7 @@ public class ScreenStackFragment extends ScreenFragment {
|
||||
}
|
||||
|
||||
private CoordinatorLayout configureView() {
|
||||
CoordinatorLayout view = new CoordinatorLayout(getContext());
|
||||
CoordinatorLayout view = new NotifyingCoordinatorLayout(getContext(), this);
|
||||
CoordinatorLayout.LayoutParams params = new CoordinatorLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
|
||||
params.setBehavior(new AppBarLayout.ScrollingViewBehavior());
|
||||
@@ -86,6 +111,30 @@ public class ScreenStackFragment extends ScreenFragment {
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewAnimationEnd() {
|
||||
super.onViewAnimationEnd();
|
||||
notifyViewAppearTransitionEnd();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
|
||||
if (enter && transit == 0) {
|
||||
// this means that the fragment will appear without transition, in this case onViewAnimationEnd
|
||||
// won't be called and we need to notify stack directly from here.
|
||||
notifyViewAppearTransitionEnd();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void notifyViewAppearTransitionEnd() {
|
||||
ViewParent screenStack = getView().getParent();
|
||||
if (screenStack instanceof ScreenStack) {
|
||||
((ScreenStack) screenStack).onViewAppearTransitionEnd();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@@ -94,7 +143,7 @@ public class ScreenStackFragment extends ScreenFragment {
|
||||
mScreenRootView = configureView();
|
||||
}
|
||||
|
||||
return mScreenRootView;
|
||||
return recycleView(mScreenRootView);
|
||||
}
|
||||
|
||||
public boolean isDismissable() {
|
||||
|
||||
@@ -132,6 +132,11 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
return;
|
||||
}
|
||||
|
||||
AppCompatActivity activity = (AppCompatActivity) getScreenFragment().getActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mIsHidden) {
|
||||
if (mToolbar.getParent() != null) {
|
||||
getScreenFragment().removeToolbar();
|
||||
@@ -143,7 +148,6 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
getScreenFragment().setToolbar(mToolbar);
|
||||
}
|
||||
|
||||
AppCompatActivity activity = (AppCompatActivity) getScreenFragment().getActivity();
|
||||
activity.setSupportActionBar(mToolbar);
|
||||
ActionBar actionBar = activity.getSupportActionBar();
|
||||
|
||||
|
||||
@@ -69,6 +69,8 @@ public class ScreenViewManager extends ViewGroupManager<Screen> {
|
||||
ScreenDismissedEvent.EVENT_NAME,
|
||||
MapBuilder.of("registrationName", "onDismissed"),
|
||||
ScreenAppearEvent.EVENT_NAME,
|
||||
MapBuilder.of("registrationName", "onAppear"));
|
||||
MapBuilder.of("registrationName", "onAppear"),
|
||||
StackFinishTransitioningEvent.EVENT_NAME,
|
||||
MapBuilder.of("registrationName", "onFinishTransitioning"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
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 StackFinishTransitioningEvent extends Event<ScreenAppearEvent> {
|
||||
public static final String EVENT_NAME = "topFinishTransitioning";
|
||||
|
||||
public StackFinishTransitioningEvent(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());
|
||||
}
|
||||
}
|
||||
@@ -30,15 +30,31 @@ class StackView extends React.Component {
|
||||
this.props.navigation.dispatch(StackActions.pop({ key: route.key }));
|
||||
};
|
||||
|
||||
_onSceneFocus = (route, descriptor) => {
|
||||
_onAppear = (route, descriptor) => {
|
||||
descriptor.options &&
|
||||
descriptor.options.onAppear &&
|
||||
descriptor.options.onAppear();
|
||||
this.props.navigation.dispatch(
|
||||
StackActions.completeTransition({ toChildKey: route.key })
|
||||
StackActions.completeTransition({
|
||||
toChildKey: route.key,
|
||||
key: this.props.navigation.state.key,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
_onFinishTransitioning = () => {
|
||||
const { routes } = this.props.navigation.state;
|
||||
let lastRoute = routes && routes.length && routes[routes.length - 1];
|
||||
if (lastRoute) {
|
||||
this.props.navigation.dispatch(
|
||||
StackActions.completeTransition({
|
||||
toChildKey: lastRoute.key,
|
||||
key: this.props.navigation.state.key,
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
_renderHeaderConfig = (index, route, descriptor) => {
|
||||
const { navigationConfig } = this.props;
|
||||
const { options } = descriptor;
|
||||
@@ -188,13 +204,13 @@ class StackView extends React.Component {
|
||||
return (
|
||||
<Screen
|
||||
key={`screen_${route.key}`}
|
||||
style={options.cardStyle}
|
||||
style={[StyleSheet.absoluteFill, options.cardStyle]}
|
||||
stackAnimation={stackAnimation}
|
||||
stackPresentation={stackPresentation}
|
||||
gestureEnabled={
|
||||
options.gestureEnabled === undefined ? true : options.gestureEnabled
|
||||
}
|
||||
onAppear={() => this._onSceneFocus(route, descriptor)}
|
||||
onAppear={() => this._onAppear(route, descriptor)}
|
||||
onDismissed={() => this._removeScene(route)}>
|
||||
{this._renderHeaderConfig(index, route, descriptor)}
|
||||
<SceneView
|
||||
@@ -210,7 +226,9 @@ class StackView extends React.Component {
|
||||
const { navigation, descriptors } = this.props;
|
||||
|
||||
return (
|
||||
<ScreenStack style={styles.scenes}>
|
||||
<ScreenStack
|
||||
style={styles.scenes}
|
||||
onFinishTransitioning={this._onFinishTransitioning}>
|
||||
{navigation.state.routes.map((route, i) =>
|
||||
this._renderScene(i, route, descriptors[route.key])
|
||||
)}
|
||||
|
||||
@@ -34,11 +34,16 @@
|
||||
|
||||
- (void)reactSetFrame:(CGRect)frame
|
||||
{
|
||||
if (_active) {
|
||||
if (![self.reactViewController.parentViewController
|
||||
isKindOfClass:[UINavigationController class]]) {
|
||||
[super reactSetFrame:frame];
|
||||
}
|
||||
// ignore setFrame call from react, the frame of this view
|
||||
// is controlled by the UIViewController it is contained in
|
||||
// when screen is mounted under UINavigationController it's size is controller
|
||||
// by the navigation controller itself. That is, it is set to fill space of
|
||||
// the controller. In that case we ignore react layout system from managing
|
||||
// the screen dimentions and we wait for the screen VC to update and then we
|
||||
// pass the dimentions to ui view manager to take into account when laying out
|
||||
// subviews
|
||||
}
|
||||
|
||||
- (void)updateBounds
|
||||
@@ -191,6 +196,19 @@
|
||||
[_touchHandler reset];
|
||||
}
|
||||
|
||||
- (BOOL)presentationControllerShouldDismiss:(UIPresentationController *)presentationController
|
||||
{
|
||||
return _gestureEnabled;
|
||||
}
|
||||
|
||||
- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController
|
||||
{
|
||||
if ([_reactSuperview respondsToSelector:@selector(presentationControllerDidDismiss:)]) {
|
||||
[_reactSuperview performSelector:@selector(presentationControllerDidDismiss:)
|
||||
withObject:presentationController];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RNSScreen {
|
||||
@@ -247,6 +265,8 @@
|
||||
if (self.parentViewController == nil && self.presentingViewController == nil) {
|
||||
// screen dismissed, send event
|
||||
[((RNSScreenView *)self.view) notifyDismissed];
|
||||
_view = self.view;
|
||||
self.view = nil;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
@interface RNSScreenStackView : UIView <RNSScreenContainerDelegate, RCTInvalidating>
|
||||
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onFinishTransitioning;
|
||||
|
||||
- (void)markChildUpdated;
|
||||
- (void)didUpdateChildren;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#import <React/RCTRootContentView.h>
|
||||
#import <React/RCTTouchHandler.h>
|
||||
|
||||
@interface RNSScreenStackView () <UINavigationControllerDelegate, UIGestureRecognizerDelegate>
|
||||
@interface RNSScreenStackView () <UINavigationControllerDelegate, UIAdaptivePresentationControllerDelegate, UIGestureRecognizerDelegate>
|
||||
@end
|
||||
|
||||
@interface RNSScreenStackAnimator : NSObject <UIViewControllerAnimatedTransitioning>
|
||||
@@ -22,7 +22,6 @@
|
||||
NSMutableArray<RNSScreenView *> *_reactSubviews;
|
||||
NSMutableSet<RNSScreenView *> *_dismissedScreens;
|
||||
NSMutableArray<UIViewController *> *_presentedModals;
|
||||
__weak UIViewController* recentPopped;
|
||||
__weak RNSScreenStackManager *_manager;
|
||||
}
|
||||
|
||||
@@ -64,15 +63,36 @@
|
||||
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
|
||||
{
|
||||
for (NSUInteger i = _reactSubviews.count; i > 0; i--) {
|
||||
if ([viewController isEqual:[_reactSubviews objectAtIndex:i - 1].controller]) {
|
||||
RNSScreenView *screenView = [_reactSubviews objectAtIndex:i - 1];
|
||||
if ([viewController isEqual:screenView.controller]) {
|
||||
break;
|
||||
} else {
|
||||
[_dismissedScreens addObject:[_reactSubviews objectAtIndex:i - 1]];
|
||||
} else if (screenView.stackPresentation == RNSScreenStackPresentationPush) {
|
||||
[_dismissedScreens addObject:screenView];
|
||||
}
|
||||
}
|
||||
if (recentPopped != nil) {
|
||||
recentPopped.view = nil;
|
||||
recentPopped = nil;
|
||||
if (self.onFinishTransitioning) {
|
||||
self.onFinishTransitioning(nil);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController
|
||||
{
|
||||
// We don't directly set presentation delegate but instead rely on the ScreenView's delegate to
|
||||
// forward certain calls to the container (Stack).
|
||||
UIView *screenView = presentationController.presentedViewController.view;
|
||||
if ([screenView isKindOfClass:[RNSScreenView class]]) {
|
||||
[_dismissedScreens addObject:(RNSScreenView *)screenView];
|
||||
[_presentedModals removeObject:presentationController.presentedViewController];
|
||||
if (self.onFinishTransitioning) {
|
||||
// instead of directly triggering onFinishTransitioning this time we enqueue the event on the
|
||||
// main queue. We do that because onDismiss event is also enqueued and we want for the transition
|
||||
// finish event to arrive later than onDismiss (see RNSScreen#notifyDismiss)
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (self.onFinishTransitioning) {
|
||||
self.onFinishTransitioning(nil);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +103,6 @@
|
||||
screen = (RNSScreenView *) toVC.view;
|
||||
} else if (operation == UINavigationControllerOperationPop) {
|
||||
screen = (RNSScreenView *) fromVC.view;
|
||||
recentPopped = fromVC;
|
||||
}
|
||||
if (screen != nil && (screen.stackAnimation == RNSScreenStackAnimationFade || screen.stackAnimation == RNSScreenStackAnimationNone)) {
|
||||
return [[RNSScreenStackAnimator alloc] initWithOperation:operation];
|
||||
@@ -123,11 +142,13 @@
|
||||
RCTLogError(@"ScreenStack only accepts children of type Screen");
|
||||
return;
|
||||
}
|
||||
subview.reactSuperview = self;
|
||||
[_reactSubviews insertObject:subview atIndex:atIndex];
|
||||
}
|
||||
|
||||
- (void)removeReactSubview:(RNSScreenView *)subview
|
||||
{
|
||||
subview.reactSuperview = nil;
|
||||
[_reactSubviews removeObject:subview];
|
||||
[_dismissedScreens removeObject:subview];
|
||||
}
|
||||
@@ -176,15 +197,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
__weak RNSScreenStackView *weakSelf = self;
|
||||
|
||||
void (^dispatchFinishTransitioning)(void) = ^{
|
||||
if (weakSelf.onFinishTransitioning) {
|
||||
weakSelf.onFinishTransitioning(nil);
|
||||
}
|
||||
};
|
||||
|
||||
void (^finish)(void) = ^{
|
||||
UIViewController *previous = changeRootController;
|
||||
for (NSUInteger i = changeRootIndex; i < controllers.count; i++) {
|
||||
UIViewController *next = controllers[i];
|
||||
BOOL animate = (i == controllers.count - 1);
|
||||
[previous presentViewController:next
|
||||
animated:(i == controllers.count - 1)
|
||||
completion:nil];
|
||||
animated:animate
|
||||
completion:animate ? dispatchFinishTransitioning : nil];
|
||||
previous = next;
|
||||
}
|
||||
if (changeRootIndex >= controllers.count) {
|
||||
dispatchFinishTransitioning();
|
||||
}
|
||||
};
|
||||
|
||||
if (changeRootController.presentedViewController) {
|
||||
@@ -299,8 +332,7 @@
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(transitioning, NSInteger)
|
||||
RCT_EXPORT_VIEW_PROPERTY(progress, CGFloat)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onFinishTransitioning, RCTDirectEventBlock);
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
|
||||
@@ -118,9 +118,12 @@
|
||||
UINavigationBar *navbar = ((UINavigationController *)vc.parentViewController).navigationBar;
|
||||
[navbar setTintColor:config.color];
|
||||
|
||||
#ifdef __IPHONE_13_0
|
||||
if (@available(iOS 13.0, *)) {
|
||||
// font customized on the navigation item level, so nothing to do here
|
||||
} else {
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
BOOL hideShadow = config.hideShadow;
|
||||
|
||||
if (config.backgroundColor && CGColorGetAlpha(config.backgroundColor.CGColor) == 0.) {
|
||||
@@ -165,15 +168,6 @@
|
||||
[navbar setLargeTitleTextAttributes:largeAttrs];
|
||||
}
|
||||
}
|
||||
|
||||
UIImage *backButtonImage = [self loadBackButtonImageInViewController:vc withConfig:config];
|
||||
if (backButtonImage) {
|
||||
navbar.backIndicatorImage = backButtonImage;
|
||||
navbar.backIndicatorTransitionMaskImage = backButtonImage;
|
||||
} else if (navbar.backIndicatorImage) {
|
||||
navbar.backIndicatorImage = nil;
|
||||
navbar.backIndicatorTransitionMaskImage = nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,6 +190,20 @@
|
||||
if (subview.type == RNSScreenStackHeaderSubviewTypeBackButton && subview.subviews.count > 0) {
|
||||
hasBackButtonImage = YES;
|
||||
RCTImageView *imageView = subview.subviews[0];
|
||||
if (imageView.image == nil) {
|
||||
// This is yet another workaround for loading custom back icon. It turns out that under
|
||||
// certain circumstances image attribute can be null despite the app running in production
|
||||
// mode (when images are loaded from the filesystem). This can happen because image attribute
|
||||
// is reset when image view is detached from window, and also in some cases initialization
|
||||
// does not populate the frame of the image view before the loading start. The latter result
|
||||
// in the image attribute not being updated. We manually set frame to the size of an image
|
||||
// in order to trigger proper reload that'd update the image attribute.
|
||||
RCTImageSource *source = imageView.imageSources[0];
|
||||
[imageView reactSetFrame:CGRectMake(imageView.frame.origin.x,
|
||||
imageView.frame.origin.y,
|
||||
source.size.width,
|
||||
source.size.height)];
|
||||
}
|
||||
UIImage *image = imageView.image;
|
||||
// IMPORTANT!!!
|
||||
// image can be nil in DEV MODE ONLY
|
||||
@@ -375,8 +383,20 @@
|
||||
navitem.standardAppearance = appearance;
|
||||
navitem.compactAppearance = appearance;
|
||||
navitem.scrollEdgeAppearance = appearance;
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
// updating backIndicatotImage does not work when called during transition. On iOS pre 13 we need
|
||||
// to update it before the navigation starts.
|
||||
UIImage *backButtonImage = [self loadBackButtonImageInViewController:vc withConfig:config];
|
||||
if (backButtonImage) {
|
||||
navctr.navigationBar.backIndicatorImage = backButtonImage;
|
||||
navctr.navigationBar.backIndicatorTransitionMaskImage = backButtonImage;
|
||||
} else if (navctr.navigationBar.backIndicatorImage) {
|
||||
navctr.navigationBar.backIndicatorImage = nil;
|
||||
navctr.navigationBar.backIndicatorTransitionMaskImage = nil;
|
||||
}
|
||||
}
|
||||
navitem.hidesBackButton = config.hideBackButton;
|
||||
navitem.leftBarButtonItem = nil;
|
||||
navitem.rightBarButtonItem = nil;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-native-screens",
|
||||
"version": "2.0.0-beta.2",
|
||||
"version": "2.0.0-beta.6",
|
||||
"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