React Native - 设备后退按钮处理

问题描述 投票:14回答:7

我想检查当设备后退按钮被击中时是否有多个屏幕在堆栈上。如果是,我想显示前一个屏幕,如果没有,我想退出应用程序。

我已经检查了一些示例,但是那些使用了BackAndroid和Navigator。但他们俩都被弃用了。 BackHandler是BackAndroid的替代品。我可以使用props.navigation.goBack(null)显示上一个屏幕。

但是我无法找到用于在堆栈中查找屏幕计数的代码。我不想使用已弃用的Navigator!

react-native navigation back-button back react-native-android
7个回答
36
投票

此示例将向您显示通常在大多数流程中预期的返回导航。您必须根据预期的行为向每个屏幕添加以下代码。有两种情况:1。如果堆叠屏幕超过1个,设备后退按钮将显示前一个屏幕。 2.如果堆叠中只有一个屏幕,设备后退按钮将退出应用程序。

案例1:显示上一个屏幕

import { BackHandler } from 'react-native';

constructor(props) {
    super(props)
    this.handleBackButtonClick = this.handleBackButtonClick.bind(this);
}

componentWillMount() {
    BackHandler.addEventListener('hardwareBackPress', this.handleBackButtonClick);
}

componentWillUnmount() {
    BackHandler.removeEventListener('hardwareBackPress', this.handleBackButtonClick);
}

handleBackButtonClick() {
    this.props.navigation.goBack(null);
    return true;
}

重要提示:不要忘记在构造函数中绑定方法并删除co​​mponentWillUnmount中的侦听器。

案例2:退出应用程序

在这种情况下,无需在要退出应用程序的屏幕上处理任何内容。

重要提示:这应该只是堆栈屏幕。


3
投票

在堆栈中堆叠多个屏幕的情况下,react-native中的默认后退按钮行为是导航回堆栈中的上一个屏幕。当只有一个屏幕退出应用程序时,按下设备后退按钮需要自定义设置。然而,这可以通过修改特定StackNavigator路由器的getStateForAction方法而不必向每个屏幕添加处理代码来实现。

假设您在应用程序中使用了以下StackNavigator

const ScreenStack = StackNavigator(
  {
    'Screen1': {
      screen: Screen1
    },
    'Screen2': {
      screen: Screen2
    },
  },
  {
    initialRouteName: 'Screen1'
  }
);

可以按如下方式修改堆栈导航器路由器的getStateForAction方法,以实现预期的反向行为。

const defaultStackGetStateForAction =
  ScreenStack.router.getStateForAction;

ScreenStack.router.getStateForAction = (action, state) => {
  if(state.index === 0 && action.type === NavigationActions.BACK){
    BackHandler.exitApp();
    return null;
  }

  return defaultStackGetStateForAction(action, state);
};

只有当堆叠中有一个屏幕时,state.index才会变成0


2
投票

Guyz请理解它可能不仅仅是本地反应的问题。将它与firebase集成时要小心。最近的firebase版本存在在react-native应用程序中集成后退按钮的问题!请将firebase版本降级到firebase-version @ 5.0.3,然后重新检查它是否有效!我有同样的问题,并担心好几天。我终于降级到@ 5.0.3版本,现在后退按钮完全正常!如果仍然遇到问题,您可以降级到较低版本。


1
投票

我在反应原生的v0.46.0并且有同样的问题。我在react-native代码库中将问题跟踪到此文件

https://github.com/facebook/react-native/blob/master/Libraries/Utilities/BackHandler.android.js#L25

当使用chrome调试器运行时关闭了该行

var subscriptions = Array.from(_backPressSubscriptions.values()).reverse()

始终为订阅返回一个空数组,这反过来导致invokeDefault变量保持为true并调用.exitApp()函数。

经过更多的调查,我认为这个问题是在以下PR #15182中发现和讨论的。

即使在较旧版本的RN中复制/粘贴PR更改之后,它也不太可能由PR中描述的问题引起。

经过一些非常轻微的修改,我通过改变来实现它

