【问题标题】:React Native - Device back button handlingReact Native - 设备后退按钮处理
【发布时间】:2017-12-15 07:29:01
【问题描述】:

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

我检查了示例的数量,但这些示例使用 BackAndroidNavigator。但是这两个都被弃用了。 BackHandlerBackAndroid 的替代品。我可以使用props.navigation.goBack(null) 显示上一个屏幕。

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

【问题讨论】:

标签: react-native navigation back-button back


【解决方案1】:

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

案例 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;
}

重要提示:不要忘记在构造函数中绑定方法并在componentWillUnmount中移除监听器。

案例 2:退出应用程序

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

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

【讨论】:

  • this.props.navigation.goBack(null);在我的情况下显示一个空白屏幕。可能是什么原因造成的?当执行“this.props.navigation.goBack(null)”时,我有一个 tabView 嵌套在 stackView 和我的 tabView 上,出现空白屏幕。
  • 当我使用此代码时,我收到错误消息“无法读取未定义的属性导航”,可能是什么问题?
  • 每当我从任何屏幕点击设备后退按钮时,我的应用程序都会直接退出。
  • 如果我们以这种方式处理,我们必须为所有屏幕处理它。有没有办法全局处理????
  • 如果你像这样使用 arrow 函数,你不必bind 方法:handleBackButtonClick = () => {...}
【解决方案2】:

在功能组件中:

import { BackHandler } from 'react-native';

function handleBackButtonClick() {
    navigation.goBack();
    return true;
  }

  useEffect(() => {
    BackHandler.addEventListener('hardwareBackPress', handleBackButtonClick);
    return () => {
      BackHandler.removeEventListener('hardwareBackPress', handleBackButtonClick);
    };
  }, []);

【讨论】:

    【解决方案3】:
     import { BackHandler } from 'react-native';
      
     constructor() {
            super();           
            this.handleBackButtonClick = this.handleBackButtonClick.bind(this);
     }
    
       componentWillMount() {
           BackHandler.addEventListener('hardwareBackPress', this.handleBackButtonClick);
       }
    
       componentWillUnmount() {
           BackHandler.removeEventListener('hardwareBackPress', this.handleBackButtonClick);
       }
    
       handleBackButtonClick() {
           //this.props.navigation.goBack(null);
           BackHandler.exitApp();
           return true;
       }
    
       handleBackButtonClick() {
           return true;   // when back button don't need to go back 
       }
    

    在功能组件中

    import { BackHandler } from 'react-native';
    
    function handleBackButtonClick() {
        navigation.goBack();
        return true;
      }
    
      useEffect(() => {
        BackHandler.addEventListener('hardwareBackPress', handleBackButtonClick);
        return () => {
          BackHandler.removeEventListener('hardwareBackPress', handleBackButtonClick);
        };
      }, []);
    

    【讨论】:

      【解决方案4】:

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

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

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

      stack navigator 的路由器的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

      【讨论】:

      • 当堆栈中只有一屏时,我得到 1 的值 state.index
      • 尝试使用控制台日志打印state。如果您有 1 个作为索引,那么您肯定会在堆栈中看到 2 个屏幕。
      【解决方案5】:

      这是我使用特定条件成功实现的方法:

      componentWillMount() {
          BackHandler.addEventListener(
            'hardwareBackPress',
            this.handleBackButtonClick,
          );
        }
      
        componentWillUnmount() {
          BackHandler.removeEventListener(
            'hardwareBackPress',
            this.handleBackButtonClick,
          );
        }
      
        handleBackButtonClick = () => {
          //some condition
          if (this.state.isSearchBarActive) {
            this.setState({
              isSearchBarActive: false,
            });
            this.props.navigation.goBack(null);
            return true;
          }
          return false;
        };
      

      【讨论】:

        【解决方案6】:

        试试这个 反应导航

        componentDidMount() {
                BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
            }
        
        
            handleBackButton = () => {
        
                const pushAction = StackActions.push({
                    routeName: 'DefaultSelections',
                });
        
                this.props.navigation.dispatch(pushAction);
            }
        

        当前屏幕是 "DefaultSelections" ,在按下后退按钮时,将切换到相同的状态,因此后退按钮禁用工作,就像禁用后退按钮一样

        return true
        

        for backButton(根据官方文档的建议)禁用所有屏幕上的后退按钮;不想要

        【讨论】:

        • 但是如何禁用它的默认行为
        【解决方案7】:

        我在 react-native 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 变量保持为真并调用 .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();
          }
        });
        

        只需使用 .forEach 即可,它是 PR 上的原始实现,之后修改后的 Array.from 语法始终有效。

        因此,您可以分叉 react-native 并使用修改后的版本,提交 PR 虽然我认为这需要一些时间才能在上游获得批准和合并,或者您可以做一些类似于我所做的事情,即覆盖用于hardwareBackPress 事件的RCTDeviceEventEmitter.addListener(...)。

        // 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 /> }
        }
        

        【讨论】:

          【解决方案8】:

          React Native Hooks 有一个很好的 useBackHandler 钩子,它简化了为 Android 后退按钮设置事件侦听器的过程。

          import { useBackHandler } from '@react-native-community/hooks'
          
          useBackHandler(() => {
            if (shouldBeHandledHere) {
              // handle it
              return true
            }
            // let the default thing happen
            return false
          })
          

          【讨论】:

            【解决方案9】:
            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,
                            })
                        }
                    />
                );
            }
            

            【讨论】:

              【解决方案10】:

              一个实用函数可能会很有帮助:

              backPressHandler.js

              import React from 'react';
              import {BackHandler} from 'react-native';
              const onBackPress = (callback) => {
                BackHandler.addEventListener('hardwareBackPress', callback);
                return () => {
                  BackHandler.removeEventListener('hardwareBackPress', callback);
                };
              };
              
              export {onBackPress};
              
              

              现在在我的屏幕上:

              myScreen.js

              import {onBackPress} from '../utils/backPressHandler';
              
                function handleBackPress() {
                  navigation.goBack();
                  return true;
                }
                useEffect(() => {
                  onBackPress(handleBackPress);
                }, []);
              
              

              【讨论】:

                【解决方案11】:

                我使用通量进行导航。

                    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;
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2020-08-22
                  • 1970-01-01
                  • 1970-01-01
                  • 2019-05-14
                  • 1970-01-01
                  • 2019-04-22
                  • 1970-01-01
                  相关资源
                  最近更新 更多