Navigation Back support and examples for Android
Summary: public - Intro new back action - Add support in the two main reducers - Use it in examples to support Android back button - Disable NavigationCard gestures on Android Reviewed By: hedgerwang Differential Revision: D2914154 fb-gh-sync-id: d4dce6538e19613a2ffca21e2e3b2ecaded3d5dc shipit-source-id: d4dce6538e19613a2ffca21e2e3b2ecaded3d5dc
This commit is contained in:
parent
7b57b5c84a
commit
7b2b0c3c1c
|
@ -44,11 +44,18 @@ class NavigationAnimatedExample extends React.Component {
|
|||
return (
|
||||
<NavigationRootContainer
|
||||
reducer={NavigationBasicReducer}
|
||||
ref={navRootContainer => { this.navRootContainer = navRootContainer; }}
|
||||
persistenceKey="NavigationAnimatedExampleState"
|
||||
renderNavigation={this._renderNavigated}
|
||||
/>
|
||||
);
|
||||
}
|
||||
handleBackAction() {
|
||||
return (
|
||||
this.navRootContainer &&
|
||||
this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction())
|
||||
);
|
||||
}
|
||||
_renderNavigated(navigationState, onNavigate) {
|
||||
if (!navigationState) {
|
||||
return null;
|
||||
|
|
|
@ -40,6 +40,7 @@ const NavigationBasicExample = React.createClass({
|
|||
<NavigationRootContainer
|
||||
reducer={NavigationBasicReducer}
|
||||
persistenceKey="NavigationBasicExampleState"
|
||||
ref={navRootContainer => { this.navRootContainer = navRootContainer; }}
|
||||
renderNavigation={(navState, onNavigate) => {
|
||||
if (!navState) { return null; }
|
||||
return (
|
||||
|
@ -69,6 +70,14 @@ const NavigationBasicExample = React.createClass({
|
|||
/>
|
||||
);
|
||||
},
|
||||
|
||||
handleBackAction: function() {
|
||||
return (
|
||||
this.navRootContainer &&
|
||||
this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction())
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
|
|
@ -135,18 +135,18 @@ function stateTypeTitleMap(pageState) {
|
|||
}
|
||||
}
|
||||
|
||||
let ExampleTabScreen = React.createClass({
|
||||
render: function() {
|
||||
class ExampleTabScreen extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<NavigationAnimatedView
|
||||
style={styles.tabContent}
|
||||
navigationState={this.props.navigationState}
|
||||
renderOverlay={this._renderHeader}
|
||||
renderScene={this._renderScene}
|
||||
renderOverlay={this._renderHeader.bind(this)}
|
||||
renderScene={this._renderScene.bind(this)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
_renderHeader: function(position, layout) {
|
||||
}
|
||||
_renderHeader(position, layout) {
|
||||
return (
|
||||
<NavigationHeader
|
||||
navigationState={this.props.navigationState}
|
||||
|
@ -155,8 +155,8 @@ let ExampleTabScreen = React.createClass({
|
|||
getTitle={state => stateTypeTitleMap(state)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
_renderScene: function(child, index, position, layout) {
|
||||
}
|
||||
_renderScene(child, index, position, layout) {
|
||||
return (
|
||||
<NavigationCard
|
||||
key={child.key}
|
||||
|
@ -187,21 +187,28 @@ let ExampleTabScreen = React.createClass({
|
|||
</ScrollView>
|
||||
</NavigationCard>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
ExampleTabScreen = NavigationContainer.create(ExampleTabScreen);
|
||||
|
||||
const NavigationCompositionExample = React.createClass({
|
||||
render: function() {
|
||||
class NavigationCompositionExample extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<NavigationRootContainer
|
||||
reducer={ExampleAppReducer}
|
||||
persistenceKey="NavigationCompositionExampleState"
|
||||
renderNavigation={this.renderApp}
|
||||
ref={navRootContainer => { this.navRootContainer = navRootContainer; }}
|
||||
renderNavigation={this.renderApp.bind(this)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
renderApp: function(navigationState, onNavigate) {
|
||||
}
|
||||
handleBackAction() {
|
||||
return (
|
||||
this.navRootContainer &&
|
||||
this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction())
|
||||
);
|
||||
}
|
||||
renderApp(navigationState, onNavigate) {
|
||||
if (!navigationState) {
|
||||
return null;
|
||||
}
|
||||
|
@ -217,11 +224,11 @@ const NavigationCompositionExample = React.createClass({
|
|||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let ExampleMainView = React.createClass({
|
||||
render: function() {
|
||||
class ExampleMainView extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<NavigationView
|
||||
navigationState={this.props.navigationState}
|
||||
|
@ -230,20 +237,20 @@ let ExampleMainView = React.createClass({
|
|||
<ExampleTabScreen
|
||||
key={tabState.key}
|
||||
navigationState={tabState}
|
||||
onNavigate={this._handleNavigation.bind(null, tabState.key)}
|
||||
onNavigate={this._handleNavigation.bind(this, tabState.key)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
_handleNavigation: function(tabKey, action) {
|
||||
}
|
||||
_handleNavigation(tabKey, action) {
|
||||
if (ExampleExitAction.match(action)) {
|
||||
this.props.onExampleExit();
|
||||
return;
|
||||
}
|
||||
this.props.onNavigate(action);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
ExampleMainView = NavigationContainer.create(ExampleMainView);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
|
|
@ -25,7 +25,7 @@ var NavigationExampleRow = require('./NavigationExampleRow');
|
|||
/*
|
||||
* Heads up! This file is not the real navigation example- only a utility to switch between them.
|
||||
*
|
||||
* To learn how to use the Navigation API, take a look at the following exmample files:
|
||||
* To learn how to use the Navigation API, take a look at the following example files:
|
||||
*/
|
||||
var EXAMPLES = {
|
||||
'Tabs': require('./NavigationTabsExample'),
|
||||
|
@ -106,13 +106,34 @@ var NavigationExperimentalExample = React.createClass({
|
|||
this.setExample('menu');
|
||||
},
|
||||
|
||||
handleBackAction: function() {
|
||||
const wasHandledByExample = (
|
||||
this.exampleRef &&
|
||||
this.exampleRef.handleBackAction &&
|
||||
this.exampleRef.handleBackAction()
|
||||
);
|
||||
if (wasHandledByExample) {
|
||||
return true;
|
||||
}
|
||||
if (this.state.example && this.state.example !== 'menu') {
|
||||
this._exitInnerExample();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.example === 'menu') {
|
||||
return this._renderMenu();
|
||||
}
|
||||
if (EXAMPLES[this.state.example]) {
|
||||
var Component = EXAMPLES[this.state.example];
|
||||
return <Component onExampleExit={this._exitInnerExample} />;
|
||||
return (
|
||||
<Component
|
||||
onExampleExit={this._exitInnerExample}
|
||||
ref={exampleRef => { this.exampleRef = exampleRef; }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
|
|
@ -64,12 +64,13 @@ const ExampleTabsReducer = NavigationReducer.TabsReducer({
|
|||
],
|
||||
});
|
||||
|
||||
const NavigationTabsExample = React.createClass({
|
||||
render: function() {
|
||||
class NavigationTabsExample extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<NavigationRootContainer
|
||||
reducer={ExampleTabsReducer}
|
||||
persistenceKey="NAV_EXAMPLE_STATE_TABS"
|
||||
ref={navRootContainer => { this.navRootContainer = navRootContainer; }}
|
||||
renderNavigation={(navigationState) => {
|
||||
if (!navigationState) { return null; }
|
||||
return (
|
||||
|
@ -88,8 +89,14 @@ const NavigationTabsExample = React.createClass({
|
|||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
handleBackAction() {
|
||||
return (
|
||||
this.navRootContainer &&
|
||||
this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
topView: {
|
||||
|
|
|
@ -109,12 +109,21 @@ var UIExplorerApp = React.createClass({
|
|||
style={styles.toolbar}
|
||||
title={this.state.example.title}
|
||||
/>
|
||||
<Component />
|
||||
<Component
|
||||
ref={(example) => { this._exampleRef = example; }}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
_handleBackButtonPress: function() {
|
||||
if (
|
||||
this._exampleRef &&
|
||||
this._exampleRef.handleBackAction &&
|
||||
this._exampleRef.handleBackAction()
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (this.state.example.title !== this._getUIExplorerHome().title) {
|
||||
this.onSelectExample(this._getUIExplorerHome());
|
||||
return true;
|
||||
|
|
|
@ -54,6 +54,7 @@ var APIS = [
|
|||
require('./IntentAndroidExample.android'),
|
||||
require('./LayoutEventsExample'),
|
||||
require('./LayoutExample'),
|
||||
require('./NavigationExperimental/NavigationExperimentalExample'),
|
||||
require('./NetInfoExample'),
|
||||
require('./PanResponderExample'),
|
||||
require('./PointerEventsExample'),
|
||||
|
|
|
@ -31,10 +31,13 @@ const Animated = require('Animated');
|
|||
const NavigationReducer = require('NavigationReducer');
|
||||
const NavigationContainer = require('NavigationContainer');
|
||||
const PanResponder = require('PanResponder');
|
||||
const Platform = require('Platform');
|
||||
const React = require('React');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const View = require('View');
|
||||
|
||||
const ENABLE_GESTURES = Platform.OS !== 'android';
|
||||
|
||||
import type {
|
||||
NavigationParentState
|
||||
} from 'NavigationState';
|
||||
|
@ -62,7 +65,12 @@ class NavigationCard extends React.Component {
|
|||
_widthListener: string;
|
||||
_heightListener: string;
|
||||
props: Props;
|
||||
componentWillMount(props) {
|
||||
componentWillMount() {
|
||||
if (ENABLE_GESTURES) {
|
||||
this._enableGestures();
|
||||
}
|
||||
}
|
||||
_enableGestures() {
|
||||
this._responder = PanResponder.create({
|
||||
onMoveShouldSetPanResponder: (e, {dx, dy, moveX, moveY, x0, y0}) => {
|
||||
if (this.props.navigationState.index === 0) {
|
||||
|
@ -119,9 +127,10 @@ class NavigationCard extends React.Component {
|
|||
render() {
|
||||
const cardPosition = Animated.add(this.props.position, new Animated.Value(-this.props.index));
|
||||
const gestureValue = Animated.multiply(cardPosition, this.props.layout.width);
|
||||
const touchResponderHandlers = this._responder ? this._responder.panHandlers : null;
|
||||
return (
|
||||
<Animated.View
|
||||
{...this._responder.panHandlers}
|
||||
{...touchResponderHandlers}
|
||||
style={[
|
||||
styles.card,
|
||||
{
|
||||
|
|
|
@ -11,19 +11,29 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
var AsyncStorage = require('AsyncStorage');
|
||||
var React = require('React');
|
||||
const AsyncStorage = require('AsyncStorage');
|
||||
const React = require('React');
|
||||
const BackAndroid = require('BackAndroid');
|
||||
const Platform = require('Platform');
|
||||
|
||||
import type {
|
||||
NavigationState,
|
||||
NavigationReducer
|
||||
} from 'NavigationState';
|
||||
|
||||
type NavigationRenderer = (
|
||||
export type NavigationRenderer = (
|
||||
navigationState: NavigationState,
|
||||
onNavigate: Function
|
||||
) => ReactElement;
|
||||
|
||||
export type BackAction = {
|
||||
type: 'BackAction';
|
||||
};
|
||||
|
||||
function getBackAction(): BackAction {
|
||||
return { type: 'BackAction' };
|
||||
}
|
||||
|
||||
type Props = {
|
||||
renderNavigation: NavigationRenderer;
|
||||
reducer: NavigationReducer;
|
||||
|
@ -61,17 +71,21 @@ class NavigationRootContainer extends React.Component {
|
|||
onNavigate: this.handleNavigation,
|
||||
};
|
||||
}
|
||||
handleNavigation(action: Object) {
|
||||
handleNavigation(action: Object): boolean {
|
||||
const navState = this.props.reducer(this.state.navState, action);
|
||||
if (navState === this.state.navState) {
|
||||
return false;
|
||||
}
|
||||
this.setState({
|
||||
navState,
|
||||
});
|
||||
if (this.props.persistenceKey) {
|
||||
AsyncStorage.setItem(this.props.persistenceKey, JSON.stringify(navState));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
render(): ReactElement {
|
||||
var navigation = this.props.renderNavigation(
|
||||
const navigation = this.props.renderNavigation(
|
||||
this.state.navState,
|
||||
this.handleNavigation
|
||||
);
|
||||
|
@ -83,4 +97,6 @@ NavigationRootContainer.childContextTypes = {
|
|||
onNavigate: React.PropTypes.func,
|
||||
};
|
||||
|
||||
NavigationRootContainer.getBackAction = getBackAction;
|
||||
|
||||
module.exports = NavigationRootContainer;
|
||||
|
|
|
@ -23,9 +23,13 @@ export type NavigationParentState = {
|
|||
children: Array<NavigationState>;
|
||||
};
|
||||
|
||||
export type NavigationAction = {
|
||||
type: string;
|
||||
};
|
||||
|
||||
export type NavigationReducer = (
|
||||
state: ?NavigationState,
|
||||
action: ?any
|
||||
action: ?NavigationAction
|
||||
) => ?NavigationState;
|
||||
|
||||
export type NavigationReducerWithDefault = (
|
||||
|
@ -142,6 +146,9 @@ export function set(state: ?NavigationState, key: string, nextChildren: Array<Na
|
|||
|
||||
export function jumpToIndex(state: NavigationState, index: number): NavigationState {
|
||||
const parentState = getParent(state);
|
||||
if (parentState && parentState.index === index) {
|
||||
return parentState;
|
||||
}
|
||||
return {
|
||||
...parentState,
|
||||
index,
|
||||
|
|
|
@ -22,7 +22,7 @@ import type {
|
|||
NavigationReducer
|
||||
} from 'NavigationState';
|
||||
|
||||
function NavigationFindReducer(reducers: Array<NavigationReducer>): ?NavigationReducer {
|
||||
function NavigationFindReducer(reducers: Array<NavigationReducer>): NavigationReducer {
|
||||
return function(lastState: ?NavigationState, action: ?any): ?NavigationState {
|
||||
for (let i = 0; i < reducers.length; i++) {
|
||||
let reducer = reducers[i];
|
||||
|
|
|
@ -18,7 +18,11 @@ import type {
|
|||
NavigationReducer,
|
||||
} from 'NavigationState';
|
||||
|
||||
export type NavigationStackReducerAction = {
|
||||
import type {
|
||||
BackAction,
|
||||
} from 'NavigationRootContainer';
|
||||
|
||||
export type NavigationStackReducerAction = BackAction | {
|
||||
type: string,
|
||||
};
|
||||
|
||||
|
@ -101,6 +105,7 @@ function NavigationStackReducer({initialStates, initialIndex, key, matchAction,
|
|||
action.state
|
||||
);
|
||||
case ActionTypes.POP:
|
||||
case 'BackAction':
|
||||
if (lastParentState.index === 0 || lastParentState.children.length === 1) {
|
||||
return lastParentState;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,8 @@ const NavigationStateUtils = require('NavigationState');
|
|||
import type {
|
||||
NavigationReducer,
|
||||
NavigationReducerWithDefault,
|
||||
NavigationState
|
||||
NavigationState,
|
||||
NavigationParentState
|
||||
} from 'NavigationState';
|
||||
|
||||
const ActionTypes = {
|
||||
|
@ -106,17 +107,15 @@ function NavigationTabsReducer({key, initialIndex, tabReducers}: TabsReducerConf
|
|||
}
|
||||
}
|
||||
const subReducers = tabReducers.map((tabReducer, tabIndex) => {
|
||||
return function reduceTab(lastTabState: ?NavigationState, tabAction: ?any): ?NavigationState {
|
||||
if (!lastTabState) {
|
||||
return tabReducer(lastTabState, tabAction);
|
||||
return function reduceTab(lastNavState: ?NavigationState, tabAction: ?any): ?NavigationState {
|
||||
if (!tabReducer || !lastNavState) {
|
||||
return lastNavState;
|
||||
}
|
||||
if (!lastParentNavState) {
|
||||
return lastTabState;
|
||||
}
|
||||
const lastSubTabState = lastParentNavState.children[tabIndex];
|
||||
const lastParentNavState = NavigationStateUtils.getParent(lastNavState);
|
||||
const lastSubTabState = lastParentNavState && lastParentNavState.children[tabIndex];
|
||||
const nextSubTabState = tabReducer(lastSubTabState, tabAction);
|
||||
if (nextSubTabState && lastSubTabState !== nextSubTabState) {
|
||||
const tabs = lastParentNavState.children;
|
||||
const tabs = lastParentNavState && lastParentNavState.children || [];
|
||||
tabs[tabIndex] = nextSubTabState;
|
||||
return {
|
||||
...lastParentNavState,
|
||||
|
@ -129,11 +128,17 @@ function NavigationTabsReducer({key, initialIndex, tabReducers}: TabsReducerConf
|
|||
});
|
||||
let selectedTabReducer = subReducers.splice(lastParentNavState.index, 1)[0];
|
||||
subReducers.unshift(selectedTabReducer);
|
||||
subReducers.push((lastParentNavState: ?NavigationState, action: ?any) => {
|
||||
if (lastParentNavState && action && action.type === 'BackAction') {
|
||||
return NavigationStateUtils.jumpToIndex(
|
||||
lastParentNavState,
|
||||
0
|
||||
);
|
||||
}
|
||||
return lastParentNavState;
|
||||
});
|
||||
const findReducer = NavigationFindReducer(subReducers);
|
||||
if (findReducer) {
|
||||
return findReducer(lastParentNavState, action);
|
||||
}
|
||||
return lastParentNavState;
|
||||
return findReducer(lastParentNavState, action);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue