React Native登录之指纹登录篇,具体内容如下所示:最近在做react-native的APP,项目登录使用了普通账号密码登录、短信登录、手势登陆、指纹登录和人脸识别登录五种方式,所以准备做一个登录方式的合集。本篇是指纹登录篇,通过手机调用指纹传感器来获取用户指纹并做校验,校验成功则自动登录。首先展示我们最后达成的成果,毕竟无图无真相,下方是真机录屏gif:分析下gif所展示的功能点:1,通过点击操作选项来弹出指纹识别界面,点击取消/上方空白处取消指纹识别2,切换到其他登录方式时,仍然可以调用"操作方式"来唤起指纹识别经过功能点分析,我们可以看出,现在需要一个指纹识别的组件,通过操作弹出项来唤起/切换指纹识别的组件。操作项组件我们可以选择antd-mobile-rn的ActionSheet组件,指纹识别组件使用react-native-fingerprint-scanner,在github上有642个star,想必是值得信任的,附上组件地址https://github.com/hieuvp/react-native-fingerprint-scanner。(多句嘴,模拟器是没有指纹识别的功能的,请使用真机调试)。ActionSheetreact-native-fingerprint-scanner接下来按照文档一通操作,下载,跑demo,不出所料有问题出现了。官方demo使用的是react16之前的class写法,并且对于唤起/隐藏指纹识别的地方写的有些模糊,所以我们需要仔细梳理并用新的函数式组件写法来重写一次。指纹识别的过程应该如下:1,判断设备是否支持指纹识别2,判断android API是否低于23,因为android 6.0之后官方才支持了指纹识别的api接口,所以如果android API<23我们需要对低版本手机做适配3,从指纹组件react-native-fingerprint-scanner引入FingerprintScanner,调用FingerprintScanner.authenticate方法来唤起指纹组件react-native-fingerprint-scannerFingerprintScannerFingerprintScanner.authenticate4,点击取消/上方空白处隐藏指纹组件5,指纹识别成功写对应的事件,如登录事件按照指纹识别的流程,我们在FingerPrint/index.tsx下写指纹组件,关键代码都写了注释:FingerPrint/index.tsx
import React, {Component, useState, useCallback} from 'react';
import {
Alert,
Image,
Text,
TouchableOpacity,
View,
ViewPropTypes,
Platform,
StyleSheet,
} from 'react-native';
import FingerprintScanner from 'react-native-fingerprint-scanner';
import ShakingText from './ShakingText.component';
import {connector, ModelState} from '@/models/connect';
import {useFocusEffect} from "@react-navigation/native";


interface Props extends ModelState{
fingerPrint,
setFingerPrint,
}

