react-native Android返回键以及 webview 中的回退处理

齐博厚
2023-12-01

最近在做一个跨平台的项目 , 遇到两个问题 , 第一是Android的back键的默认行为是退出应用 , 而在我们的实际应用场景中期望的并非如此 ; 第二就是使用 webview 时 , 我们期望 back 键执行的是 web 页面的 history.go(-1);而不是RN navigator的出栈操作或者退出应用。

1. 解决back键退出应用的问题:

基本用法 : 在 navigator 的 initialRoute 组件中 ( 官方术语 : 我们在App这一级别 ) 绑定事件 , 按back键回退导航栈的功能 , 上代码:

//根组件 , 设置顶层的  navigator , 加载 initialRoute 里的组件
class rootApp extends Component{
    render(){
        return (
            <View style={styles.container}>
                <StatusBar
                    backgroundColor="blue"
                    barStyle="light-content"
                  />
                <Navigator
                    initialRoute={{component: FirstPage}}
                    configureScene={this._configureScene}
                    renderScene={this._renderScene} 
                />
            </View>
        )
    }

    _renderScene(route, navigator) {
        return <route.component navigator={navigator} {...route.passProps} />;
    }

    _configureScene(route, routeStack) {
        return Navigator.SceneConfigs.FloatFromBottom
    }
}
    class FirstPage extends Component{
    // 在 navigator 的第一个组件中 ( 官方术语 : 我们在App这一级别 ) 绑定事件 , 按back键回退导航栈的功能
    componentWillMount(){  
        BackHandler.addEventListener('hardwareBackPress', () => {  
            if (this.props.navigator) {  
                let routes = this.props.navigator.getCurrentRoutes();  
                let lastRoute = routes[routes.length - 1]; // 当前页面对应的route对象  
                if (lastRoute.onHardwareBackPress) {// 先执行route注册的事件  
                    let flag = lastRoute.onHardwareBackPress();  
                    if (flag === false) {// 返回值为false就终止后续操作  
                        return true;  
                    }  
                }    
                if (routes.length === 1) {// 在第一页了,2秒之内点击两次返回键,退出应用  

                    if (this.lastBackPressed && this.lastBackPressed + 2000 >= Date.now()) {
                        //最近2秒内按过back键,可以退出应用。
                        return false;
                    }
                    this.lastBackPressed = Date.now();
                    ToastAndroid.show('再按返回退出应用', ToastAndroid.SHORT); 
                    return true;
                } else {  
                    this.props.navigator.pop();  
                }  
            }  
            return true;  
        });  
    }  
    componentWillUnmount() {  
        if (Platform.OS === 'android') {  
            BackHandler.removeEventListener('hardwareBackPress',()=>{});  
        }  
    }


    render(){
        return (
            <TWebView />
        );
    }
}

以上这样就可以实现按back键退回上一页 , 连续按两次退出应用

2. 实现在使用 webview 时按back键web网页回退

原理是利用 webview 的 onNavigationStateChange 事件返回的canGoBack 属性,判断网页是否可以回退 ; 如果可以回退就使用 webview.goBack() ; 如果不能回退就执行 navigator 的出栈操作。代码和问题1基本相同 , 只需要加入如下部分:

// 在 BackHandler 的 hardwareBackPress 中加入 canGoBack 的判断
// 判断是否是执行 webview 中网页的的回退
if(isBack){
    webview.goBack();
    return true;
}
// 添加 TWebView 组件
class TWebView extends Component{
    constructor(props){
        super(props);
        this.state = {
            url : 'http://www.baidu.com',
            isError:false
        }
    }

    // 获取 webview 事件返回的 canGoBack 属性 , 判断网页是否可以回退
    _onNavigationStateChange (navState){
        if(navState.canGoBack){
            global.isBack = true;
        }else{
            global.isBack = false;
        }
    }

    _showError(){
        this.setState({
            isError : true
        });
    }

    render(){
        return (
            <View style={styles.container}>

                {
                    this.state.isError?
                    <View style={styles.errorInfo}>
                        <Text style={styles.errorText}>网络有问题请检查网络情况,再刷新</Text>
                    </View>
                    :<WebView
                     ref={w => global.webview = w}
                     onNavigationStateChange={this._onNavigationStateChange}
                     style={{marginTop:-20}}
                     onError={this._showError.bind(this)}
                     startInLoadingState={true}
                     source={{uri: this.state.url}}/>
                }
            </View>
        );
    }
}

