下面将详细讲解如何在 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">
<EditText
android:id="@+id/et_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="用户名/手机号" />
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="密码"
android:inputType="textPassword" />
<EditText
android:id="@+id/et_verification_code"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="验证码"
android:visibility="gone" />
<Button
android:id="@+id/btn_send_verification_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发送验证码"
android:visibility="gone" />
<Button
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="登录" />
<Button
android: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: EditText
private lateinit var etPassword: EditText
private lateinit var etVerificationCode: EditText
private lateinit var btnSendVerificationCode: Button
private lateinit var btnLogin: Button
private lateinit var btnSwitchLoginType: Button
private 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.GONE
etVerificationCode.visibility = View.VISIBLE
btnSendVerificationCode.visibility = View.VISIBLE
btnSwitchLoginType.text = "切换到密码登录"
isPasswordLogin = false
} else {
// 切换到密码登录
etPassword.visibility = View.VISIBLE
etVerificationCode.visibility = View.GONE
btnSendVerificationCode.visibility = View.GONE
btnSwitchLoginType.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()?.token
token?.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()?.token
token?.let {
saveToken(it)
goToMainActivity()
}
} else {
showError("验证码错误")
}
}
override fun onFailure(call: Call<VerifyCodeResponse>, t: Throwable) {
showError("网络错误,请稍后重试")
}
})
}
5. 优化用户体验
为了提升用户体验,可以添加以下功能:
发送验证码倒计时
private fun startCountdown() {
btnSendVerificationCode.isEnabled = false
var 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 组件。
- 错误处理:更细致的网络错误和服务器响应处理。