【HarmonyOS】RN of HarmonyOS实战项目:密码显示隐藏交互实现

🌸你好呀!我是 lbb小魔仙
🌟 感谢陪伴~ 小白博主在线求友
🌿 跟着小白学Linux/Java/Python
📖 专栏汇总:
《Linux》专栏 | 《Java》专栏 | 《Python》专栏

- [【HarmonyOS】RN of HarmonyOS实战项目:密码显示隐藏交互实现](#【HarmonyOS】RN of HarmonyOS实战项目:密码显示隐藏交互实现)
项目概述:本文深入探讨在HarmonyOS平台上使用React Native实现优雅的密码显示/隐藏切换功能,从交互设计到工程实现,提供符合HarmonyOS设计规范的生产级解决方案。
欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net
一、项目背景
在移动应用的用户认证场景中,密码输入框是不可或缺的元素。为了兼顾安全性与用户体验,允许用户临时查看自己输入的密码明文已成为行业标准功能。
核心设计要素:
- 状态管理 :使用React Hook维护
secureTextEntry状态 - 交互反馈:图标切换与动画效果
- 无障碍访问:符合HarmonyOS无障碍标准
- 安全考虑:自动隐藏、超时控制
二、交互设计
2.1 交互流程图
是
否
用户进入密码输入
默认状态
密码隐藏状态
secureTextEntry=true
显示:闭眼图标
用户点击图标
切换为明文状态
secureTextEntry=false
显示:睁眼图标
3秒无操作
自动切回隐藏状态
用户点击图标
2.2 视觉状态定义
| 状态 | secureTextEntry | 图标 | 文本显示 | 背景色 |
|---|---|---|---|---|
| 默认隐藏 | true | eye-off | •••••• | #FFFFFF |
| 明文显示 | false | eye | 123456 | #F0F9FF |
| 错误状态 | true | eye-off | •••••• | #FFF5F5 |
| 成功状态 | true | eye-off | •••••• | #F5FFF9 |
三、核心实现代码
3.1 密码输入组件基础版
typescript
/**
* 密码输入组件 - 基础版
* 实现密码显示/隐藏切换的核心功能
*
* @platform HarmonyOS 2.0+
* @react-native 0.72+
*/
import React, { useState, useRef, useEffect } from 'react';
import {
View,
TextInput,
TouchableOpacity,
StyleSheet,
TextInputProps,
} from 'react-native';
interface PasswordInputProps extends Omit<TextInputProps, 'secureTextEntry'> {
onVisibilityChange?: (isVisible: boolean) => void;
autoHideDelay?: number; // 自动隐藏延迟(毫秒)
}
export const PasswordInput: React.FC<PasswordInputProps> = ({
onVisibilityChange,
autoHideDelay = 3000,
style,
...props
}) => {
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
const autoHideTimer = useRef<NodeJS.Timeout>();
const inputRef = useRef<TextInput>(null);
/**
* 切换密码可见性
*/
const togglePasswordVisibility = () => {
const newState = !isPasswordVisible;
setIsPasswordVisible(newState);
onVisibilityChange?.(newState);
// 设置自动隐藏定时器
if (newState && autoHideDelay > 0) {
if (autoHideTimer.current) {
clearTimeout(autoHideTimer.current);
}
autoHideTimer.current = setTimeout(() => {
setIsPasswordVisible(false);
onVisibilityChange?.(false);
}, autoHideDelay);
}
};
/**
* 清理定时器
*/
useEffect(() => {
return () => {
if (autoHideTimer.current) {
clearTimeout(autoHideTimer.current);
}
};
}, []);
/**
* 获取当前图标名称
*/
const getIconName = (): string => {
return isPasswordVisible ? 'eye' : 'eye-off';
};
return (
<View style={styles.container}>
<TextInput
ref={inputRef}
style={[styles.input, style]}
secureTextEntry={!isPasswordVisible}
placeholder="请输入密码"
placeholderTextColor="#999"
textContentType="password"
{...props}
/>
<TouchableOpacity
onPress={togglePasswordVisibility}
style={styles.iconButton}
activeOpacity={0.6}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
accessibilityLabel={isPasswordVisible ? '隐藏密码' : '显示密码'}
accessibilityHint="点击切换密码可见状态"
accessibilityState={{ checked: isPasswordVisible }}
>
{/* 图标组件需要根据项目选择 */}
<PasswordIcon is_visible={isPasswordVisible} />
</TouchableOpacity>
</View>
);
};
/**
* 密码图标组件(使用Text渲染简单图标)
* 实际项目中建议使用 react-native-vector-icons
*/
const PasswordIcon: React.FC<{ is_visible: boolean }> = ({ is_visible }) => {
return (
<View style={iconStyles.container}>
{/* 使用SVG Path渲染眼睛图标 */}
<Text style={iconStyles.icon}>
{is_visible ? '👁️' : '👁️🗨️'}
</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#fff',
borderWidth: 1,
borderColor: '#E5E5E5',
borderRadius: 12,
paddingHorizontal: 16,
},
input: {
flex: 1,
height: 50,
fontSize: 16,
color: '#333',
paddingVertical: 0,
},
iconButton: {
padding: 8,
marginLeft: 8,
},
});
const iconStyles = StyleSheet.create({
container: {
width: 24,
height: 24,
justifyContent: 'center',
alignItems: 'center',
},
icon: {
fontSize: 20,
color: '#666',
},
});
export default PasswordInput;
3.2 矢量图标版本
typescript
/**
* 使用 react-native-vector-icons 的密码输入组件
* 提供更精美的图标显示效果
*/
import React, { useState, useRef, useEffect } from 'react';
import {
View,
TextInput,
TouchableOpacity,
StyleSheet,
TextInputProps,
Animated,
} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
interface VectorPasswordInputProps extends Omit<TextInputProps, 'secureTextEntry'> {
iconSize?: number;
iconColor?: string;
autoHideDelay?: number;
enableAnimation?: boolean;
}
export const VectorPasswordInput: React.FC<VectorPasswordInputProps> = ({
iconSize = 22,
iconColor = '#666',
autoHideDelay = 3000,
enableAnimation = true,
style,
...props
}) => {
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
const autoHideTimer = useRef<NodeJS.Timeout>();
const scaleAnim = useRef(new Animated.Value(1)).current;
const togglePasswordVisibility = () => {
const newState = !isPasswordVisible;
setIsPasswordVisible(newState);
// 缩放动画
if (enableAnimation) {
Animated.sequence([
Animated.timing(scaleAnim, {
toValue: 0.8,
duration: 100,
useNativeDriver: true,
}),
Animated.timing(scaleAnim, {
toValue: 1,
duration: 100,
useNativeDriver: true,
}),
]).start();
}
// 自动隐藏
if (newState && autoHideDelay > 0) {
if (autoHideTimer.current) {
clearTimeout(autoHideTimer.current);
}
autoHideTimer.current = setTimeout(() => {
setIsPasswordVisible(false);
}, autoHideDelay);
}
};
useEffect(() => {
return () => {
if (autoHideTimer.current) {
clearTimeout(autoHideTimer.current);
}
};
}, []);
return (
<View style={vectorStyles.container}>
<TextInput
style={[vectorStyles.input, style]}
secureTextEntry={!isPasswordVisible}
placeholder="请输入密码"
placeholderTextColor="#999"
textContentType="password"
{...props}
/>
<Animated.View style={{ transform: [{ scale: scaleAnim }] }}>
<TouchableOpacity
onPress={togglePasswordVisibility}
style={vectorStyles.iconButton}
activeOpacity={0.6}
hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
accessibilityLabel={isPasswordVisible ? '隐藏密码' : '显示密码'}
accessibilityHint="点击切换密码可见状态"
accessibilityState={{ checked: isPasswordVisible }}
>
<Icon
name={isPasswordVisible ? 'visibility' : 'visibility-off'}
size={iconSize}
color={iconColor}
/>
</TouchableOpacity>
</Animated.View>
</View>
);
};
const vectorStyles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#fff',
borderWidth: 1,
borderColor: '#E5E5E5',
borderRadius: 12,
paddingHorizontal: 16,
},
input: {
flex: 1,
height: 50,
fontSize: 16,
color: '#333',
paddingVertical: 0,
},
iconButton: {
padding: 8,
marginLeft: 8,
justifyContent: 'center',
alignItems: 'center',
},
});
3.3 高级功能版本
typescript
/**
* 高级密码输入组件
* 集成密码强度检测、验证、自动隐藏等功能
*/
import React, { useState, useRef, useEffect, useCallback } from 'react';
import {
View,
TextInput,
TouchableOpacity,
StyleSheet,
TextInputProps,
Alert,
Platform,
} from 'react-native';
import { PasswordStrengthIndicator } from './PasswordStrengthIndicator';
interface AdvancedPasswordInputProps extends TextInputProps {
minLength?: number;
maxLength?: number;
requireSpecialChar?: boolean;
requireNumber?: boolean;
requireUpperCase?: boolean;
showStrengthIndicator?: boolean;
onValidationChange?: (isValid: boolean, strength: number) => void;
}
export const AdvancedPasswordInput: React.FC<AdvancedPasswordInputProps> = ({
minLength = 8,
maxLength = 32,
requireSpecialChar = true,
requireNumber = true,
requireUpperCase = true,
showStrengthIndicator = true,
onValidationChange,
style,
...props
}) => {
const [password, setPassword] = useState('');
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
const [validationErrors, setValidationErrors] = useState<string[]>([]);
const [strength, setStrength] = useState(0);
const inputRef = useRef<TextInput>(null);
const autoHideTimer = useRef<NodeJS.Timeout>();
/**
* 计算密码强度
*/
const calculateStrength = useCallback((pwd: string): number => {
if (!pwd) return 0;
let score = 0;
// 长度得分
if (pwd.length >= minLength) score += 1;
if (pwd.length >= 12) score += 1;
// 字符类型得分
if (/[a-z]/.test(pwd)) score += 1;
if (/[A-Z]/.test(pwd)) score += 1;
if (/\d/.test(pwd)) score += 1;
if (/[!@#$%^&*(),.?":{}|<>]/.test(pwd)) score += 1;
return Math.min(score, 5);
}, [minLength]);
/**
* 验证密码
*/
const validatePassword = useCallback((pwd: string): string[] => {
const errors: string[] = [];
if (pwd.length < minLength) {
errors.push(`至少${minLength}个字符`);
}
if (pwd.length > maxLength) {
errors.push(`不能超过${maxLength}个字符`);
}
if (requireUpperCase && !/[A-Z]/.test(pwd)) {
errors.push('需要包含大写字母');
}
if (requireNumber && !/\d/.test(pwd)) {
errors.push('需要包含数字');
}
if (requireSpecialChar && !/[!@#$%^&*(),.?":{}|<>]/.test(pwd)) {
errors.push('需要包含特殊字符');
}
return errors;
}, [minLength, maxLength, requireSpecialChar, requireNumber, requireUpperCase]);
/**
* 处理密码输入变化
*/
const handlePasswordChange = useCallback((text: string) => {
setPassword(text);
const newStrength = calculateStrength(text);
setStrength(newStrength);
const errors = validatePassword(text);
setValidationErrors(errors);
const isValid = text.length > 0 && errors.length === 0;
onValidationChange?.(isValid, newStrength);
}, [calculateStrength, validatePassword, onValidationChange]);
/**
* 切换密码可见性
*/
const togglePasswordVisibility = useCallback(() => {
const newState = !isPasswordVisible;
setIsPasswordVisible(newState);
// HarmonyOS震动反馈
if (Platform.OS === 'harmony') {
// 需要VIBRATE权限
console.log('Toggle password visibility');
}
// 自动隐藏(3秒后)
if (newState) {
if (autoHideTimer.current) {
clearTimeout(autoHideTimer.current);
}
autoHideTimer.current = setTimeout(() => {
setIsPasswordVisible(false);
}, 3000);
}
}, [isPasswordVisible]);
/**
* 清理定时器
*/
useEffect(() => {
return () => {
if (autoHideTimer.current) {
clearTimeout(autoHideTimer.current);
}
};
}, []);
return (
<View style={advancedStyles.container}>
<View style={advancedStyles.inputWrapper}>
<TextInput
ref={inputRef}
style={[advancedStyles.input, style]}
value={password}
onChangeText={handlePasswordChange}
secureTextEntry={!isPasswordVisible}
placeholder="请输入密码"
placeholderTextColor="#999"
textContentType="password"
autoCapitalize="none"
autoCorrect={false}
{...props}
/>
<TouchableOpacity
onPress={togglePasswordVisibility}
style={[
advancedStyles.iconButton,
validationErrors.length > 0 && password.length > 0
? advancedStyles.iconButtonError
: validationErrors.length === 0 && password.length >= minLength
? advancedStyles.iconButtonSuccess
: null,
]}
activeOpacity={0.6}
accessibilityLabel={isPasswordVisible ? '隐藏密码' : '显示密码'}
accessibilityHint="点击切换密码可见状态"
>
<Text style={advancedStyles.iconText}>
{isPasswordVisible ? '👁️' : '👁️🗨️'}
</Text>
</TouchableOpacity>
</View>
{/* 密码强度指示器 */}
{showStrengthIndicator && password.length > 0 && (
<PasswordStrengthIndicator password={password} showLabel />
)}
{/* 验证错误提示 */}
{validationErrors.length > 0 && password.length > 0 && (
<View style={advancedStyles.errorsContainer}>
{validationErrors.map((error, index) => (
<Text key={index} style={advancedStyles.errorText}>
• {error}
</Text>
))}
</View>
)}
</View>
);
};
const advancedStyles = StyleSheet.create({
container: {
width: '100%',
},
inputWrapper: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#fff',
borderWidth: 1,
borderColor: '#E5E5E5',
borderRadius: 12,
paddingHorizontal: 16,
},
input: {
flex: 1,
height: 50,
fontSize: 16,
color: '#333',
paddingVertical: 0,
},
iconButton: {
padding: 8,
marginLeft: 8,
},
iconButtonError: {
backgroundColor: '#FFF5F5',
borderRadius: 8,
},
iconButtonSuccess: {
backgroundColor: '#F5FFF9',
borderRadius: 8,
},
iconText: {
fontSize: 18,
},
errorsContainer: {
marginTop: 8,
backgroundColor: '#FFF5F5',
borderRadius: 8,
padding: 12,
},
errorText: {
fontSize: 13,
color: '#FF3B30',
lineHeight: 20,
},
});
3.4 使用示例
typescript
/**
* 密码输入组件使用示例
*/
import React, { useState } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
SafeAreaView,
ScrollView,
} from 'react-native';
import { VectorPasswordInput } from './VectorPasswordInput';
import { AdvancedPasswordInput } from './AdvancedPasswordInput';
const PasswordInputDemo: React.FC = () => {
const [password, setPassword] = useState('');
const [isValid, setIsValid] = useState(false);
const [strength, setStrength] = useState(0);
const handleSubmit = () => {
if (!isValid) {
Alert.alert('验证失败', '密码不符合要求,请检查');
return;
}
// 提交逻辑
console.log('密码验证通过,可以提交');
};
return (
<SafeAreaView style={demoStyles.container}>
<ScrollView contentContainerStyle={demoStyles.scrollContent}>
<Text style={demoStyles.title}>密码输入演示</Text>
{/* 基础版本 */}
<View style={demoStyles.section}>
<Text style={demoStyles.sectionTitle}>基础版本</Text>
<VectorPasswordInput
placeholder="请输入密码(基础版)"
autoHideDelay={0} // 不自动隐藏
/>
</View>
{/* 高级版本 */}
<View style={demoStyles.section}>
<Text style={demoStyles.sectionTitle}>高级版本(带强度检测)</Text>
<AdvancedPasswordInput
minLength={8}
maxLength={32}
showStrengthIndicator
onValidationChange={(valid, str) => {
setIsValid(valid);
setStrength(str);
}}
/>
</View>
{/* 提交按钮 */}
<TouchableOpacity
style={[
demoStyles.submitButton,
!isValid && demoStyles.submitButtonDisabled,
]}
onPress={handleSubmit}
disabled={!isValid}
activeOpacity={0.8}
>
<Text style={demoStyles.submitButtonText}>提交</Text>
</TouchableOpacity>
{/* 当前状态显示 */}
{password.length > 0 && (
<View style={demoStyles.statusBox}>
<Text>密码有效: {isValid ? '是' : '否'}</Text>
<Text>强度等级: {strength}/5</Text>
</View>
)}
</ScrollView>
</SafeAreaView>
);
};
const demoStyles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F5F5',
},
scrollContent: {
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#333',
marginBottom: 30,
textAlign: 'center',
},
section: {
marginBottom: 30,
},
sectionTitle: {
fontSize: 16,
fontWeight: '600',
color: '#666',
marginBottom: 12,
},
submitButton: {
backgroundColor: '#007AFF',
borderRadius: 12,
paddingVertical: 16,
alignItems: 'center',
marginTop: 20,
},
submitButtonDisabled: {
backgroundColor: '#B4D4FF',
},
submitButtonText: {
color: '#fff',
fontSize: 18,
fontWeight: '600',
},
statusBox: {
marginTop: 20,
padding: 16,
backgroundColor: '#fff',
borderRadius: 12,
borderWidth: 1,
borderColor: '#E5E5E5',
},
});
export default PasswordInputDemo;
四、HarmonyOS平台适配
4.1 权限配置
json5
// harmony/entry/src/main/module.json5
{
"module": {
"name": "entry",
"type": "entry",
"deviceTypes": ["phone", "tablet"],
"requestPermissions": [
{
"name": "ohos.permission.VIBRATE",
"reason": "$string:vibrate_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
}
4.2 触觉反馈工具
typescript
/**
* HarmonyOS触觉反馈工具
*/
import { Platform } from 'react-native';
export class HarmonyOSHaptics {
/**
* 轻微点击反馈
*/
static lightTap() {
if (Platform.OS === 'harmony') {
// 调用原生震动API
// 需要VIBRATE权限
}
}
/**
* 验证成功反馈
*/
static success() {
if (Platform.OS === 'harmony') {
// 成功震动模式
}
}
/**
* 验证失败反馈
*/
static error() {
if (Platform.OS === 'harmony') {
// 错误震动模式
}
}
}
五、安全最佳实践
| 安全措施 | 实现方式 | 说明 |
|---|---|---|
| 自动隐藏 | 3秒无操作后自动隐藏密码 | 防止密码长时间暴露 |
| 禁用截图 | 在密码输入时禁用截图 | HarmonyOS系统级支持 |
| 清除剪贴板 | 提交后清除剪贴板 | 防止密码被复制 |
| 加密传输 | 使用HTTPS | 确保传输安全 |
| 本地加密 | 敏感数据加密存储 | 使用HarmonyOS密钥库 |
六、总结
本文详细介绍了在HarmonyOS平台上实现密码显示/隐藏功能的完整方案,从基础版到高级版,涵盖了:
- 核心实现:secureTextEntry状态控制、图标切换
- 交互优化:自动隐藏、动画效果、触觉反馈
- 功能增强:密码强度检测、实时验证
- 平台适配:HarmonyOS权限、无障碍访问
- 安全考虑:自动隐藏、加密传输
📕个人领域 :Linux/C++/java/AI
🚀 个人主页 :有点流鼻涕 · CSDN
💬 座右铭 : "向光而行,沐光而生。"
