项目简介
这是一款模仿手机验证码登录的简易App,利用Bmob平台提供的短信验证服务实现验证码验证功能。
效果展示:
项目准备
注册Bmob平台账号,创建一个应用。(温馨提示:短信发送的次数有限,所以不要频繁测试)
Bmob环境搭建
参考:Bmob平台数据服务
1、配置AndroidManifest.xml
<!--允许联网 -->
<uses-permission android:name="android.permission.INTERNET" />
<!--获取GSM(2g)、WCDMA(联通3g)等网络状态的信息 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!--获取wifi网络状态的信息 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!--保持CPU 运转,屏幕和键盘灯有可能是关闭的,用于文件上传和下载 -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!--获取sd卡写的权限,用于文件上传和下载-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!--允许读取手机状态 用于创建BmobInstallation-->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
2、初始化BmobSDK
class MyApplication:Application() {
override fun onCreate() {
super.onCreate()
//第一:默认初始化
Bmob.initialize(this, "56ad195da375b130d0e9b054a9e550d0")
}
}
大致设计过程
第一个页面设计
手机号输入框:
输入框的shape资源代:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="10dp"/>
<stroke android:color="@color/teal_200"
android:width="3dp"/>
<solid android:color="@color/my_blue"/>
</shape>
给输入框添加监听事件:
主要涉及到输入数字的格式化:使输入的11位电话号码有两个空格
mPhoneEditText.addTextChangedListener(object :LoginTextWatcher(){
override fun afterTextChanged(s: Editable?) {
//设置登录按钮是否可以点击
mLoginButton.isEnabled = s.toString().length==13
//如果shouldAutoSplit为false 那么整个方法就结束了 不会再执行后面的方法了
if (!shouldAutoSplit) return
//调整号码显示格式 191 1206 9048
s.toString().length.also {
if (it == 3||it == 8){
s?.append(' ')
}
}
}
/**
* 通过测试打印值的变化可以知道:
* 当count=1,before=0时 正在进行输入操作
* 当count=0,before=1时 正在进行删除操作
* 所以可以通过count或者before的值来设置shouldAutoSplit的值
* 在afterTextChanged方法中 就通过shouldAutoSplit的值来判断是输入还是删除操作
*/
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
shouldAutoSplit = count==1
}
})
登录按钮:
<Button
android:id="@+id/mLoginButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="25dp"
android:background="@drawable/btn_status_selector"
android:enabled="false"
android:text="获取验证码"
app:layout_constraintBottom_toTopOf="@+id/guideline4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" />
可选与不可选的样式变化
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!--根据控键的enable值 来设定不同的颜色
注意顺序:特殊在前 常规在后
enable状态 运行起来正常状态enable=true 其他状态enable=false
从上至下匹配
-->
<item android:drawable="@color/gray" android:state_enabled="false"/>
<item android:drawable="@color/botton_blue" android:state_enabled="true"/>
</selector>
//按钮点击事件
mLoginButton.setOnClickListener{
Intent().apply {
//跳转方向
setClass(this@MainActivity,VerifyActivity::class.java)
//配置跳转的数据
putExtra("phone",getPhoneNumber(mPhoneEditText.text))
//启动
startActivity(this)
}
}
至于getPhoneNumber的作用,就是将格式化的数据转换为正常数据传递给下一个页面,后面将具体讲解。
验证码输入页面
主要就是验证码输入框
简单说一下小编自己的设计思路:底部是6个小方格TextView用于显示,表层有一个EditText用于输入,但是其alpha的值为0.01所以看不到,给人的视觉效果就是可以直接在小方格中输入。
//监听文本框的内容改变事件
mOrigin.addTextChangedListener(object :LoginTextWatcher(){
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
//将输入的内容拆分到每一个textView中
//获取i对应的textView
for ((i,item) in s?.withIndex()!!){
verifyViews[i].text = item.toString()
}
//如果位数小于6个 后面的显示的都为空
for (i in s.length..5){
verifyViews[i].text =""
}
if (s.length==6){
BmobUtil.verifySMSCode(mPhone.text.toString(),s.toString()){
if (it==BmobUtil.SUCCESS){
//模拟验证成功 跳转到主页
startActivity(Intent(this@VerifyActivity,HomeActivity::class.java))
}else{
Toast.makeText(this@VerifyActivity,"验证码错误",Toast.LENGTH_LONG)
mOrigin.text.clear()
}
}
}
}
})
还有一个测试主页就不再展示,因为没啥内容,仅仅用来测试。
Bug处理
- 1从输入验证码界面返回第一个界面,在获取号码后,号码不再是格式化。
其实解决方法很简单:就是再创建一个对象(SpannableStringBuilder)封装输入的号码对其进行格式化转正常数据的处理即可。
- 2 验证码框可长按现象
因为小编在实现验证码输入框的时候是在6个正方形框(使用的TextView)的上方采用EditText(将其alpha设置为0.01),再使每次输入的值放到TextView上显示,所以这里有个小bug,长按输入框会出现选择、复制、剪切的选项出现。所以需要将EditText的longClickable设置为false。
-3 验证码删除,但显示未变化。
出现此现象的原因和上述那个一样,小编设计原理的过。
解决方案就是没有数字的方格显示空。
//如果位数小于6个 后面的显示的都为空
for (i in s.length..5){
verifyViews[i].text =""
}
两种实现获取短信的方式
1、直接使用系统封装的类方法
(注:上述代码Toast未调用show()方法,所以测试的时候没弹出提示)
可以点击短信自定义模板
2、自己在系统提供的基础上封装自己的方法(推荐)
优点:可以增强复用性
同样的,验证码验证的实现也可采用两种方式
主要代码
MainActivity
package com.example.phonelogin2
import android.content.Intent
import android.os.Bundle
import android.text.Editable
import android.text.SpannableStringBuilder
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
//用于判断是否分割
private var shouldAutoSplit = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mPhoneEditText.addTextChangedListener(object :LoginTextWatcher(){
override fun afterTextChanged(s: Editable?) {
//设置登录按钮是否可以点击
mLoginButton.isEnabled = s.toString().length==13
//如果shouldAutoSplit为false 那么整个方法就结束了 不会再执行后面的方法了
if (!shouldAutoSplit) return
//调整号码显示格式 191 1206 9048
s.toString().length.also {
if (it == 3||it == 8){
s?.append(' ')
}
}
}
/**
* 通过测试打印值的变化可以知道:
* 当count=1,before=0时 正在进行输入操作
* 当count=0,before=1时 正在进行删除操作
* 所以可以通过count或者before的值来设置shouldAutoSplit的值
* 在afterTextChanged方法中 就通过shouldAutoSplit的值来判断是输入还是删除操作
*/
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
shouldAutoSplit = count==1
}
})
//按钮点击事件
mLoginButton.setOnClickListener{
Intent().apply {
//跳转方向
setClass(this@MainActivity,VerifyActivity::class.java)
//配置跳转的数据
putExtra("phone",getPhoneNumber(mPhoneEditText.text))
//启动
startActivity(this)
}
}
}
/**
* 为什么需要创建一个新的对象 来进行操作?
* 当输入数据进行格式化后 进行跳转 再次返回 那么数据就不再是格式化数据 不满足跳转条件
* 所以在将格式化转化为正常数据的方法中 选哟创建一个新的对象来完成该功能
* 而SpannableStringBuilder是Editable的实现类,有其所有方法,所以新对象就是该类型
*/
//将格式化的数据转化为正常的数据
private fun getPhoneNumber(editable: Editable):String{
//创建一个新的对象 用于操作editable对象里面的内容
SpannableStringBuilder(editable.toString()).apply {
delete(3,4)
delete(7,8)
return this.toString()
}
}
}
VerifyActivity
package com.example.phonelogin2
import android.content.Intent
import android.os.Bundle
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_verify.*
class VerifyActivity : AppCompatActivity() {
//保存所有显示验证码的textView
private val verifyViews:Array<TextView> by lazy {
arrayOf(mv1,mv2,mv3,mv4,mv5,mv6)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_verify)
//获取数据
intent.getStringExtra("phone").also {
//显示号码
mPhone.text = it
}
//监听文本框的内容改变事件
mOrigin.addTextChangedListener(object :LoginTextWatcher(){
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
//将输入的内容拆分到每一个textView中
//获取i对应的textView
for ((i,item) in s?.withIndex()!!){
verifyViews[i].text = item.toString()
}
//如果位数小于6个 后面的显示的都为空
for (i in s.length..5){
verifyViews[i].text =""
}
if (s.length==6){
BmobUtil.verifySMSCode(mPhone.text.toString(),s.toString()){
if (it==BmobUtil.SUCCESS){
//模拟验证成功 跳转到主页
startActivity(Intent(this@VerifyActivity,HomeActivity::class.java))
}else{
Toast.makeText(this@VerifyActivity,"验证码错误",Toast.LENGTH_LONG)
mOrigin.text.clear()
}
}
}
}
})
}
override fun onResume() {
super.onResume()
BmobUtil.requestSMSCode(mPhone.text.toString()){
if (it==BmobUtil.SUCCESS){
Toast.makeText(this,"短信请求成功",Toast.LENGTH_LONG).show()
}else{
Toast.makeText(this,"短信请求失败",Toast.LENGTH_LONG).show()
}
}
}
}
BmobUtil
package com.example.phonelogin2
import cn.bmob.v3.BmobSMS
import cn.bmob.v3.exception.BmobException
import cn.bmob.v3.listener.QueryListener
import cn.bmob.v3.listener.UpdateListener
/**
*@Description
*@Author PC
*@QQ 1578684787
*/
object BmobUtil {
const val SUCCESS = 0
const val FAILURE = 1
//向服务器请求...发送验证码
fun requestSMSCode(phone:String,callBack:(Int)->Unit){
BmobSMS.requestSMSCode(phone,"",object :QueryListener<Int>(){
override fun done(p0: Int?, p1: BmobException?) {
if (p1 == null){
//短信发送成功
callBack(SUCCESS)
}else{
//短信发送失败
callBack(FAILURE)
}
}
})
}
//验证用户输入的验证码是否正确
fun verifySMSCode(phone: String,code:String,callBack:(Int)->Unit){
BmobSMS.verifySmsCode(phone,code,object :UpdateListener(){
override fun done(p0: BmobException?) {
if (p0==null){
//验证成功
callBack(SUCCESS)
}else{
//验证失败
callBack(FAILURE)
}
}
})
}
}
其他可去小编github账号上获取
项目完整代码:
https://github.com/gun-ctrl/PhoneLogin2