RCTDeviceEventEmitter.addListener(DEVICE_BACK_EVENT, function() {
  var invokeDefault = true;
  var subscriptions = []
  _backPressSubscriptions.forEach(sub => subscriptions.push(sub))

  for (var i = 0; i < subscriptions.reverse().length; ++i) {
    if (subscriptions[i]()) {
      invokeDefault = false;
      break;
    }
  }

  if (invokeDefault) {
    BackHandler.exitApp();
  }
});

在修改后的Array.from语法之前,只需使用.forEach就是PR上的原始实现。

所以你可以fork react-native并使用修改后的版本,提交一个PR,虽然我想这需要一段时间才能被批准并合并到上游,或者你可以做类似于我所做的事情来覆盖RCTDeviceEventEmitter.addListener (...)用于hardwareBackPress事件。

// other imports
import { BackHandler, DeviceEventEmitter } from 'react-native'

class MyApp extends Component {
  constructor(props) {
    super(props)
    this.backPressSubscriptions = new Set()
  }

  componentDidMount = () => {
    DeviceEventEmitter.removeAllListeners('hardwareBackPress')
    DeviceEventEmitter.addListener('hardwareBackPress', () => {
      let invokeDefault = true
      const subscriptions = []

      this.backPressSubscriptions.forEach(sub => subscriptions.push(sub))

      for (let i = 0; i < subscriptions.reverse().length; i += 1) {
        if (subscriptions[i]()) {
          invokeDefault = false
          break
        }
      }

      if (invokeDefault) {
        BackHandler.exitApp()
      }
    })

    this.backPressSubscriptions.add(this.handleHardwareBack)
  }

  componentWillUnmount = () => {
    DeviceEventEmitter.removeAllListeners('hardwareBackPress')
    this.backPressSubscriptions.clear()
  }

  handleHardwareBack = () => { /* do your thing */ }

  render() { return <YourApp /> }
}

1
投票

试试这个反应导航

componentDidMount() {
        BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
    }


    handleBackButton = () => {

        const pushAction = StackActions.push({
            routeName: 'DefaultSelections',
        });

        this.props.navigation.dispatch(pushAction);
    }

当前屏幕是“DefaultSelections”,按下后退按钮,将被切换到相同的,因此后退按钮禁用工作,因为禁用后退按钮

return true

对于后退按钮(由官方文档建议)禁用所有屏幕上的后退按钮;不想要


0
投票
constructor(props){
    super(props)
    this.onBackPress = this.onBackPress.bind(this);
}

componentWillMount() {
        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;
    return(
        <DrawerRouter
            navigation= {
                addNavigationHelpers({
                    dispatch,
                    state: nav,
                    addListener,
                })
            }
        />
    );
}

-1
投票

我使用焊剂进行导航。

    const RouterComp = () => {

    let backLoginScene=false;

    return (

        <Router
        backAndroidHandler={() => {
            const back_button_prohibited = ['login','userInfo','dashboard'];
            if (back_button_prohibited.includes(Actions.currentScene) ) {
                if (backLoginScene == false) {
                    ToastAndroid.show("Click back again to exit.", ToastAndroid.SHORT);
                    backLoginScene = !backLoginScene;
                    setTimeout(() => {
                        backLoginScene = false;
                    }, 2000);
                    return true;
                } else {
                    backLoginScene = false;
                    BackHandler.exitApp();
                }
                return false;
            }}}>
            <Scene key='root' hideNavBar>
                <Scene key='guest' hideNavBar >
                    <Scene key='login' component={Login} ></Scene>
                    <Scene key='userInfo' component={UserInfo}></Scene>
                </Scene>

                <Scene key='user' hideNavBar>
                    <Scene key='dashboard' component={Dashboard} title='Dashboard' initial />
                    <Scene key='newAd' component={NewAd} title='New Ad' />

                </Scene>



            </Scene>
        </Router>
    )
}

export default RouterComp;
© www.soinside.com 2019 - 2024. All rights reserved.