const FingerPrintPopup: React.FC = React.memo(props => {
let {fingerPrint, setFingerPrint,dispatch} = props;
const description = null;

const [LegacyInfo, setLegacyInfo] = useState({

errorMessageLegacy: undefined,

biometricLegacy: undefined
})
useFocusEffect(

React.useCallback(() => {

console.log("进入指纹组件");

//判断设备是否支持指纹识别

detectFingerprintAvailable();

//判断Android API是不是<23,高于此版本使用标准指纹解锁api;低于此版本使用兼容适配版本

if (requiresLegacyAuthentication()) {

authLegacy();

} else {

authCurrent();

}


return () => {

console.log("离开指纹组件");

//组件卸载,停止指纹监听指纹扫描器并释放内部缓存

FingerprintScanner.release();

}

}, [])
)

//判断安卓版本
function requiresLegacyAuthentication() {

return Platform.Version < 23;
}

//控制指纹组件消失
const handleFingerprintDismissed = () => {

setFingerPrint({

...fingerPrint,

popupShowed: false

});
};
//检测手机是否支持指纹识别
const detectFingerprintAvailable = () => {

FingerprintScanner

.isSensorAvailable()

.catch(error => {

Alert.alert("您的设备不支持指纹识别,请选择其他方式登录")

setFingerPrint({

...fingerPrint,

errorMessage: error.message,

biometric: error.biometric,

popupShowed: false,

})

});
}
//android API>23时,调用authCurrent
const authCurrent = () => {

FingerprintScanner

.authenticate({title: '指纹登录', cancelButton: '取消'})

.then(() => {

//离开页面时将popupShowed置为false

handleFingerprintDismissed();

//指纹验证成功后的事件,比如登录

successVerify();

})

.catch(() => {

//点击取消或上方空白区隐藏组件后,将popupShowed置为false

//这里是控制指纹组件切换显示/隐藏的关键!

handleFingerprintDismissed();

})
}

//指纹验证成功后的事件,比如登录
const successVerify = () => {

dispatch({

type: 'user/login',

payload: {

username: "张三",

password: '123456',

}

});

}
//android API<23时调用指纹组件的兼容写法
const authLegacy = () => {

FingerprintScanner

.authenticate({onAttempt: handleAuthenticationAttemptedLegacy})

.then(() => {

//指纹验证成功

handleFingerprintDismissed();

Alert.alert('指纹身份验证', '身份验证成功');

successVerify();

})

.catch((error) => {

//指纹验证失败

setLegacyInfo({errorMessageLegacy: error.message, biometricLegacy: error.biometric});

description.shake();

handleFingerprintDismissed();

});
}

//当用户尝试扫描指纹但失败时的回调函数
const handleAuthenticationAttemptedLegacy = (error) => {

setLegacyInfo({

...LegacyInfo,

errorMessageLegacy: error.message

});

description.shake();
};

//手动写一个指纹验证的组件
const renderLegacy = (







style={styles.logo}

source={require('../../../assets/login/finger_print.png')}

/>




生物识别{'\n'}身份验证




ref={(instance) => {

description = instance;

}}

style={styles.description(!!LegacyInfo.errorMessageLegacy)}>

{LegacyInfo.errorMessageLegacy || `Scan your ${LegacyInfo.biometricLegacy} on the\ndevice scanner to continue`}





style={styles.buttonContainer}

onPress={handleFingerprintDismissed}

>



返回









);

return () => {

//android API>23时,不需要渲染;否则渲染renderLegacy的组件

if (requiresLegacyAuthentication()) {

return renderLegacy

}


return null;

}

})


export default connector(FingerPrintPopup);
const styles = StyleSheet.create({
container: {

position: 'absolute',

top: 0,

bottom: 0,

left: 0,

right: 0,

backgroundColor: 'rgba(0, 164, 222, 0.9)',

flexDirection: 'column',

justifyContent: 'center',

alignItems: 'center',
},
contentContainer: {

flexDirection: 'column',

justifyContent: 'center',

alignItems: 'center',

backgroundColor: '#ffffff',
},
logo: {

marginVertical: 45,
},
heading: {

textAlign: 'center',

color: '#00a4de',

fontSize: 21,
},
description: (error) => {

return {

textAlign: 'center',

color: error ? '#ea3d13' : '#a5a5a5',

height: 65,

fontSize: 18,

marginVertical: 10,

marginHorizontal: 20,

}
},
buttonContainer: {

padding: 20,
},
buttonText: {

color: '#8fbc5a',

fontSize: 15,

fontWeight: 'bold',
},
});

import React, {Component, useState, useCallback} from 'react';
import {
Alert,
Image,
Text,
TouchableOpacity,
View,
ViewPropTypes,
Platform,
StyleSheet,
} from 'react-native';
import FingerprintScanner from 'react-native-fingerprint-scanner';
import ShakingText from './ShakingText.component';
import {connector, ModelState} from '@/models/connect';
import {useFocusEffect} from "@react-navigation/native";


interface Props extends ModelState{
fingerPrint,
setFingerPrint,
}

