最近在做一个跨平台的项目 , 遇到两个问题 , 第一是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({
:{
flex:1
},
errorInfo:{
flex:1,
justifyContent:"center",
alignItems:'center'
},
errorText:{
fontSize:16,
color:'#666'
},
});
AppRegistry.registerComponent('myApp', () => rootApp);