美文网首页React NativeAndroid知识React Native开发经验集
React Native Back自定义物理返回键事件

React Native Back自定义物理返回键事件

作者: 凹凸怪cq | 来源:发表于2017-03-14 12:02 被阅读1751次

基本用法

从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.lastBackPressed && this.lastBackPressed + 2000 >= Date.now()) { 
      //最近2秒内按过back键,可以退出应用。
       return false; 
      } 
      this.lastBackPressed = Date.now(); 
      ToastAndroid.show('再按一次退出应用', ToastAndroid.SHORT); 
      return true; 
  }

如果是二级页面,默认是回到上一个页面,一般不做处理,如果是tab首页或者1级页面,可以做些类似如上的判断

方法2:比较复杂,但是可以统一处理返回事件,结合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 (
      <AppNavigator navigation={addNavigationHelpers({
        dispatch: this.props.dispatch,
        state: this.props.nav,
        addListener,
      })} />
    );
  }
}

const mapStateToProps = (state) => ({
  nav: state.nav
});

const AppWithNavigationState = connect(mapStateToProps)(App);

const store = createStore(
  appReducer,
  applyMiddleware(middleware),
);

class Root extends React.Component {
  render() {
    return (
      <Provider store={store}>
        <AppWithNavigationState />
      </Provider>
    );
  }
}

然后在首页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 <AppNavigation navigation={navigation} />;
  }
}

可参考这个链接

老版本的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;
};

相关文章

网友评论

  • 帆船姑娘:接着楼上继续请教,我在componentWillUnmount里加了 BackHandler.removeEventListener(),可是在跳转其他的页面还是会一直监听,不知道怎么解决。。
    凹凸怪cq:@帆船姑娘 加我QQ吧,不是很明白你的意思,770580039。
    帆船姑娘:@凹凸怪cq 我在 Login 页面添加了BackHandler.addEventListener,回调函数里获取 this.props.navigation.state.routeName,判断出来是当前的路由 "Login"就跳到 home 页。实际效果确实跳到了 home 页又突然跳到了 Login 页,一直无解。。。
    凹凸怪cq:是啊,跳转其他页面是不会销毁上个页面的,不会执行componentWillUnmount,所以可以不用管,你这样现在会导致其他什么问题吗?
  • 会飞的鱼儿_0012:请教一下 使用的最新react-navigation,在第一个页面加了监听返回按钮的代码,往后都每个页面都执行这段代码
    凹凸怪cq:是的,因为这个页面没有销毁,所以会一直存在。所以你需要在监听方法里进行一些判断,当前的路由的index是不是等于0或者routename是不是等于首页的name,如果不是,那就是二级及以后的页面,如果是等于0,那就是首页,然后进行一些相应的处理。

本文标题:React Native Back自定义物理返回键事件

本文链接:https://www.haomeiwen.com/subject/ergcnttx.html