const FingerPrintPopup: React.FC = React.memo(props => {
let {fingerPrint, setFingerPrint,dispatch} = props;
const description = null;

const [LegacyInfo, setLegacyInfo] = useState({

errorMessageLegacy: undefined,

biometricLegacy: undefined
})
useFocusEffect(

React.useCallback(() => {

console.log("进入指纹组件");

//判断设备是否支持指纹识别

detectFingerprintAvailable();

//判断Android API是不是<23,高于此版本使用标准指纹解锁api;低于此版本使用兼容适配版本

if (requiresLegacyAuthentication()) {

authLegacy();

} else {

authCurrent();

}


return () => {

console.log("离开指纹组件");

//组件卸载,停止指纹监听指纹扫描器并释放内部缓存

FingerprintScanner.release();

}

}, [])
)

//判断安卓版本
function requiresLegacyAuthentication() {

return Platform.Version < 23;
}

//控制指纹组件消失
const handleFingerprintDismissed = () => {

setFingerPrint({

...fingerPrint,

popupShowed: false

});
};
//检测手机是否支持指纹识别
const detectFingerprintAvailable = () => {

FingerprintScanner

.isSensorAvailable()

.catch(error => {

Alert.alert("您的设备不支持指纹识别,请选择其他方式登录")

setFingerPrint({

...fingerPrint,

errorMessage: error.message,

biometric: error.biometric,

popupShowed: false,

})

});
}
//android API>23时,调用authCurrent
const authCurrent = () => {

FingerprintScanner

.authenticate({title: '指纹登录', cancelButton: '取消'})

.then(() => {

//离开页面时将popupShowed置为false

handleFingerprintDismissed();

//指纹验证成功后的事件,比如登录

successVerify();

})

.catch(() => {

//点击取消或上方空白区隐藏组件后,将popupShowed置为false

//这里是控制指纹组件切换显示/隐藏的关键!

handleFingerprintDismissed();

})
}

//指纹验证成功后的事件,比如登录
const successVerify = () => {

dispatch({

type: 'user/login',

payload: {

username: "张三",

password: '123456',

}

});

}
//android API<23时调用指纹组件的兼容写法
const authLegacy = () => {

FingerprintScanner

.authenticate({onAttempt: handleAuthenticationAttemptedLegacy})

.then(() => {

//指纹验证成功

handleFingerprintDismissed();

Alert.alert('指纹身份验证', '身份验证成功');

successVerify();

})

.catch((error) => {

//指纹验证失败

setLegacyInfo({errorMessageLegacy: error.message, biometricLegacy: error.biometric});

description.shake();

handleFingerprintDismissed();

});
}

//当用户尝试扫描指纹但失败时的回调函数
const handleAuthenticationAttemptedLegacy = (error) => {

setLegacyInfo({

...LegacyInfo,

errorMessageLegacy: error.message

});

description.shake();
};

//手动写一个指纹验证的组件
const renderLegacy = (







style={styles.logo}

source={require('../../../assets/login/finger_print.png')}

/>




生物识别{'\n'}身份验证




ref={(instance) => {

description = instance;

}}

style={styles.description(!!LegacyInfo.errorMessageLegacy)}>

{LegacyInfo.errorMessageLegacy || `Scan your ${LegacyInfo.biometricLegacy} on the\ndevice scanner to continue`}





style={styles.buttonContainer}

onPress={handleFingerprintDismissed}

>



返回









);

return () => {

//android API>23时,不需要渲染;否则渲染renderLegacy的组件

if (requiresLegacyAuthentication()) {

return renderLegacy

}


return null;

}

})


