【HarmonyOS】RN_of_HarmonyOS实战项目_密码显示隐藏

【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

一、项目背景

在移动应用的用户认证场景中,密码输入框是不可或缺的元素。为了兼顾安全性与用户体验,允许用户临时查看自己输入的密码明文已成为行业标准功能。

核心设计要素

  1. 状态管理 :使用React Hook维护secureTextEntry状态
  2. 交互反馈:图标切换与动画效果
  3. 无障碍访问:符合HarmonyOS无障碍标准
  4. 安全考虑:自动隐藏、超时控制

二、交互设计

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平台上实现密码显示/隐藏功能的完整方案,从基础版到高级版,涵盖了:

  1. 核心实现:secureTextEntry状态控制、图标切换
  2. 交互优化:自动隐藏、动画效果、触觉反馈
  3. 功能增强:密码强度检测、实时验证
  4. 平台适配:HarmonyOS权限、无障碍访问
  5. 安全考虑:自动隐藏、加密传输

📕个人领域 :Linux/C++/java/AI

🚀 个人主页有点流鼻涕 · CSDN

💬 座右铭 : "向光而行,沐光而生。"

相关推荐
平安的平安2 小时前
【OpenHarmony】React Native鸿蒙实战:NetInfo 网络状态详解
网络·react native·harmonyos
果粒蹬i2 小时前
【HarmonyOS】RN_of_HarmonyOS实战项目_自动完成功能
华为·harmonyos
Betelgeuse762 小时前
【Flutter For OpenHarmony】 项目结项复盘
华为·交互·开源软件·鸿蒙
平安的平安2 小时前
【OpenHarmony】React Native鸿蒙实战:SecureStorage 安全存储详解
安全·react native·harmonyos
松叶似针2 小时前
Flutter三方库适配OpenHarmony【secure_application】— 错误处理与异常边界
flutter·harmonyos
果粒蹬i2 小时前
【HarmonyOS】RN_of_HarmonyOS实战项目_邮箱地址输入
华为·harmonyos
星空22232 小时前
【HarmonyOS】React Native 实战:原生手势交互开发
react native·交互·harmonyos
阿林来了2 小时前
Flutter三方库适配OpenHarmony【flutter_speech】— 错误处理与异常恢复
flutter·harmonyos
爱华晨宇2 小时前
快速清理C盘,释放10GB空间!
harmonyos