React Native:双击退出应用程序
React Native: Double back press to Exit App
如何在不需要 Redux 的情况下通过单击后退按钮退出应用程序
我一直在寻找一种解决方案来限制用户,不要在 React Native 中一键退出应用程序。
import React, {Component} from 'react';
import {BackHandler, View, Dimensions, Animated, TouchableOpacity, Text} from 'react-native';
let {width, height} = Dimensions.get('window');
export default class App extends Component<Props> {
state = {
backClickCount: 0
};
constructor(props) {
super(props);
this.springValue = new Animated.Value(100) ;
}
componentWillMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton.bind(this));
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton.bind(this));
}
_spring() {
this.setState({backClickCount: 1}, () => {
Animated.sequence([
Animated.spring(
this.springValue,
{
toValue: -.15 * height,
friction: 5,
duration: 300,
useNativeDriver: true,
}
),
Animated.timing(
this.springValue,
{
toValue: 100,
duration: 300,
useNativeDriver: true,
}
),
]).start(() => {
this.setState({backClickCount: 0});
});
});
}
handleBackButton = () => {
this.state.backClickCount == 1 ? BackHandler.exitApp() : this._spring();
return true;
};
render() {
return (
<View style={styles.container}>
<Text>
container box
</Text>
<Animated.View style={[styles.animatedView, {transform: [{translateY: this.springValue}]}]}>
<Text style={styles.exitTitleText}>press back again to exit the app</Text>
<TouchableOpacity
activeOpacity={0.9}
onPress={() => BackHandler.exitApp()}
>
<Text style={styles.exitText}>Exit</Text>
</TouchableOpacity>
</Animated.View>
</View>
);
}
}
const styles = {
container: {
flex: 1,
justifyContent: "center",
alignItems: "center"
},
animatedView: {
width,
backgroundColor: "#0a5386",
elevation: 2,
position: "absolute",
bottom: 0,
padding: 10,
justifyContent: "center",
alignItems: "center",
flexDirection: "row",
},
exitTitleText: {
textAlign: "center",
color: "#ffffff",
marginRight: 10,
},
exitText: {
color: "#e5933a",
paddingHorizontal: 10,
paddingVertical: 3
}
};
运行 在 snack.expo 中:https://snack.expo.io/HyhD657d7
下面的代码自行解释。
诀窍是将它放在 Main AppContainer 而不是 每个页面
import { Alert, BackHandler, ToastAndroid } from 'react-native';
import { StackActions } from 'react-navigation';
// common statless class variable.
let backHandlerClickCount = 0;
class App extends React.Component {
constructor(props) {
super(props);
// add listener to didFocus
this._didFocusSubscription = props.navigation.addListener('didFocus', payload =>
BackHandler.addEventListener('hardwareBackPress', () => this.onBackButtonPressAndroid(payload)));
}
// remove listener on unmount
componentWillUnmount() {
if (this._didFocusSubscription) {
this._didFocusSubscription.remove();
}
}
onBackButtonPressAndroid = () => {
const shortToast = message => {
ToastAndroid.showWithGravityAndOffset(
message,
ToastAndroid.SHORT,
ToastAndroid.BOTTOM,
25,
50
);
const {
clickedPosition
} = this.state;
backHandlerClickCount += 1;
if ((clickedPosition !== 1)) {
if ((backHandlerClickCount < 2)) {
shortToast('Press again to quit the application!');
} else {
BackHandler.exitApp();
}
}
// timeout for fade and exit
setTimeout(() => {
backHandlerClickCount = 0;
}, 2000);
if (((clickedPosition === 1) &&
(this.props.navigation.isFocused()))) {
Alert.alert(
'Exit Application',
'Do you want to quit application?', [{
text: 'Cancel',
onPress: () => console.log('Cancel Pressed'),
style: 'cancel'
}, {
text: 'OK',
onPress: () => BackHandler.exitApp()
}], {
cancelable: false
}
);
} else {
this.props.navigation.dispatch(StackActions.pop({
n: 1
}));
}
return true;
}
}
import React, { Component } from 'react'
import { Text, View, StyleSheet, TouchableOpacity,BackHandler} from 'react-native'
import { Toast } from "native-base";
class Dashboard extends Component {
state={
clickcount:0
}
componentDidMount(){
BackHandler.addEventListener("hardwareBackPress",()=>{
this.setState({'clickcount':this.state.clickcount+1})
this.check();
return true
})
}
check=()=>{
if(this.state.clickcount<2){
Toast.show({
text:`Press back again to exit App `,
duration:2000,
onClose:()=>{this.setState({'clickcount':0})}
})
}
else if(this.state.clickcount==2)
{
BackHandler.exitApp()
}
}
render() {
return (
<View style={styles.container}>
<Text> Hello this is the Dashboard Screen</Text>
</View>
)
}
}
export default Dashboard
const styles = StyleSheet.create({
container:{
flex:1,
marginTop:25,
justifyContent:'center',
alignItems:'center',
borderBottomLeftRadius:30,
borderBottomRightRadius:30,
backgroundColor:'#fff'
},
});
更好的方法是简单地使用 BackHandler 和 ToastAndroid
import { BackHandler, ToastAndroid } from 'react-native';
//rest of imports
class SomeClass extends Component{
constructor(state, props) {
super(state, props)
this.state = {
validCloseWindow: false
}
}
async componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton.bind(this));
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton.bind(this));
}
handleBackButton = () => {
if (!this.props.navigation.canGoBack()) {
if (this.state.validCloseWindow)
return false;
this.state.validCloseWindow = true
setTimeout(() => {
this.state.validCloseWindow = false
}, 3000);
ToastAndroid.show("Press Again To Exit !", ToastAndroid.SHORT);
return true;
}
};
//rest of component code
}
只需确保在导航的初始路线页面上使用它即可。
我已经将它作为分离的功能组件以这种方式解决了。这样您就不需要为每个应用程序重新编码,只需在您的新应用程序中包含该组件即可!
import * as React from 'react';
import {useEffect, useState} from 'react';
import {Platform, BackHandler, ToastAndroid} from 'react-native';
export const ExecuteOnlyOnAndroid = (props) => {
const {message} = props;
const [exitApp, setExitApp] = useState(0);
const backAction = () => {
setTimeout(() => {
setExitApp(0);
}, 2000); // 2 seconds to tap second-time
if (exitApp === 0) {
setExitApp(exitApp + 1);
ToastAndroid.show(message, ToastAndroid.SHORT);
} else if (exitApp === 1) {
BackHandler.exitApp();
}
return true;
};
useEffect(() => {
const backHandler = BackHandler.addEventListener(
'hardwareBackPress',
backAction,
);
return () => backHandler.remove();
});
return <></>;
};
export default function DoubleTapToClose(props) {
const {message = 'tap back again to exit the App'} = props;
return Platform.OS !== 'ios' ? (
<ExecuteOnlyOnAndroid message={message} />
) : (
<></>
);
}
=> 您唯一需要做的就是将此组件包含在您的应用程序中。 <=
因为 IOS 没有后退按钮,IOS 不需要此功能。以上组件自动检测 Device 是否为 Android。
默认情况下,Toast 中显示的消息是用英语预定义的,但是如果您将名为 message
的 属性 添加到您的 DoubleTapToClose-Component,您可以设置自己的消息。
...
import DoubleTapToClose from '../lib/android_doubleTapToClose';
...
return(
<>
<DoubleTapToClose />
...other Stuff goes here
</>
根据您的导航结构,您必须检查您是否在应用程序的初始屏幕上。
就我而言,我有一个抽屉,里面有多个 StackNavigatiors。
所以我检查当前屏幕是否是初始屏幕(索引:0)。如果是,我设置了一个 Hook-Variable,它将只用于那些初始屏幕。
看起来像这样:
const isCurrentScreenInitialOne = (state) => {
const route = state.routes[state.index];
if (route.state) {
// Dive into nested navigators
return isCurrentScreenInitialOne(route.state);
}
return state.index === 0;
};
...
...
export default function App() {
...
const [isInitialScreen, setIsInitialScreen] = useState(true);
{isInitialScreen && (<DoubleTapToClose message="Tap again to exit app" />)}
...
...
<NavigationContainer
...
onStateChange={(state) => {
setIsInitialScreen(isCurrentScreenInitialOne(state));
}}>
如果该描述对您有帮助,不要错过投票。
目前最简单的做法:
在App.js:
componentDidMount() {
const backHandler=BackHandler.addEventListener('hardwareBackPress', ()=>{
if(this.backHandler){
return false;
}
Toast.show('再按一次退出应用');
this.backHandler=backHandler;
setTimeout(()=>{
this.backHandler=null;
},2000);
return true;
});
}
componentWillUnmount() {
this.backHandler.remove();
}
抱歉,如果我迟到了,但我有类似的要求并通过创建我自己的自定义挂钩解决了它!
let currentCount = 0;
export const useDoubleBackPressExit = (exitHandler: () => void) => {
if (Platform.OS === "ios") return;
const subscription = BackHandler.addEventListener("hardwareBackPress", () => {
if (currentCount === 1) {
exitHandler();
subscription.remove();
return true;
}
backPressHandler();
return true;
});
};
const backPressHandler = () => {
if (currentCount < 1) {
currentCount += 1;
WToast.show({
data: "Press again to close!",
duration: WToast.duration.SHORT,
});
}
setTimeout(() => {
currentCount = 0;
}, 2000);
};
现在我可以在任何地方使用它,只需执行以下操作:
useDoubleBackPressExit(() => {
// user has pressed "back" twice. Do whatever you want!
});
我在我的应用程序中使用的最简单的解决方案是这个。它适用于 react-navigation 4.4.1,并且比此处提供的其他一些正确答案短得多。
import React from 'react';
import { BackHandler, ToastAndroid} from 'react-native';
export default class LoginScreen extends React.Component {
state = {
canBeClosed: false
}
componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
}
handleBackButton = () => {
if (this.props.navigation.isFocused()) {
if (this.state.canBeClosed)
return this.state.canBeClosed = false;
else {
setTimeout(() => { this.state.canBeClosed = false }, 3000);
ToastAndroid.show("Press Again To Exit !", ToastAndroid.SHORT);
return this.state.canBeClosed = true
}
}
};
//some code
}
最简单的
import { useNavigationState } from '@react-navigation/native';
import { BackHandler, Alert } from 'react-native';
//index to get index of home screen when index == 0 in navigation stack
const index = useNavigationState(state => state.index);
const backAction = () => {
Alert.alert("Hold on!", "Are you sure you want to Exit App?", [
{
text: "Cancel",
onPress: () => null,
style: "cancel"
},
{ text: "YES", onPress: () => BackHandler.exitApp() }
]);
return true;
};
useEffect(() => {
// if index==0 this is initial screen 'Home Screen'
if (index == 0) {
BackHandler.addEventListener("hardwareBackPress", backAction);
return () =>
BackHandler.removeEventListener("hardwareBackPress", backAction);
}
}, [index]);
最简单的方法(直接粘贴到你的功能组件中):
const navigation = useNavigation();
const navIndex = useNavigationState(s => s.index);
const [backPressCount, setBackPressCount] = useState(0);
const handleBackPress = useCallback(() => {
if (backPressCount === 0) {
setBackPressCount(prevCount => prevCount + 1);
setTimeout(() => setBackPressCount(0), 2000);
ToastAndroid.show('Press one more time to exit', ToastAndroid.SHORT);
} else if (backPressCount === 1) {
BackHandler.exitApp();
}
return true;
}, [backPressCount]);
useEffect(() => {
if (Platform.OS === 'android' && navIndex === 0) {
const backListener = BackHandler.addEventListener(
'hardwareBackPress',
handleBackPress,
);
return backListener.remove;
}
}, [handleBackPress]);
您也可以将其包装在自定义挂钩中并在组件中使用挂钩。
如何在不需要 Redux 的情况下通过单击后退按钮退出应用程序
我一直在寻找一种解决方案来限制用户,不要在 React Native 中一键退出应用程序。
import React, {Component} from 'react';
import {BackHandler, View, Dimensions, Animated, TouchableOpacity, Text} from 'react-native';
let {width, height} = Dimensions.get('window');
export default class App extends Component<Props> {
state = {
backClickCount: 0
};
constructor(props) {
super(props);
this.springValue = new Animated.Value(100) ;
}
componentWillMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton.bind(this));
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton.bind(this));
}
_spring() {
this.setState({backClickCount: 1}, () => {
Animated.sequence([
Animated.spring(
this.springValue,
{
toValue: -.15 * height,
friction: 5,
duration: 300,
useNativeDriver: true,
}
),
Animated.timing(
this.springValue,
{
toValue: 100,
duration: 300,
useNativeDriver: true,
}
),
]).start(() => {
this.setState({backClickCount: 0});
});
});
}
handleBackButton = () => {
this.state.backClickCount == 1 ? BackHandler.exitApp() : this._spring();
return true;
};
render() {
return (
<View style={styles.container}>
<Text>
container box
</Text>
<Animated.View style={[styles.animatedView, {transform: [{translateY: this.springValue}]}]}>
<Text style={styles.exitTitleText}>press back again to exit the app</Text>
<TouchableOpacity
activeOpacity={0.9}
onPress={() => BackHandler.exitApp()}
>
<Text style={styles.exitText}>Exit</Text>
</TouchableOpacity>
</Animated.View>
</View>
);
}
}
const styles = {
container: {
flex: 1,
justifyContent: "center",
alignItems: "center"
},
animatedView: {
width,
backgroundColor: "#0a5386",
elevation: 2,
position: "absolute",
bottom: 0,
padding: 10,
justifyContent: "center",
alignItems: "center",
flexDirection: "row",
},
exitTitleText: {
textAlign: "center",
color: "#ffffff",
marginRight: 10,
},
exitText: {
color: "#e5933a",
paddingHorizontal: 10,
paddingVertical: 3
}
};
运行 在 snack.expo 中:https://snack.expo.io/HyhD657d7
下面的代码自行解释。 诀窍是将它放在 Main AppContainer 而不是 每个页面
import { Alert, BackHandler, ToastAndroid } from 'react-native';
import { StackActions } from 'react-navigation';
// common statless class variable.
let backHandlerClickCount = 0;
class App extends React.Component {
constructor(props) {
super(props);
// add listener to didFocus
this._didFocusSubscription = props.navigation.addListener('didFocus', payload =>
BackHandler.addEventListener('hardwareBackPress', () => this.onBackButtonPressAndroid(payload)));
}
// remove listener on unmount
componentWillUnmount() {
if (this._didFocusSubscription) {
this._didFocusSubscription.remove();
}
}
onBackButtonPressAndroid = () => {
const shortToast = message => {
ToastAndroid.showWithGravityAndOffset(
message,
ToastAndroid.SHORT,
ToastAndroid.BOTTOM,
25,
50
);
const {
clickedPosition
} = this.state;
backHandlerClickCount += 1;
if ((clickedPosition !== 1)) {
if ((backHandlerClickCount < 2)) {
shortToast('Press again to quit the application!');
} else {
BackHandler.exitApp();
}
}
// timeout for fade and exit
setTimeout(() => {
backHandlerClickCount = 0;
}, 2000);
if (((clickedPosition === 1) &&
(this.props.navigation.isFocused()))) {
Alert.alert(
'Exit Application',
'Do you want to quit application?', [{
text: 'Cancel',
onPress: () => console.log('Cancel Pressed'),
style: 'cancel'
}, {
text: 'OK',
onPress: () => BackHandler.exitApp()
}], {
cancelable: false
}
);
} else {
this.props.navigation.dispatch(StackActions.pop({
n: 1
}));
}
return true;
}
}
import React, { Component } from 'react'
import { Text, View, StyleSheet, TouchableOpacity,BackHandler} from 'react-native'
import { Toast } from "native-base";
class Dashboard extends Component {
state={
clickcount:0
}
componentDidMount(){
BackHandler.addEventListener("hardwareBackPress",()=>{
this.setState({'clickcount':this.state.clickcount+1})
this.check();
return true
})
}
check=()=>{
if(this.state.clickcount<2){
Toast.show({
text:`Press back again to exit App `,
duration:2000,
onClose:()=>{this.setState({'clickcount':0})}
})
}
else if(this.state.clickcount==2)
{
BackHandler.exitApp()
}
}
render() {
return (
<View style={styles.container}>
<Text> Hello this is the Dashboard Screen</Text>
</View>
)
}
}
export default Dashboard
const styles = StyleSheet.create({
container:{
flex:1,
marginTop:25,
justifyContent:'center',
alignItems:'center',
borderBottomLeftRadius:30,
borderBottomRightRadius:30,
backgroundColor:'#fff'
},
});
更好的方法是简单地使用 BackHandler 和 ToastAndroid
import { BackHandler, ToastAndroid } from 'react-native';
//rest of imports
class SomeClass extends Component{
constructor(state, props) {
super(state, props)
this.state = {
validCloseWindow: false
}
}
async componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton.bind(this));
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton.bind(this));
}
handleBackButton = () => {
if (!this.props.navigation.canGoBack()) {
if (this.state.validCloseWindow)
return false;
this.state.validCloseWindow = true
setTimeout(() => {
this.state.validCloseWindow = false
}, 3000);
ToastAndroid.show("Press Again To Exit !", ToastAndroid.SHORT);
return true;
}
};
//rest of component code
}
只需确保在导航的初始路线页面上使用它即可。
我已经将它作为分离的功能组件以这种方式解决了。这样您就不需要为每个应用程序重新编码,只需在您的新应用程序中包含该组件即可!
import * as React from 'react';
import {useEffect, useState} from 'react';
import {Platform, BackHandler, ToastAndroid} from 'react-native';
export const ExecuteOnlyOnAndroid = (props) => {
const {message} = props;
const [exitApp, setExitApp] = useState(0);
const backAction = () => {
setTimeout(() => {
setExitApp(0);
}, 2000); // 2 seconds to tap second-time
if (exitApp === 0) {
setExitApp(exitApp + 1);
ToastAndroid.show(message, ToastAndroid.SHORT);
} else if (exitApp === 1) {
BackHandler.exitApp();
}
return true;
};
useEffect(() => {
const backHandler = BackHandler.addEventListener(
'hardwareBackPress',
backAction,
);
return () => backHandler.remove();
});
return <></>;
};
export default function DoubleTapToClose(props) {
const {message = 'tap back again to exit the App'} = props;
return Platform.OS !== 'ios' ? (
<ExecuteOnlyOnAndroid message={message} />
) : (
<></>
);
}
=> 您唯一需要做的就是将此组件包含在您的应用程序中。 <=
因为 IOS 没有后退按钮,IOS 不需要此功能。以上组件自动检测 Device 是否为 Android。
默认情况下,Toast 中显示的消息是用英语预定义的,但是如果您将名为 message
的 属性 添加到您的 DoubleTapToClose-Component,您可以设置自己的消息。
...
import DoubleTapToClose from '../lib/android_doubleTapToClose';
...
return(
<>
<DoubleTapToClose />
...other Stuff goes here
</>
根据您的导航结构,您必须检查您是否在应用程序的初始屏幕上。 就我而言,我有一个抽屉,里面有多个 StackNavigatiors。 所以我检查当前屏幕是否是初始屏幕(索引:0)。如果是,我设置了一个 Hook-Variable,它将只用于那些初始屏幕。
看起来像这样:
const isCurrentScreenInitialOne = (state) => {
const route = state.routes[state.index];
if (route.state) {
// Dive into nested navigators
return isCurrentScreenInitialOne(route.state);
}
return state.index === 0;
};
...
...
export default function App() {
...
const [isInitialScreen, setIsInitialScreen] = useState(true);
{isInitialScreen && (<DoubleTapToClose message="Tap again to exit app" />)}
...
...
<NavigationContainer
...
onStateChange={(state) => {
setIsInitialScreen(isCurrentScreenInitialOne(state));
}}>
如果该描述对您有帮助,不要错过投票。
目前最简单的做法:
在App.js:
componentDidMount() {
const backHandler=BackHandler.addEventListener('hardwareBackPress', ()=>{
if(this.backHandler){
return false;
}
Toast.show('再按一次退出应用');
this.backHandler=backHandler;
setTimeout(()=>{
this.backHandler=null;
},2000);
return true;
});
}
componentWillUnmount() {
this.backHandler.remove();
}
抱歉,如果我迟到了,但我有类似的要求并通过创建我自己的自定义挂钩解决了它!
let currentCount = 0;
export const useDoubleBackPressExit = (exitHandler: () => void) => {
if (Platform.OS === "ios") return;
const subscription = BackHandler.addEventListener("hardwareBackPress", () => {
if (currentCount === 1) {
exitHandler();
subscription.remove();
return true;
}
backPressHandler();
return true;
});
};
const backPressHandler = () => {
if (currentCount < 1) {
currentCount += 1;
WToast.show({
data: "Press again to close!",
duration: WToast.duration.SHORT,
});
}
setTimeout(() => {
currentCount = 0;
}, 2000);
};
现在我可以在任何地方使用它,只需执行以下操作:
useDoubleBackPressExit(() => {
// user has pressed "back" twice. Do whatever you want!
});
我在我的应用程序中使用的最简单的解决方案是这个。它适用于 react-navigation 4.4.1,并且比此处提供的其他一些正确答案短得多。
import React from 'react';
import { BackHandler, ToastAndroid} from 'react-native';
export default class LoginScreen extends React.Component {
state = {
canBeClosed: false
}
componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
}
handleBackButton = () => {
if (this.props.navigation.isFocused()) {
if (this.state.canBeClosed)
return this.state.canBeClosed = false;
else {
setTimeout(() => { this.state.canBeClosed = false }, 3000);
ToastAndroid.show("Press Again To Exit !", ToastAndroid.SHORT);
return this.state.canBeClosed = true
}
}
};
//some code
}
最简单的
import { useNavigationState } from '@react-navigation/native';
import { BackHandler, Alert } from 'react-native';
//index to get index of home screen when index == 0 in navigation stack
const index = useNavigationState(state => state.index);
const backAction = () => {
Alert.alert("Hold on!", "Are you sure you want to Exit App?", [
{
text: "Cancel",
onPress: () => null,
style: "cancel"
},
{ text: "YES", onPress: () => BackHandler.exitApp() }
]);
return true;
};
useEffect(() => {
// if index==0 this is initial screen 'Home Screen'
if (index == 0) {
BackHandler.addEventListener("hardwareBackPress", backAction);
return () =>
BackHandler.removeEventListener("hardwareBackPress", backAction);
}
}, [index]);
最简单的方法(直接粘贴到你的功能组件中):
const navigation = useNavigation();
const navIndex = useNavigationState(s => s.index);
const [backPressCount, setBackPressCount] = useState(0);
const handleBackPress = useCallback(() => {
if (backPressCount === 0) {
setBackPressCount(prevCount => prevCount + 1);
setTimeout(() => setBackPressCount(0), 2000);
ToastAndroid.show('Press one more time to exit', ToastAndroid.SHORT);
} else if (backPressCount === 1) {
BackHandler.exitApp();
}
return true;
}, [backPressCount]);
useEffect(() => {
if (Platform.OS === 'android' && navIndex === 0) {
const backListener = BackHandler.addEventListener(
'hardwareBackPress',
handleBackPress,
);
return backListener.remove;
}
}, [handleBackPress]);
您也可以将其包装在自定义挂钩中并在组件中使用挂钩。