export default connector(FingerPrintPopup);
const styles = StyleSheet.create({
container: {

position: 'absolute',

top: 0,

bottom: 0,

left: 0,

right: 0,

backgroundColor: 'rgba(0, 164, 222, 0.9)',

flexDirection: 'column',

justifyContent: 'center',

alignItems: 'center',
},
contentContainer: {

flexDirection: 'column',

justifyContent: 'center',

alignItems: 'center',

backgroundColor: '#ffffff',
},
logo: {

marginVertical: 45,
},
heading: {

textAlign: 'center',

color: '#00a4de',

fontSize: 21,
},
description: (error) => {

return {

textAlign: 'center',

color: error ? '#ea3d13' : '#a5a5a5',

height: 65,

fontSize: 18,

marginVertical: 10,

marginHorizontal: 20,

}
},
buttonContainer: {

padding: 20,
},
buttonText: {

color: '#8fbc5a',

fontSize: 15,

fontWeight: 'bold',
},
});
可以看出来,android API在23以上版本是不需要渲染指纹识别组件的,23以下版本需要自己手动写一个渲染的组件,引用了一个ShakingText组件,这个组件官网有给,我们直接复制过来就可以,在ShakingText.component.js写:ShakingTextShakingText.component.js
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import {
Animated,
Text,
} from 'react-native';

class ShakingText extends Component {

componentWillMount() {

this.shakedValue = new Animated.Value(0);
}

get animatedStyle() {

return {

transform: [

{

translateY: this.shakedValue.interpolate({

inputRange: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1],

outputRange: [0, 10, -15, 12, -9, 18, -7, 10, -11, 5, 0],

}),

},

{

translateX: this.shakedValue.interpolate({

inputRange: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1],

outputRange: [0, 2, -3, 4, -4, 3, -3, 4, -5, 2, 0],

}),

},

],

};
}

shake = () => {

this.shakedValue.setValue(0);

Animated.spring(this.shakedValue, {

toValue: 1,

friction: 3,

tension: 10,

}).start(() => this.shakedValue.setValue(0));
};

render() {

return (


{...this.props}

style={[this.animatedStyle, this.props.style]}

/>

);
}
}

ShakingText.propTypes = {
children: PropTypes.oneOfType([

PropTypes.arrayOf(PropTypes.node),

PropTypes.node
]),
style:Text.propTypes.style,
};

export default ShakingText;
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import {
Animated,
Text,
} from 'react-native';

class ShakingText extends Component {

componentWillMount() {

this.shakedValue = new Animated.Value(0);
}

get animatedStyle() {

return {

transform: [

{

translateY: this.shakedValue.interpolate({

inputRange: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1],

outputRange: [0, 10, -15, 12, -9, 18, -7, 10, -11, 5, 0],

}),

},

{

translateX: this.shakedValue.interpolate({

inputRange: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1],

outputRange: [0, 2, -3, 4, -4, 3, -3, 4, -5, 2, 0],

}),

},

],

};
}

shake = () => {

this.shakedValue.setValue(0);

Animated.spring(this.shakedValue, {

toValue: 1,

friction: 3,

tension: 10,

}).start(() => this.shakedValue.setValue(0));
};

render() {

return (


{...this.props}

style={[this.animatedStyle, this.props.style]}

/>

);
}
}

ShakingText.propTypes = {
children: PropTypes.oneOfType([

PropTypes.arrayOf(PropTypes.node),

PropTypes.node
]),
style:Text.propTypes.style,
};

export default ShakingText;写好了指纹组件,接下来我们需要使用ActionSheet组件来切换和控制指纹组件的显示隐藏。控制指纹组件的显示隐藏我们使用popupShowed这个变量来控制,将它放在state中。我们在LoginSheet/index.tsx下写:ActionSheetpopupShowedLoginSheet/index.tsx
import React,{useState} from 'react';
import {View, Text, StyleSheet} from 'react-native';
import {px2rem} from "@/utils/px2rem";
import {ActionSheet} from "@ant-design/react-native";
import {navigateReplace} from "@/utils/navigation";
import FingerprintPopup from "@/pages/Account/FingerPrint";
import {connector,ModelState} from '@/models/connect';


