基本用法
从44版本以后RN已经废弃了BackAndroid API,加了新的组件BackHandler,老版本的可以直接把BackAndroid替换成BackHandler就行。
根据文档,安卓back键的处理主要就是一个事件监听:
BackHandler. addEventListener('hardwareBackPress', this.onBackPressed);
BackHandler. removeEventListener('hardwareBackPress', this.onBackPressed);
在App里,二级后的页面系统默认是实现了back键按顺序回退导航栈的功能,其实可不用管,这里具体说下新版本react-navigation和老版本navigator路由的情况下的用法
新版本的react-navigation使用用法
方法1:在你需要的页面加上返回事件判断(一般只加在首页)
class ScreenWithCustomBackBehavior extends React.Component {
componentDidMount() {
BackHandler.addEventListener('hardwareBackPress',
this.onBackButtonPressAndroid);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress',
this.onBackButtonPressAndroid);
}
onBackButtonPressAndroid = () => {
if (this.props.navigation.isFocused()) {
if (this.lastBackPressed && this.lastBackPressed + 2000 >= Date.now()) {
//最近2秒内按过back键,可以退出应用。
return false;
}
this.lastBackPressed = Date.now();
ToastAndroid.show('再按一次退出应用', ToastAndroid.SHORT);
return true;
}
}
发现有些版本在componentWillUnmount里执行removeEventListener并没有用,还是会监听到,所以这个时候可以换一种写法:
componentDidMount() {
this.backHandler = BackHandler.addEventListener('hardwareBackPress',
this.onBackButtonPressAndroid);
}
componentWillUnmount() {
this.backHandler&&this.backHandler.remove();
}
如果是二级页面,默认是回到上一个页面,一般不做处理,如果是tab首页或者1级页面,可以做些类似如上的判断。还有个建议,当进入到二级以上的页面需要监听返回物理键时候,当前是不好监听到活跃的是在哪个页面,所以安卓的物理键返回处理事件统一在componentWillUnmount里执行较好,有个缺点就是会先看到上一个页面然后在执行componentWillUnmount,有个延期过程。主要还是期待后面导航栏能够监听到当前进行到了哪个活跃页面。
方法2: 在你的项目导航栏首页里自定义导航路由:
const MyApp = StackNavigator({
Home: { screen: HomeScreen },
Profile: { screen: ProfileScreen },
}, {
initialRouteName: 'Home',
})
const defaultGetStateForAction = MyApp.router.getStateForAction;
MyApp.router.getStateForAction = (action, state) => {
if (state&&state.routes.length==1&&action.type=='Navigation/BACK'){
//在项目首页并且按了物理键返回
BackHandler.exitApp()
}
return defaultGetStateForAction(action, state);
};
方法3:比较复杂,但是可以统一处理返回事件,结合redux使用
yarn add react-navigation-redux-helpers
or
npm install --save react-navigation-redux-helpers
import {
StackNavigator,
addNavigationHelpers,
} from 'react-navigation';
import {
createStore,
applyMiddleware,
combineReducers,
} from 'redux';
import {
createReduxBoundAddListener,
createReactNavigationReduxMiddleware,
} from 'react-navigation-redux-helpers';
import { Provider, connect } from 'react-redux';
import React from 'react';
const AppNavigator = StackNavigator(AppRouteConfigs);
const initialState = AppNavigator.router.getStateForAction(AppNavigator.router.getActionForPathAndParams('Login'));
const navReducer = (state = initialState, action) => {
const nextState = AppNavigator.router.getStateForAction(action, state);
// Simply return the original `state` if `nextState` is null or undefined.
return nextState || state;
};
const appReducer = combineReducers({
nav: navReducer,
...
});
// Note: createReactNavigationReduxMiddleware must be run before createReduxBoundAddListener
const middleware = createReactNavigationReduxMiddleware(
"root",
state => state.nav,
);
const addListener = createReduxBoundAddListener("root");
class App extends React.Component {
render() {
return (
);
}
}
const mapStateToProps = (state) => ({
nav: state.nav
});
const AppWithNavigationState = connect(mapStateToProps)(App);
const store = createStore(
appReducer,
applyMiddleware(middleware),
);
class Root extends React.Component {
render() {
return (
);
}
}
然后在首页AppNavigator里统一处理返回事件
import React from "react";
import { BackHandler } from "react-native";
import { addNavigationHelpers, NavigationActions } from "react-navigation";
const AppNavigation = TabNavigator(
{
Home: { screen: HomeScreen },
Settings: { screen: SettingScreen }
}
);
class ReduxNavigation extends React.Component {
componentDidMount() {
BackHandler.addEventListener("hardwareBackPress", this.onBackPress);
}
componentWillUnmount() {
BackHandler.removeEventListener("hardwareBackPress", this.onBackPress);
}
onBackPress = () => {
const { dispatch, nav } = this.props;
if (nav.index === 0) {
return false;
}
dispatch(NavigationActions.back());
return true;
};
render() {
const { dispatch, nav } = this.props;
const navigation = addNavigationHelpers({
dispatch,
state: nav,
addListener,
});
return ;
}
}
可参考这个链接
老版本的navigator使用用法
export default class APP extends Component {
componentWillMount() {
if (Platform.OS === 'android') {
BackHandler.addEventListener('hardwareBackPress',this.onBackAndroid);
}
}
componentWillUnmount() {
if (Platform.OS === 'android') {
BackHandler.removeEventListener('hardwareBackPress', this.onBackAndroid);
}
}
onBackAndroid = () => {
const nav = this.navigator;
const routers = nav.getCurrentRoutes();
if (routers.length > 1) {
nav.pop();
return true;
}
return false;
}
注意这里为了方便后续removeEventListener,采用了用绑定this的函数属性的方法来创建回调函数,而非箭头函数或者bind(this)
代码中,当componentWillMount的时候挂接事件。对于应用根组件来说,这个生命周期就基本和我们应用的生命周期一致了。当back键被按下的时候,首先检查当前的导航栈,如果多余一个页面,则退回顶部的页面。
说明:BackHandler在iOS平台下是一个空实现,所以理论上不做这个Platform.OS === 'android'判断也是安全的。
使用默认行为/退出应用
back键的默认行为是如果栈里就只有一个页面,就退出应用了。我们通常需要判断某些条件,并最后决定是否要退出应用。上文中的例子就使用了第一种调用默认行为的方法:
如果所有事件监听函数中,没有任何一个返回真值,就会默认调用默认行为
如果你只挂接了一个监听函数,那么你的返回值就决定了是否要调用默认行为:true为不调用,false为调用。
在上文代码中,我们如果导航栈多于一个页面,就不调用默认行为,而如果只有一个页面,则调用默认界面。
例子:“再按一次退出应用”
常有这种需求:按下back键以后,弹出一个toast,然后在一定时间内再按一次,才退出应用。这个代码就可以这样写:
onBackAndroid = () => {
if (this.lastBackPressed && this.lastBackPressed + 2000 >= Date.now()) {
//最近2秒内按过back键,可以退出应用。
return false;
}
this.lastBackPressed = Date.now();
ToastAndroid.show('再按一次退出应用', ToastAndroid.SHORT);
return true;
};
还有一种情况,我们在监听函数中不能决定是否要调用默认行为,要等待一个异步操作之后才调用默认行为,此时可以通过第二种办法:
使用 BackHandler. exitApp()__ 来退出应用。
例子:在退出应用之前保存数据
写法1:
onBackAndroid = () =>{
saveData().then(()=>{
BackHandler.exitApp();
});
return true;
}
在监听函数中,我们开始异步事件,并直接return true。此时默认行为不会被调用。当保存完毕后,我们调用exitApp(),触发默认行为,退出应用。
写法2:
onBackAndroid = async () =>{
await saveData();
BackHandler.exitApp();
}
这里我们用了async函数,async 函数总是返回一个Promise,Promise作为一个对象,也被认为是一个“真值”,所以这种情况下默认行为总是不会被调用。当保存完毕后,我们调用exitApp(),触发默认行为,退出应用。
根据当前界面决定作何动作
有时候我们有这样的需求:当用户处于某些界面下时,back键要做特殊的动作,如:提示用户是否要保存数据,或者解锁界面禁止back键返回等等。此时,最佳实践是在route或route中对应的Component上保存关于如何处理back键的信息:
onBackAndroid = () => {
const nav = this.navigator;
const routers = nav.getCurrentRoutes();
if (routers.length > 1) {
const top = routers[routers.length - 1];
if (top.ignoreBack || top.component.ignoreBack){
// 路由或组件上决定这个界面忽略back键
return true;
}
const handleBack = top.handleBack || top.component.handleBack;
if (handleBack) {
// 路由或组件上决定这个界面自行处理back键
return handleBack(); }
// 默认行为: 退出当前界面。
nav.pop();
return true;
}
return false;
};