(二十五)安卓开发一个完整的登录页面-支持密码登录和手机验证码登录
下面将详细讲解如何在 Android 中开发一个完整的登录页面,支持密码登录和手机验证码登录。以下是实现过程的详细步骤,从布局设计到逻辑实现,再到用户体验优化,逐步展开。
1. 设计登录页面布局
首先,我们需要设计一个用户友好的登录页面布局,包含以下核心元素:
- 用户名/手机号输入框:用于输入用户名或手机号。
- 密码输入框:用于密码登录。
- 验证码输入框:用于手机验证码登录,默认隐藏。
- 登录按钮:触发登录操作。
- 切换登录方式按钮:在密码登录和验证码登录之间切换。
- 发送验证码按钮:发送验证码到用户手机,默认隐藏。
我们可以使用 LinearLayout 或 ConstraintLayout 来布局这些元素。以下是一个简单的 XML 布局示例:
<?xml version="1.0" encoding="utf-8"?>
<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"><EditTextandroid:id="@+id/et_username"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="用户名/手机号" /><EditTextandroid:id="@+id/et_password"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="密码"android:inputType="textPassword" /><EditTextandroid:id="@+id/et_verification_code"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="验证码"android:visibility="gone" /><Buttonandroid:id="@+id/btn_send_verification_code"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="发送验证码"android:visibility="gone" /><Buttonandroid:id="@+id/btn_login"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="登录" /><Buttonandroid:id="@+id/btn_switch_login_type"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="切换到手机验证码登录" /></LinearLayout>
说明:
android:visibility="gone"表示验证码输入框和发送验证码按钮默认隐藏。android:inputType="textPassword"确保密码输入框显示为密码格式。
2. 处理登录逻辑
接下来,我们在 Activity 中实现登录逻辑。这里以 Kotlin 为例,使用 AppCompatActivity 实现。
初始化视图和监听器
class LoginActivity : AppCompatActivity() {private lateinit var etUsername: EditTextprivate lateinit var etPassword: EditTextprivate lateinit var etVerificationCode: EditTextprivate lateinit var btnSendVerificationCode: Buttonprivate lateinit var btnLogin: Buttonprivate lateinit var btnSwitchLoginType: Buttonprivate var isPasswordLogin = true // 默认使用密码登录override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_login)// 初始化视图etUsername = findViewById(R.id.et_username)etPassword = findViewById(R.id.et_password)etVerificationCode = findViewById(R.id.et_verification_code)btnSendVerificationCode = findViewById(R.id.btn_send_verification_code)btnLogin = findViewById(R.id.btn_login)btnSwitchLoginType = findViewById(R.id.btn_switch_login_type)// 设置登录按钮点击事件btnLogin.setOnClickListener {if (isPasswordLogin) {performPasswordLogin()} else {performVerificationCodeLogin()}}// 设置切换登录方式按钮点击事件btnSwitchLoginType.setOnClickListener {switchLoginType()}// 设置发送验证码按钮点击事件btnSendVerificationCode.setOnClickListener {val phoneNumber = etUsername.text.toString()sendVerificationCode(phoneNumber)}}
}
说明:
isPasswordLogin是一个布尔变量,用于跟踪当前登录方式。- 点击登录按钮时,根据
isPasswordLogin的值决定执行密码登录还是验证码登录。
切换登录方式
private fun switchLoginType() {if (isPasswordLogin) {// 切换到手机验证码登录etPassword.visibility = View.GONEetVerificationCode.visibility = View.VISIBLEbtnSendVerificationCode.visibility = View.VISIBLEbtnSwitchLoginType.text = "切换到密码登录"isPasswordLogin = false} else {// 切换到密码登录etPassword.visibility = View.VISIBLEetVerificationCode.visibility = View.GONEbtnSendVerificationCode.visibility = View.GONEbtnSwitchLoginType.text = "切换到手机验证码登录"isPasswordLogin = true}
}
说明:
- 通过改变视图的可见性(
View.VISIBLE和View.GONE)和按钮文本,实现登录方式的切换。
3. 实现密码登录
密码登录需要向服务器发送用户名和密码,并处理响应。我们使用 Retrofit 库进行网络请求。
定义 API 接口
interface ApiService {@POST("login")fun login(@Body request: LoginRequest): Call<LoginResponse>
}data class LoginRequest(val username: String, val password: String)
data class LoginResponse(val token: String)
实现密码登录逻辑
private fun performPasswordLogin() {val username = etUsername.text.toString()val password = etPassword.text.toString()val retrofit = Retrofit.Builder().baseUrl("https://your-api-url.com/") // 替换为实际 API 地址.addConverterFactory(GsonConverterFactory.create()).build()val apiService = retrofit.create(ApiService::class.java)val request = LoginRequest(username, password)apiService.login(request).enqueue(object : Callback<LoginResponse> {override fun onResponse(call: Call<LoginResponse>, response: Response<LoginResponse>) {if (response.isSuccessful) {val token = response.body()?.tokentoken?.let {saveToken(it)goToMainActivity()}} else {showError("登录失败,请检查用户名或密码")}}override fun onFailure(call: Call<LoginResponse>, t: Throwable) {showError("网络错误,请稍后重试")}})
}
说明:
saveToken和goToMainActivity用于保存 token 并跳转到主界面,具体实现见后文。showError用于显示错误提示。
4. 实现手机验证码登录
手机验证码登录分为两步:发送验证码和验证验证码。
定义 API 接口
interface ApiService {@POST("send_verification_code")fun sendVerificationCode(@Body request: SendCodeRequest): Call<SendCodeResponse>@POST("verify_code")fun verifyCode(@Body request: VerifyCodeRequest): Call<VerifyCodeResponse>
}data class SendCodeRequest(val phoneNumber: String)
data class SendCodeResponse(val success: Boolean)data class VerifyCodeRequest(val phoneNumber: String, val code: String)
data class VerifyCodeResponse(val token: String)
发送验证码
private fun sendVerificationCode(phoneNumber: String) {val retrofit = Retrofit.Builder().baseUrl("https://your-api-url.com/").addConverterFactory(GsonConverterFactory.create()).build()val apiService = retrofit.create(ApiService::class.java)val request = SendCodeRequest(phoneNumber)apiService.sendVerificationCode(request).enqueue(object : Callback<SendCodeResponse> {override fun onResponse(call: Call<SendCodeResponse>, response: Response<SendCodeResponse>) {if (response.isSuccessful && response.body()?.success == true) {Toast.makeText(this@LoginActivity, "验证码已发送", Toast.LENGTH_SHORT).show()startCountdown() // 开始倒计时} else {showError("验证码发送失败")}}override fun onFailure(call: Call<SendCodeResponse>, t: Throwable) {showError("网络错误,请稍后重试")}})
}
验证验证码
private fun performVerificationCodeLogin() {val phoneNumber = etUsername.text.toString()val verificationCode = etVerificationCode.text.toString()val retrofit = Retrofit.Builder().baseUrl("https://your-api-url.com/").addConverterFactory(GsonConverterFactory.create()).build()val apiService = retrofit.create(ApiService::class.java)val request = VerifyCodeRequest(phoneNumber, verificationCode)apiService.verifyCode(request).enqueue(object : Callback<VerifyCodeResponse> {override fun onResponse(call: Call<VerifyCodeResponse>, response: Response<VerifyCodeResponse>) {if (response.isSuccessful) {val token = response.body()?.tokentoken?.let {saveToken(it)goToMainActivity()}} else {showError("验证码错误")}}override fun onFailure(call: Call<VerifyCodeResponse>, t: Throwable) {showError("网络错误,请稍后重试")}})
}
5. 优化用户体验
为了提升用户体验,可以添加以下功能:
发送验证码倒计时
private fun startCountdown() {btnSendVerificationCode.isEnabled = falsevar remainingTime = 60 // 60秒倒计时val handler = Handler(Looper.getMainLooper())val runnable = object : Runnable {override fun run() {if (remainingTime > 0) {btnSendVerificationCode.text = "重新发送($remainingTime)"remainingTime--handler.postDelayed(this, 1000)} else {btnSendVerificationCode.text = "发送验证码"btnSendVerificationCode.isEnabled = true}}}handler.post(runnable)
}
说明:
- 在
sendVerificationCode成功后调用startCountdown(),防止用户频繁发送验证码。
输入验证
可以在登录前检查输入是否合法,例如:
private fun isValidPhoneNumber(phone: String): Boolean {return phone.matches(Regex("^1[3-9]\\d{9}$")) // 简单验证中国手机号
}private fun performPasswordLogin() {val username = etUsername.text.toString()val password = etPassword.text.toString()if (username.isEmpty() || password.isEmpty()) {showError("用户名或密码不能为空")return}// 继续登录逻辑
}private fun performVerificationCodeLogin() {val phoneNumber = etUsername.text.toString()val code = etVerificationCode.text.toString()if (!isValidPhoneNumber(phoneNumber)) {showError("请输入有效的手机号")return}if (code.isEmpty()) {showError("请输入验证码")return}// 继续验证逻辑
}
6. 处理登录成功和失败
保存 Token 并跳转
private fun saveToken(token: String) {val sharedPreferences = getSharedPreferences("app_prefs", Context.MODE_PRIVATE)sharedPreferences.edit().putString("token", token).apply()
}private fun goToMainActivity() {val intent = Intent(this, MainActivity::class.java)startActivity(intent)finish()
}
显示错误提示
private fun showError(message: String) {Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
7. 总结
通过以上步骤,我们实现了一个完整的登录页面,支持密码登录和手机验证码登录。主要包括:
- 布局设计:使用 XML 创建用户界面。
- 逻辑处理:通过 Kotlin 实现切换登录方式和网络请求。
- 网络请求:使用 Retrofit 与服务器交互。
- 用户体验:添加倒计时和输入验证。
在实际开发中,还需考虑:
- 安全性:密码加密传输、验证码防刷。
- 界面美化:使用 Material Design 组件。
- 错误处理:更细致的网络错误和服务器响应处理。
相关文章:
(二十五)安卓开发一个完整的登录页面-支持密码登录和手机验证码登录
下面将详细讲解如何在 Android 中开发一个完整的登录页面,支持密码登录和手机验证码登录。以下是实现过程的详细步骤,从布局设计到逻辑实现,再到用户体验优化,逐步展开。 1. 设计登录页面布局 首先,我们需要设计一个用…...
【java 13天进阶Day05】数据结构,List,Set ,TreeSet集合,Collections工具类
常见的数据结构种类 集合是基于数据结构做出来的,不同的集合底层会采用不同的数据结构。不同的数据结构,功能和作用是不一样的。数据结构: 数据结构指的是数据以什么方式组织在一起。不同的数据结构,增删查的性能是不一样的。不同…...
水污染治理(生物膜+机器学习)
文章目录 **1. 水质监测与污染预测****2. 植物-微生物群落优化****3. 系统设计与运行调控****4. 维护与风险预警****5. 社区参与与政策模拟****挑战与解决思路****未来趋势** 前言: 将机器学习(ML)等人工智能技术融入植树生物膜系统ÿ…...
Python人工智能 使用可视图方法转换时间序列为复杂网络
基于可视图方法的时间序列复杂网络转换实践 引言 在人工智能与数据科学领域,时间序列分析是一项基础且重要的技术。本文将介绍一种创新的时间序列分析方法——可视图方法,该方法能将时间序列转换为复杂网络,从而利用复杂网络理论进行更深入…...
spring:加载配置类
在前面的学习中,通过读取xml文件将类加载,或他通过xml扫描包,将包中的类加载。无论如何都需要通过读取xml才能够进行后续操作。 在此创建配置类。通过对配置类的读取替代xml的功能。 配置类就是Java类,有以下内容需要执行&#…...
使用Pydantic优雅处理几何数据结构 - 前端输入验证实践
使用Pydantic优雅处理几何数据结构 - 前端输入验证实践 一、应用场景解析 在视频分析类项目中,前端常需要传递几何坐标数据。例如智能安防系统中,需要接收: 视频流地址(rtsp_video)检测区域坐标点(point…...
从零搭建一套前端开发环境
一、基础环境搭建 1.NVM(Node Version Manager)安装 简介 nvm(Node Version Manager) 是一个用于管理多个 Node.js 版本的工具,允许开发者在同一台机器上轻松安装、切换和使用不同版本的 Node.js。它特别适合需要同时维护多个项目ÿ…...
金融数据库转型实战读后感
荣幸收到老友太保科技有限公司数智研究院首席专家林春的签名赠书。 这是国内第一本关于OceanBase数据库实际替换过程总结的的实战书。打个比方可以说是从战场上下来分享战斗经验。读后感受颇深。我在这里讲讲我的感受。 第三章中提到的应用改造如何降本。应用改造是国产化替换…...
代码审计系列2:小众cms oldcms
目录 sql注入 1. admin/admin.php Login_check 2. admin/application/label/index.php 3. admin/application/hr/index.php 4. admin/application/feedback/index.php 5. admin/application/article/index.php sql注入 1. admin/admin.php Login_check 先看一下p…...
Cursor + MCP,实现自然语言操作 GitLab 仓库
本分分享如何使用 cursor mcp 来操作极狐GitLab 仓库,体验用自然语言在不接触极狐GitLab 的情况下来完成一些仓库操作。 极狐GitLab 是 GitLab 在中国的发行版,关于中文参考文档和资料有: 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitL…...
Vue el-from的el-form-item v-for循环表单如何校验rules(一)
实际业务需求场景: 新增或编辑页面(基础信息表单,一个数据列表的表单),数据列表里面的表单数是动态添加的。数据可新增、可删除,在表单保存前,常常需要做表单必填项的校验,校验通过以…...
C#集合List<T>与HashSet<T>的区别
在C#中,List和HashSet都是用于存储元素的集合,但它们在内部实现、用途、性能特性以及使用场景上存在一些关键区别。 内部实现 List:基于数组实现的,可以包含重复的元素,并且元素是按照添加的顺序存储的。 HashSet&…...
【Reading Notes】(8.3)Favorite Articles from 2025 March
【March】 雷军一度登顶中国首富,太厉害了(2025年03月02日) 早盘,小米港股一路高歌猛进,暴涨4%,股价直接飙到52港元的历史新高。这一波猛如虎的操作,直接把雷军的身家拉到了2980亿元,…...
Spring Boot 项目里设置默认国区时区,Jave中Date时区配置
在 Spring Boot 项目里设置国区时区(也就是中国标准时间,即 Asia/Shanghai),可通过以下几种方式实现: 方式一:在application.properties或application.yml里设置 application.properties properties sp…...
从PDF到播客:MIT开发的超越NotebookLM的工具
NotebookLM是谷歌推出的更具创意的AI产品之一,几个月前刚刚推出。 许多人对它的能力感到惊叹——尤其是将长文本转化为两位播客主持人之间有趣对话的功能。 NotebookLM提供的不仅仅是这些,还包括聊天(问答)甚至生成思维导图。 如果你还没有尝试过NotebookLM,我强烈建议…...
Kotlin协程Semaphore withPermit约束并发任务数量
Kotlin协程Semaphore withPermit约束并发任务数量 import kotlinx.coroutines.* import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.launch import kotlinx.coroutines.runBlockingfun main() {val permits 1 /…...
Redis的下载安装和使用(超详细)
目录 一、所需的安装包资源小编放下述网盘了,提取码:wshf 二、双击打开文件redis.desktop.manager.exe 三、点击next后,再点击i agree 四、点击箭头指向,选择安装路径,然后点击Install进行安装 五、安装完后依次点…...
Springboot 学习 之 logback-spring.xml 日志打印
文章目录 1. property2. springProperty3. appender4. logger4.1. 通过包路径控制日志4.2. 通过类名控制日志4.3. 按自定义 Logger 名称控制日志 5. root6. springProfile SpringBoot 项目中可以通过自定义 logback-spring.xml 中各项配置,实现日志的打印控制 1. p…...
从游戏显卡到AI引擎:NVIDIA CUDA如何重构计算世界的底层逻辑
当GPU不再是"显卡" 2025年,当ChatGPT-5的万亿参数模型在0.1秒内完成推理时,人们很少意识到,支撑这场智能革命的不仅是算法突破,更是一场持续20年的架构革命。NVIDIA CUDA技术,这个最初被游戏玩家视为"…...
无线网络入侵检测系统实战 | 基于React+Python的可视化安全平台开发详解
随着无线网络的普及,网络攻击风险也日益严峻。本项目旨在构建一个实时监测、智能识别、高效防护的无线网络安全平台,通过结合前后端技术与安全算法,实现对常见攻击行为的有效监控和防御。 一、项目简介与功能目的 本系统是一款基于 React 前…...
前端 实现文字打字效果(仿AI)
DOM结构 <scroll-view class"scroll-view" scroll-y"true" :scroll-top"scrollTop" :style"{height: contentHeight px}"scroll-with-animation show-scrollbar"false" id"report-scroll-view"><view …...
C#核心(25)练手小项目:飞机大战
简介 通过核心部分的学习,我们已经可以做一些复杂的项目了。 我们这次会用我们学到的面向对象知识写一个飞机大战(性能可能不太好,因为毕竟是控制台项目) 如果你有所不懂,建议多查多思考多问。 因为这次的项目比较难,博主会稍微讲仔细一点。 基类设计:GameObject 抽…...
[经验总结]Linux双机双网卡Keepalived高可用配置及验证细节
1. 前言 这种配置需求比较少见,在网上也很少有相关文章,于是记录在此,供有需要的朋友参考。 本篇重点介绍配置的关键点,基础部分简单提及,不赘述。 2. 需求描述 如上图,即给两个主机配置两对高可用主从配…...
Go语言入门到入土——三、处理并返回异常
Go语言入门到入土——三、处理并返回异常 文章目录 Go语言入门到入土——三、处理并返回异常1. 在greetings.go中添加异常处理代码2. 在hello.go中添加日志记录代码3. 运行 1. 在greetings.go中添加异常处理代码 处理空输入的异常,代码如下: package g…...
2025.04.17【Dendrogram】生信数据可视化:Dendrogram图表详解
Dendrogram customization Go further with ggraph: edge style, general layout, node features, adding labels, and more. Customized circular dendrogram Learn how to build a circular dendrogram with proper labels. 文章目录 Dendrogram customizationCustomized c…...
Linux下的网络管理
一、ipv4原理 网络接口是指网络中的计算机或网络设备与其他设备实现通讯的进出口,一般是指计算机的网络接口即网卡设备 从RHEL7开始引入了一种新的“一致网络设备命名”的方式为网络接口命名,该方式可以根据固件、设备拓扑、设备类型和位置信息分配固…...
GPT-4o Image Generation Capabilities: An Empirical Study
GPT-4o 图像生成能力:一项实证研究 目录 介绍研究背景方法论文本到图像生成图像到图像转换图像到 3D 能力主要优势局限性与挑战对比性能影响与未来方向结论介绍 近年来,图像生成领域发生了巨大的变化,从生成对抗网络 (GAN) 发展到扩散模型,再到可以处理多种模态的统一生成架…...
Zookeeper介绍与安装配置
1.综述 1.1.Zookeeper介绍 Zookeeper 是一个分布式协调服务,由 Apache 开发,主要用于管理分布式应用中的配置信息、命名服务、分布式同步和组服务。它通过简单的接口提供高性能、高可用性和严格的顺序访问控制,广泛应用于分布式系统的协调与…...
提示词阶段总结
经过这些天的提示词学习,总结了一下提示词示例,可以直接拿来使用,规范大模型的输出。 CoT(适用于算术题) {问题},让我们一步一步思考。 Auto-CoT(自动思维链,适合回答多个问题一起…...
实验五 内存管理实验
实验五 内存管理实验 一、实验目的 1、了解操作系统动态分区存储管理过程和方法。 2、掌握动态分区存储管理的主要数据结构--空闲表区。 3、加深理解动态分区存储管理中内存的分配和回收。 4、掌握空闲区表中空闲区3种不同放置策略的基本思想和实现过程。 5、通过模拟程…...
