Android 全屏6位密码输入框:优化布局与功能实现

功能概述

自动验证:输入第6位密码后自动验证。

删除键优化:按下删除键时,如果当前输入框为空,则回退到上一个输入框并清空内容。

密码可见性切换:点击"显示密码"按钮,可以切换密码的可见性。

**震动反馈:**密码错误时提供震动反馈。

输入框抖动动画:密码错误时,输入框抖动提示用户。

实现步骤
布局文件:定义主界面和弹框布局,调整输入框间隙为 3dp。

**样式文件:**为输入框添加边框样式。

动画文件:实现输入框抖动动画。

**逻辑处理:**优化删除键逻辑、自动验证、密码可见性切换等功能。

代码结构
布局文件:

activity_my_number_edit.xml:主界面布局。

dialog_password.xml:密码输入弹框布局。

样式文件:

edittext_border.xml:输入框边框样式。

动画文件:

shake.xml:输入框抖动动画。

逻辑处理:

MyNumberEditActivity.java:实现弹框显示、密码验证、删除键优化等功能。

1. 主界面布局文件 (activity_my_number_edit.xml)

主界面只有一个按钮,点击后弹出密码输入框:

复制代码
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center">

    <Button
        android:id="@+id/btnShowPasswordDialog"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="输入密码"/>
</LinearLayout>

2. 密码输入框布局文件 (dialog_password.xml)

弹框布局,密码输入框之间的间隙为 3dp,并支持全屏显示:

复制代码
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    android:gravity="center"
    android:background="@android:color/white">

    <TextView
        android:id="@+id/tvTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="请输入6位密码"
        android:textSize="18sp"
        android:layout_marginBottom="16dp"/>

    <LinearLayout
        android:id="@+id/passwordContainer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center">

        <EditText
            android:id="@+id/et1"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:maxLength="1"
            android:inputType="numberPassword"
            android:gravity="center"
            android:background="@drawable/edittext_border"
            android:layout_marginEnd="3dp"/>

        <EditText
            android:id="@+id/et2"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:maxLength="1"
            android:inputType="numberPassword"
            android:gravity="center"
            android:background="@drawable/edittext_border"
            android:layout_marginEnd="3dp"/>

        <EditText
            android:id="@+id/et3"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:maxLength="1"
            android:inputType="numberPassword"
            android:gravity="center"
            android:background="@drawable/edittext_border"
            android:layout_marginEnd="3dp"/>

        <EditText
            android:id="@+id/et4"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:maxLength="1"
            android:inputType="numberPassword"
            android:gravity="center"
            android:background="@drawable/edittext_border"
            android:layout_marginEnd="3dp"/>

        <EditText
            android:id="@+id/et5"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:maxLength="1"
            android:inputType="numberPassword"
            android:gravity="center"
            android:background="@drawable/edittext_border"
            android:layout_marginEnd="3dp"/>

        <EditText
            android:id="@+id/et6"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:maxLength="1"
            android:inputType="numberPassword"
            android:gravity="center"
            android:background="@drawable/edittext_border"/>
    </LinearLayout>

    <Button
        android:id="@+id/btnToggleVisibility"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="显示密码"
        android:layout_marginTop="16dp"/>
</LinearLayout>

3. 输入框边框样式 (edittext_border.xml)

在 res/drawable 目录下创建 edittext_border.xml,用于设置输入框的边框样式:

复制代码
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#FFFFFF"/>
    <stroke android:width="1dp" android:color="#000000"/>
    <corners android:radius="4dp"/>
</shape>

4. 抖动动画 (res/anim/shake.xml)

在 res/anim 目录下创建 shake.xml,用于实现输入框抖动动画:

复制代码
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="50"
    android:fromXDelta="0"
    android:toXDelta="10"
    android:repeatCount="5"
    android:repeatMode="reverse"/>

5. 逻辑处理 (MyNumberEditActivity.java)

以下是完整的逻辑处理代码:

复制代码
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.os.Vibrator;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;

public class MyNumberEditActivity extends AppCompatActivity {