interface Props {

}
const LoginSheet: React.FC = React.memo(props => {
const {dispatch} = props;

const [fingerPrint,setFingerPrint] = useState({

errorMessage: undefined,

biometric: undefined,

popupShowed: false,
})

//点击指纹登录选项时,将popupShowed置为true
const handleFingerprintShowed = () => {

setFingerPrint({

...fingerPrint,

popupShowed: true

});
}

const showActionSheet = () =>{

const BUTTONS = [

'账号 / 短信登录',

'手势登录',

'指纹登录',

'人脸识别登录',

'取消',

];

ActionSheet.showActionSheetWithOptions(

{

options: BUTTONS,

cancelButtonIndex: 4,

},

buttonIndex => {

const clickOption = BUTTONS[buttonIndex];

console.log('clicked: ',clickOption);

switch (clickOption){

case '账号 / 短信登录':

navigateReplace("Login");

return;

case '手势登录':

navigateReplace("GestureUnlock");

return;

case '指纹登录':

//显示指纹登录

handleFingerprintShowed();

return;

case '人脸识别登录':

navigateReplace("Login");

return;

}

}

);
}

return (





登录方式



{fingerPrint.popupShowed ? (


fingerPrint={fingerPrint}

setFingerPrint={setFingerPrint}

/>

) : null}


);

})

export default connector(LoginSheet);
const styles = StyleSheet.create({
container:{

},
more:{

width:'100%',

alignItems:'center',

height:px2rem(50),

marginBottom:px2rem(30),
},
moreText:{

// color:'#a1a1a1'
}
});
import React,{useState} from 'react';
import {View, Text, StyleSheet} from 'react-native';
import {px2rem} from "@/utils/px2rem";
import {ActionSheet} from "@ant-design/react-native";
import {navigateReplace} from "@/utils/navigation";
import FingerprintPopup from "@/pages/Account/FingerPrint";
import {connector,ModelState} from '@/models/connect';


interface Props {

}
const LoginSheet: React.FC = React.memo(props => {
const {dispatch} = props;

const [fingerPrint,setFingerPrint] = useState({

errorMessage: undefined,

biometric: undefined,

popupShowed: false,
})

//点击指纹登录选项时,将popupShowed置为true
const handleFingerprintShowed = () => {

setFingerPrint({

...fingerPrint,

popupShowed: true

});
}

const showActionSheet = () =>{

const BUTTONS = [

'账号 / 短信登录',

'手势登录',

'指纹登录',

'人脸识别登录',

'取消',

];

ActionSheet.showActionSheetWithOptions(

{

options: BUTTONS,

cancelButtonIndex: 4,

},

buttonIndex => {

const clickOption = BUTTONS[buttonIndex];

console.log('clicked: ',clickOption);

switch (clickOption){

case '账号 / 短信登录':

navigateReplace("Login");

return;

case '手势登录':

navigateReplace("GestureUnlock");

return;

case '指纹登录':

//显示指纹登录

handleFingerprintShowed();

return;

case '人脸识别登录':

navigateReplace("Login");

return;

}

}

);
}

return (





登录方式



{fingerPrint.popupShowed ? (


fingerPrint={fingerPrint}

setFingerPrint={setFingerPrint}

/>

) : null}


);

})

export default connector(LoginSheet);
const styles = StyleSheet.create({
container:{

},
more:{

width:'100%',

alignItems:'center',

height:px2rem(50),

marginBottom:px2rem(30),
},
moreText:{

// color:'#a1a1a1'
}
});这样就完成了react-native 的指纹登录功能,总结一下,关键点在于:切换指纹组件显示隐藏在于控制popupShowed的true/false状态来切换FingerprintPopup组件的展示与否每次唤醒组件都要通过显示FingerprintPopup组件,在初始化期间进行判断可用、版本然后调用组件展示每次隐藏组件都要通过隐藏FingerprintPopup组件,在组件卸载阶段释放指纹组件监听器FingerprintScanner.release();操作指纹组件时不管是指纹识别成功还是点击取消,都一定要将popupShowed置为false;指纹识别失败不做操作。popupShowedFingerprintPopupFingerprintPopupFingerprintPopupFingerprintScanner.release();popupShowed总结总结总结