如此一来已经实现了我们想要的功能 , 为了直观的说明问题 ,让代码变得易懂,我直接使用全局变量 gloabl来传值 : global.isBack = true; 来实现父子组件之间的通讯

附上完整代码(实际项目中拆分多个组件):

import React, { Component } from 'react';
import {
  AppRegistry,
  Platform,
  BackHandler,
  ToastAndroid,
  View,
  StyleSheet,
  StatusBar,
  Text,
  WebView
} from 'react-native';
import { Navigator } from 'react-native-deprecated-custom-components';


//根组件 , 设置顶层的  navigator , 加载 initialRoute 里的组件
class rootApp extends Component{
    render(){
        return (
            <View style={styles.container}>
                <StatusBar
                    backgroundColor="blue"
                    barStyle="light-content"
                  />
                <Navigator
                    initialRoute={{component: FirstPage}}
                    configureScene={this._configureScene}
                    renderScene={this._renderScene} 
                />
            </View>
        )
    }

    _renderScene(route, navigator) {
        return <route.component navigator={navigator}  {...route.passProps} />;
    }

    _configureScene(route, routeStack) {
        return Navigator.SceneConfigs.FloatFromBottom
    }
}

class FirstPage extends Component{
    // 在 navigator 的第一个组件中 ( 官方术语 : 在starter-kit里,我们在App这一级别 ) 绑定事件 , 按back键回退导航栈的功能
    componentWillMount(){  
        BackHandler.addEventListener('hardwareBackPress', () => {  
            if (this.props.navigator) {  
                let routes = this.props.navigator.getCurrentRoutes();  
                let lastRoute = routes[routes.length - 1]; // 当前页面对应的route对象  
                if (lastRoute.onHardwareBackPress) {// 先执行route注册的事件  
                    let flag = lastRoute.onHardwareBackPress();  
                    if (flag === false) {// 返回值为false就终止后续操作  
                        return true;  
                    }  
                }    

                // 判断是否是执行 webview 中网页的的回退
                if(isBack){
                    webview.goBack();
                    return true;
                }

                if (routes.length === 1) {// 在第一页了,2秒之内点击两次返回键,退出应用  
                    //连按两次退出应用
                    if (this.lastBackPressed && this.lastBackPressed + 2000 >= Date.now()) {
                        //最近2秒内按过back键,可以退出应用。
                        return false;
                    }
                    this.lastBackPressed = Date.now();
                    ToastAndroid.show('再按返回退出应用', ToastAndroid.SHORT); 
                    return true;
                } else {  
                    this.props.navigator.pop();  
                }  
            }  
            return true;  
        });  
    }  
    componentWillUnmount() {  
        if (Platform.OS === 'android') {  
            BackHandler.removeEventListener('hardwareBackPress',()=>{});  
        }  
    }


    render(){
        return (
            <TWebView />
        );
    }
}

class TWebView extends Component{
    constructor(props){
        super(props);
        this.state = {
            url : 'http://www.3mentuan.cn/site/login',
            isError:false
        }
    }
    _onNavigationStateChange (navState){
        if(navState.canGoBack){
            global.isBack = true;
        }else{
            global.isBack = false;
        }
    }

    _showError(){
        this.setState({
            isError : true
        });
    }

    render(){
        return (
            <View style={styles.container}>

                {
                    this.state.isError?
                    <View style={styles.errorInfo}>
                        <Text style={styles.errorText}>网络有问题请检查网络情况,再刷新</Text>
                    </View>
                    :<WebView
                     ref={w => global.webview = w}
                     onNavigationStateChange={this._onNavigationStateChange}
                     style={{marginTop:-20}}
                     onError={this._showError.bind(this)}
                     startInLoadingState={true}
                     source={{uri: this.state.url}}/>
                }
            </View>
        );
    }
}


const styles = StyleSheet.create({
    container:{
        flex:1
    },
    errorInfo:{
        flex:1,
        justifyContent:"center",
        alignItems:'center'     
    },
    errorText:{
        fontSize:16,
        color:'#666'
    },
});


AppRegistry.registerComponent('myApp', () => rootApp);
 类似资料: