mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-03-27 01:34:25 +08:00
Compare commits
8 Commits
2.0.0-alph
...
2.0.0-alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c88da09348 | ||
|
|
9aac0b89f2 | ||
|
|
a02dd46eec | ||
|
|
0919404b2e | ||
|
|
e61b8b3bd6 | ||
|
|
77d877f0c1 | ||
|
|
15c27e3fe7 | ||
|
|
ed478a829d |
@@ -127,7 +127,7 @@ Otherwise the views will be attached as long as the parent container is attached
|
||||
|
||||
### `<ScreenStack>`
|
||||
|
||||
Screen stack component expects one or more `Screen` components as direct children and renders them in a platform native stack container (for iOS it is `UINavigationController` and for Android inside `Fragment` container). For `Screen` components placed as children of `ScteenStack` the `active` property is ignored and instead the screen that corresponds to the last child is rendered as active. All type of updates done to the list of children are acceptable, when the top element is exchanged the container will use platform default (unless customized) animation to transition between screens.
|
||||
Screen stack component expects one or more `Screen` components as direct children and renders them in a platform native stack container (for iOS it is `UINavigationController` and for Android inside `Fragment` container). For `Screen` components placed as children of `ScreenStack` the `active` property is ignored and instead the screen that corresponds to the last child is rendered as active. All type of updates done to the list of children are acceptable, when the top element is exchanged the container will use platform default (unless customized) animation to transition between screens.
|
||||
|
||||
`StackScreen` extends the capabilities of `Screen` component to allow additional customizations and to make it possible to handle events such as using hardware back or back gesture to dismiss the top screen. Below is the list of additional properties that can be used for `Screen` component:
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
@@ -15,6 +16,7 @@ import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
||||
import com.facebook.react.views.text.ReactFontManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -184,6 +186,17 @@ public class ScreenStackHeaderConfig extends ViewGroup {
|
||||
ScreenStackHeaderSubview view = mConfigSubviews.get(i);
|
||||
ScreenStackHeaderSubview.Type type = view.getType();
|
||||
|
||||
if (type == ScreenStackHeaderSubview.Type.BACK) {
|
||||
// we special case BACK button header config type as we don't add it as a view into toolbar
|
||||
// but instead just copy the drawable from imageview that's added as a first child to it.
|
||||
View firstChild = view.getChildAt(0);
|
||||
if (!(firstChild instanceof ImageView)) {
|
||||
throw new JSApplicationIllegalArgumentException("Back button header config view should have Image as first child");
|
||||
}
|
||||
actionBar.setHomeAsUpIndicator(((ImageView) firstChild).getDrawable());
|
||||
continue;
|
||||
}
|
||||
|
||||
Toolbar.LayoutParams params =
|
||||
new Toolbar.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@ public class ScreenStackHeaderSubview extends ReactViewGroup {
|
||||
LEFT,
|
||||
CENTER,
|
||||
TITLE,
|
||||
RIGHT
|
||||
RIGHT,
|
||||
BACK
|
||||
}
|
||||
|
||||
private int mReactWidth, mReactHeight;
|
||||
|
||||
@@ -47,6 +47,8 @@ public class ScreenStackHeaderSubviewManager extends ReactViewManager {
|
||||
view.setType(ScreenStackHeaderSubview.Type.TITLE);
|
||||
} else if ("right".equals(type)) {
|
||||
view.setType(ScreenStackHeaderSubview.Type.RIGHT);
|
||||
} else if ("back".equals(type)) {
|
||||
view.setType(ScreenStackHeaderSubview.Type.BACK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
ScreenStack,
|
||||
Screen,
|
||||
ScreenStackHeaderConfig,
|
||||
ScreenStackHeaderBackButtonImage,
|
||||
ScreenStackHeaderLeftView,
|
||||
ScreenStackHeaderRightView,
|
||||
ScreenStackHeaderTitleView,
|
||||
@@ -93,6 +94,12 @@ class StackView extends React.Component {
|
||||
|
||||
const children = [];
|
||||
|
||||
if (options.backButtonImage) {
|
||||
children.push(
|
||||
<ScreenStackHeaderBackButtonImage source={options.backButtonImage} />
|
||||
);
|
||||
}
|
||||
|
||||
if (options.headerLeft !== undefined) {
|
||||
children.push(
|
||||
<ScreenStackHeaderLeftView key="left">
|
||||
@@ -168,7 +175,7 @@ class StackView extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
let stackAnimation;
|
||||
let stackAnimation = options.stackAnimation;
|
||||
if (options.animationEnabled === false) {
|
||||
stackAnimation = 'none';
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
|
||||
- (void)reactSetFrame:(CGRect)frame
|
||||
{
|
||||
if (_active) {
|
||||
[super reactSetFrame:frame];
|
||||
}
|
||||
// ignore setFrame call from react, the frame of this view
|
||||
// is controlled by the UIViewController it is contained in
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
@end
|
||||
|
||||
typedef NS_ENUM(NSInteger, RNSScreenStackHeaderSubviewType) {
|
||||
RNSScreenStackHeaderSubviewTypeBackButton,
|
||||
RNSScreenStackHeaderSubviewTypeLeft,
|
||||
RNSScreenStackHeaderSubviewTypeRight,
|
||||
RNSScreenStackHeaderSubviewTypeTitle,
|
||||
|
||||
@@ -5,6 +5,20 @@
|
||||
#import <React/RCTUIManager.h>
|
||||
#import <React/RCTUIManagerUtils.h>
|
||||
#import <React/RCTShadowView.h>
|
||||
#import <React/RCTImageLoader.h>
|
||||
#import <React/RCTImageView.h>
|
||||
#import <React/RCTImageSource.h>
|
||||
|
||||
// Some RN private method hacking below. Couldn't figure out better way to access image data
|
||||
// of a given RCTImageView. See more comments in the code section processing SubviewTypeBackButton
|
||||
@interface RCTImageView (Private)
|
||||
- (UIImage*)image;
|
||||
@end
|
||||
|
||||
@interface RCTImageLoader (Private)
|
||||
- (id<RCTImageCache>)imageCache;
|
||||
@end
|
||||
|
||||
|
||||
@interface RNSScreenHeaderItemMeasurements : NSObject
|
||||
@property (nonatomic, readonly) CGSize headerSize;
|
||||
@@ -30,6 +44,7 @@
|
||||
|
||||
@interface RNSScreenStackHeaderSubview : UIView
|
||||
|
||||
@property (nonatomic, weak) RCTBridge *bridge;
|
||||
@property (nonatomic, weak) UIView *reactSuperview;
|
||||
@property (nonatomic) RNSScreenStackHeaderSubviewType type;
|
||||
|
||||
@@ -150,6 +165,15 @@
|
||||
[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +188,59 @@
|
||||
}
|
||||
}
|
||||
|
||||
+ (UIImage*)loadBackButtonImageInViewController:(UIViewController *)vc
|
||||
withConfig:(RNSScreenStackHeaderConfig *)config
|
||||
{
|
||||
BOOL hasBackButtonImage = NO;
|
||||
for (RNSScreenStackHeaderSubview *subview in config.reactSubviews) {
|
||||
if (subview.type == RNSScreenStackHeaderSubviewTypeBackButton && subview.subviews.count > 0) {
|
||||
hasBackButtonImage = YES;
|
||||
RCTImageView *imageView = subview.subviews[0];
|
||||
UIImage *image = imageView.image;
|
||||
// IMPORTANT!!!
|
||||
// image can be nil in DEV MODE ONLY
|
||||
//
|
||||
// It is so, because in dev mode images are loaded over HTTP from the packager. In that case
|
||||
// we first check if image is already loaded in cache and if it is, we take it from cache and
|
||||
// display immediately. Otherwise we wait for the transition to finish and retry updating
|
||||
// header config.
|
||||
// Unfortunately due to some problems in UIKit we cannot update the image while the screen
|
||||
// transition is ongoing. This results in the settings being reset after the transition is done
|
||||
// to the state from before the transition.
|
||||
if (image == nil) {
|
||||
// in DEV MODE we try to load from cache (we use private API for that as it is not exposed
|
||||
// publically in headers).
|
||||
RCTImageSource *source = imageView.imageSources[0];
|
||||
image = [subview.bridge.imageLoader.imageCache
|
||||
imageForUrl:source.request.URL.absoluteString
|
||||
size:source.size
|
||||
scale:source.scale
|
||||
resizeMode:imageView.resizeMode];
|
||||
}
|
||||
if (image == nil) {
|
||||
// This will be triggered if the image is not in the cache yet. What we do is we wait until
|
||||
// the end of transition and run header config updates again. We could potentially wait for
|
||||
// image on load to trigger, but that would require even more private method hacking.
|
||||
if (vc.transitionCoordinator) {
|
||||
[vc.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
|
||||
// nothing, we just want completion
|
||||
} completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
|
||||
// in order for new back button image to be loaded we need to trigger another change
|
||||
// in back button props that'd make UIKit redraw the button. Otherwise the changes are
|
||||
// not reflected. Here we change back button visibility which is then immediately restored
|
||||
vc.navigationItem.hidesBackButton = YES;
|
||||
[config updateViewControllerIfNeeded];
|
||||
}];
|
||||
}
|
||||
return [UIImage new];
|
||||
} else {
|
||||
return image;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (void)willShowViewController:(UIViewController *)vc withConfig:(RNSScreenStackHeaderConfig *)config
|
||||
{
|
||||
[self updateViewController:vc withConfig:config];
|
||||
@@ -201,7 +278,6 @@
|
||||
}
|
||||
|
||||
navitem.title = config.title;
|
||||
navitem.hidesBackButton = config.hideBackButton;
|
||||
if (config.backTitle != nil) {
|
||||
prevItem.backBarButtonItem = [[UIBarButtonItem alloc]
|
||||
initWithTitle:config.backTitle
|
||||
@@ -290,11 +366,19 @@
|
||||
appearance.largeTitleTextAttributes = largeAttrs;
|
||||
}
|
||||
|
||||
UIImage *backButtonImage = [self loadBackButtonImageInViewController:vc withConfig:config];
|
||||
if (backButtonImage) {
|
||||
[appearance setBackIndicatorImage:backButtonImage transitionMaskImage:backButtonImage];
|
||||
} else if (appearance.backIndicatorImage) {
|
||||
[appearance setBackIndicatorImage:nil transitionMaskImage:nil];
|
||||
}
|
||||
|
||||
navitem.standardAppearance = appearance;
|
||||
navitem.compactAppearance = appearance;
|
||||
navitem.scrollEdgeAppearance = appearance;
|
||||
}
|
||||
#endif
|
||||
navitem.hidesBackButton = config.hideBackButton;
|
||||
navitem.leftBarButtonItem = nil;
|
||||
navitem.rightBarButtonItem = nil;
|
||||
navitem.titleView = nil;
|
||||
@@ -378,6 +462,7 @@ RCT_EXPORT_VIEW_PROPERTY(gestureEnabled, BOOL)
|
||||
@implementation RCTConvert (RNSScreenStackHeader)
|
||||
|
||||
RCT_ENUM_CONVERTER(RNSScreenStackHeaderSubviewType, (@{
|
||||
@"back": @(RNSScreenStackHeaderSubviewTypeBackButton),
|
||||
@"left": @(RNSScreenStackHeaderSubviewTypeLeft),
|
||||
@"right": @(RNSScreenStackHeaderSubviewTypeRight),
|
||||
@"title": @(RNSScreenStackHeaderSubviewTypeTitle),
|
||||
@@ -386,9 +471,7 @@ RCT_ENUM_CONVERTER(RNSScreenStackHeaderSubviewType, (@{
|
||||
|
||||
@end
|
||||
|
||||
@implementation RNSScreenStackHeaderSubview {
|
||||
__weak RCTBridge *_bridge;
|
||||
}
|
||||
@implementation RNSScreenStackHeaderSubview
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-native-screens",
|
||||
"version": "2.0.0-alpha.18",
|
||||
"version": "2.0.0-alpha.22",
|
||||
"description": "First incomplete navigation solution for your react-native app.",
|
||||
"scripts": {
|
||||
"start": "node node_modules/react-native/local-cli/cli.js start",
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
requireNativeComponent,
|
||||
View,
|
||||
UIManager,
|
||||
Image,
|
||||
StyleSheet,
|
||||
} from 'react-native';
|
||||
import { version } from 'react-native/Libraries/Core/ReactNativeVersion';
|
||||
@@ -146,6 +147,14 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
const ScreenStackHeaderBackButtonImage = props => (
|
||||
<ScreensNativeModules.NativeScreenStackHeaderSubview
|
||||
type="back"
|
||||
style={styles.headerSubview}>
|
||||
<Image resizeMode="center" fadeDuration={0} {...props} />
|
||||
</ScreensNativeModules.NativeScreenStackHeaderSubview>
|
||||
);
|
||||
|
||||
const ScreenStackHeaderRightView = props => (
|
||||
<ScreensNativeModules.NativeScreenStackHeaderSubview
|
||||
{...props}
|
||||
@@ -198,6 +207,7 @@ module.exports = {
|
||||
get ScreenStackHeaderSubview() {
|
||||
return ScreensNativeModules.NativeScreenStackHeaderSubview;
|
||||
},
|
||||
ScreenStackHeaderBackButtonImage,
|
||||
ScreenStackHeaderRightView,
|
||||
ScreenStackHeaderLeftView,
|
||||
ScreenStackHeaderTitleView,
|
||||
|
||||
Reference in New Issue
Block a user