    private final String CORRECT_PASSWORD = "123456"; // 正确的密码

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my_number_edit);

        findViewById(R.id.btnShowPasswordDialog).setOnClickListener(v -> showPasswordDialog());
    }

    private void showPasswordDialog() {
        // 创建Dialog
        final Dialog dialog = new Dialog(this, android.R.style.Theme_NoTitleBar_Fullscreen); // 全屏样式
        dialog.setContentView(R.layout.dialog_password);
        dialog.setCancelable(true);

        // 自动弹出软键盘
        dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);

        // 获取Dialog中的控件
        EditText[] etPassword = new EditText[6];
        etPassword[0] = dialog.findViewById(R.id.et1);
        etPassword[1] = dialog.findViewById(R.id.et2);
        etPassword[2] = dialog.findViewById(R.id.et3);
        etPassword[3] = dialog.findViewById(R.id.et4);
        etPassword[4] = dialog.findViewById(R.id.et5);
        etPassword[5] = dialog.findViewById(R.id.et6);

        Button btnToggleVisibility = dialog.findViewById(R.id.btnToggleVisibility);

        // 清空输入框
        for (EditText et : etPassword) {
            et.setText("");
        }

        // 设置输入监听
        setupTextWatchers(etPassword, dialog);

        // 切换密码可见性
        btnToggleVisibility.setOnClickListener(v -> togglePasswordVisibility(etPassword, btnToggleVisibility));

        // 显示Dialog
        dialog.show();
    }

    private void setupTextWatchers(EditText[] etPassword, Dialog dialog) {
        for (int i = 0; i < etPassword.length; i++) {
            final int currentIndex = i;
            etPassword[i].addTextChangedListener(new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                    if (s.length() == 1 && currentIndex < etPassword.length - 1) {
                        etPassword[currentIndex + 1].requestFocus(); // 自动跳到下一个输入框
                    }

                    // 当第6位输入完成时,自动验证密码
                    if (currentIndex == etPassword.length - 1 && s.length() == 1) {
                        String inputPassword = getPassword(etPassword);
                        if (inputPassword.equals(CORRECT_PASSWORD)) {
                            Toast.makeText(MyNumberEditActivity.this, "密码正确,进入下一步", Toast.LENGTH_SHORT).show();
                            dialog.dismiss();
                            // 这里可以跳转到下一步
                        } else {
                            Toast.makeText(MyNumberEditActivity.this, "密码错误,请重试", Toast.LENGTH_SHORT).show();
                            vibrate(); // 震动反馈
                            shakeInputs(etPassword); // 输入框抖动动画
                            clearInput(etPassword); // 清空输入框
                            etPassword[0].requestFocus(); // 焦点回到第一个输入框
                        }
                    }
                }

                @Override
                public void afterTextChanged(Editable s) {}
            });

            // 处理删除键
            etPassword[i].setOnKeyListener((v, keyCode, event) -> {
                if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) {
                    if (etPassword[currentIndex].getText().length() == 0 && currentIndex > 0) {
                        etPassword[currentIndex - 1].requestFocus(); // 回退到上一个输入框
                        etPassword[currentIndex - 1].setText(""); // 清空上一个输入框的内容
                        return true; // 拦截删除键事件
                    }
                }
                return false;
            });
        }
    }

    private String getPassword(EditText[] etPassword) {
        StringBuilder password = new StringBuilder();
        for (EditText et : etPassword) {
            password.append(et.getText().toString());
        }
        return password.toString();
    }

    private void clearInput(EditText[] etPassword) {
        for (EditText et : etPassword) {
            et.setText("");
        }
    }

    private void vibrate() {
        Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
        if (vibrator != null) {
            vibrator.vibrate(200); // 震动200毫秒
        }
    }

    private void shakeInputs(EditText[] etPassword) {
        Animation shake = AnimationUtils.loadAnimation(this, R.anim.shake);
        for (EditText et : etPassword) {
            et.startAnimation(shake);
        }
    }

    private void togglePasswordVisibility(EditText[] etPassword, Button btnToggleVisibility) {
        boolean isVisible = etPassword[0].getInputType() == (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
        for (EditText et : etPassword) {
            if (isVisible) {
                et.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
            } else {
                et.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
            }
        }
        btnToggleVisibility.setText(isVisible ? "显示密码" : "隐藏密码");
    }
}

运行效果

点击主界面的"输入密码"按钮,弹出全屏密码输入框。

依次输入6位数字,输入内容显示为圆点。

当输入第6位时,自动验证密码:

如果密码正确,提示"密码正确,进入下一步",并关闭弹框。

如果密码错误,提示"密码错误,请重试",清空输入框并震动反馈,同时输入框抖动。

支持删除键回退到上一个输入框,并清空上一个输入框的内容。

点击"显示密码"按钮,可以切换密码的可见性。

相关推荐
Dovis(誓平步青云)1 小时前
家里设备越来越多,如何用一张空间地图控制灯光和温度![
android·java·前端·javascript·人工智能·电脑
吴声子夜歌2 小时前
Nginx应用与运维——Nginx集群负载与配置管理(二)
运维·nginx
cpolar技术支持3 小时前
外网测试机的异常传不回内网?自托管 Sentry,用 cpolar 打通错误上报链路
python·nginx·docker·cpolar·sentry
晚风叙码3 小时前
MySQL 数据类型详解:从数值到字符串,一篇讲透
android·mysql·adb
传奇开心果编程4 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程6 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
吴声子夜歌7 小时前
Nginx应用与运维——Nginx在Kubernetes中的应用(一)
运维·nginx·kubernetes
传奇开心果编程8 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
螺蛳粉 螺蛳粉8 小时前
第一篇:Keepalived 高可用实战:VIP 漂移与 Nginx 主备切换完整指南
运维·nginx·负载均衡·keepalived·高可用
传奇开心果编程8 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer