2018-09-09 关于用户登录的过程(用Fragment去实
之前已经说过如何进行登录界面的编写,一个好的app是需要从用户的角度去出发的,我们登录界面不仅能提供用户密码登录,还应该能支持用户短信登录甚至是短信修改密码,这样就需要我们用更多的精力去优化我们的代码.首先说一下本篇文章的重点
1.关于如何进行Fragment的跳转
2.如何进行Fragment之前的传参
3.如何使从当前Fragment回退到我们需要的界面
4.如何使用mobSdk进行短信验证.
5.对于用户输入信息的判断.
这样,我们一点一点来分析,首先,我们需要进行Fragment的跳转。这个问题很重要,因为我们的界面肯定是有按键需求的,当用户按下一个键后,会要跳转到我们需要的界面上,上一篇文章在最后提到可以用接口回调的方式去实现,其实这样太麻烦了,我们可以用事务处理的方式去实现对Fragment管理.
public class FragmentTranscationUtil {
//获取FragmentManager,开启事务,添加碎片
public static void replaceFragment(FragmentActivity activity, Fragment fragment) {
FragmentManager manager = activity.getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.setCustomAnimations(R.anim.activity_right_in, 0, 0, R.anim.activity_right_out);
transaction.replace(R.id.userloginFrame_layout, fragment);
transaction.addToBackStack(null);
transaction.commit();
}
//获取FragmentManager,开启事务,添加碎片
public static void replaceFragment(FragmentActivity activity, Fragment fragment, Bundle bundle) {
FragmentManager manager = activity.getSupportFragmentManager();
fragment.setArguments(bundle);
FragmentTransaction transaction = manager.beginTransaction();
transaction.setCustomAnimations(R.anim.activity_right_in, 0, 0, R.anim.activity_right_out);
transaction.replace(R.id.userloginFrame_layout, fragment);
transaction.addToBackStack(null);
transaction.commit();
}
这是一个帮助类,参数很简单,首先,我们要获取当前所在Activity,这个不用多说吗,因为Fragment多是嵌套在Activity里面的,第二个参数就是我们需要跳转的目标Fragment
下面第二个构造函数的第三个参数Bundle则是用来进行传参的.这个我下面会详细讲解
首先,我们要获取到FragmentManger的实例manger,然后manager.beginTransaction();
去开启事务,将我们需要执行的逻辑放入到事务中,在调用transaction.commit();去执行
就行了,这里顺便向大家展示一下Fragment生命周期
Fragment的生命周期和activity生命周期很像,其生命周期方法如下所示。
- onAttach:绑定到activity
- onCreate:创建fragment
- onCreateView: 创建fragment的布局
- onActivityCreated: activity创建完成后
- onStart: 可见, 不可交互
- onResume: 可见, 可交互
- onPause: 部分可见, 不可交互
- onStop:不可见
- onDestroyView: 销毁fragment的view对象
- onDestroy: fragment销毁了
- onDetach: 从activity解绑了
下面给出FragmentTransaction的全部方法(API 24)转载自https://www.jianshu.com/p/5761ee2d3ea1
- add(Fragment fragment, String tag) // 调用add(int, Fragment, String),填入为0的containerViewId.
- add(int containerViewId, Fragment fragment) // 调用add(int, Fragment, String),填入为null的tag.
- add(int containerViewId, Fragment fragment, String tag) // 向Activity中添加一个Fragment.
- addSharedElement(View sharedElement, String name) // 添加共享元素
- addToBackStack(String name) // 将事务添加到回退栈
- attach(Fragment fragment) // 重新关联Fragment(当Fragment被detach时)
- commit() // 提交事务
- commitAllowingStateLoss() // 类似commit(),但允许在Activity状态保存之后提交(即允许状态丢失)。
- commitNow() // 同步提交事务
- commitNowAllowingStateLoss() // 类似commitNow(),但允许在Activity状态保存之后提交(即允许状态丢失)。
- detach(Fragment fragment) // 将fragment保存的界面从UI中移除
- disallowAddToBackStack() // 不允许调用addToBackStack(String)操作
- hide(Fragment fragment) // 隐藏已存在的Fragment
- isAddToBackStackAllowed() // 是否允许添加到回退栈
- isEmpty() // 事务是否未包含的任何操作
- remove(Fragment fragment) // 移除一个已存在的Fragment
- replace(int containerViewId, Fragment fragment) // 调用replace(int, Fragment, String)填入为null的tag.
- replace(int containerViewId, Fragment fragment, String tag) // 替换已存在的Fragment
- setBreadCrumbShortTitle(int res) // 为事务设置一个BreadCrumb短标题
- setBreadCrumbShortTitle(CharSequence text) // 为事务设置一个BreadCrumb短标题,将会被FragmentBreadCrumbs使用
- setBreadCrumbTitle(int res) // 为事务设置一个BreadCrumb全标题,将会被FragmentBreadCrumbs使用
- setBreadCrumbTitle(CharSequence text) // 为事务设置一个BreadCrumb全标题
- setCustomAnimations(int enter, int exit, int popEnter, int popExit) // 自定义事务进入/退出以及入栈/出栈的动画效果
- setCustomAnimations(int enter, int exit) // 自定义事务进入/退出的动画效果
- setTransition(int transit) // 为事务设置一个标准动画
- setTransitionStyle(int styleRes) // 为事务标准动画设置自定义样式
- show(Fragment fragment) // 显示一个被隐藏的Fragment
这样我们就解决了Fragment跳转问题,同时Fragment回退问题也给出了答案,那就是利用addToBackStack(String name)
我们知道Activity有任务栈,用户通过startActivity将Activity加入栈,点击返回按钮将Activity出栈。Fragment也有类似的栈,称为回退栈(Back Stack),回退栈是由FragmentManager管理的。
默认情况下,Fragment事务是不会加入回退栈的,如果想将Fragment加入回退栈并实现事物回滚,首先需要在commit()方法之前调用事务的以下方法将其添加到回退栈中:
- addToBackStack(String name)
这个方法在我们跳转Fragment时候将事务添加至回退栈
Fragment的回退非常简单,然而这里又会出现一个新的问题,就是在修改后的案例每次只能回退到上一步操作,而并不能一次性回退到我们想要的位置,这样才更满足实际开发需要。这就需要我们来多了解事物回滚的相关原理,其实在Fragment回退时,默认调用FragmentManager的popBackStack()方法将最上层的操作弹出回退栈。当栈中有多层时,我们可以根据id或TAG标识来指定弹出到的操作所在层。 - popBackStack(int id, int flags):其中id表示提交变更时commit()的返回值。
- popBackStack(String name, int flags):其中name是addToBackStack(String tag)中的tag值。
在上面2个方法里面,都用到了flags,其实flags有两个取值:0或FragmentManager.POP_BACK_STACK_INCLUSIVE。当取值0时,表示除了参数指定这一层之上的所有层都退出栈,指定的这一层为栈顶层;当取值POP_BACK_STACK_INCLUSIVE时,表示连着参数指定的这一层一起退出栈。
这样Fragment的跳转和回退就说到这里,接下来是传参问题,这个问题也很简单,因为Bundle的底层代码就是HasMap<key,value>组合,首先实例化一个bundle,然后用<key,value>储存想要保存的数据,然后调用 - setArguments(bundle);//添加将要传递的bundle
在目标Fragment,使用getArguments();去获取到传入的值,比较简单,就不在赘述了。这样,1,2,3,问题我们都已经解决
接下来是重点!如何使用mobSDK去实现短信验证.
首先给大家一张图,便于理解短信验证流程
TIM截图20180930115033.png
我个人使用的是Mob开发者平台(http://sms.mob.com)注册,获取到Appkey和appSecret,这个非常重要
一 配置Gradle
1、打开项目根目录的build.gradle,在buildscrip–>dependencies 模块下面添加 classpath ‘com.mob.sdk:MobSDK:+’,如下所示;
buildscript {
repositories {
jcenter()
}
dependencies {
...
classpath 'com.mob.sdk:MobSDK:+'
}
}
2、在使用SMSSDK模块的build.gradle中,添加MobSDK插件和扩展,如:
// 添加插件
apply plugin: 'com.mob.sdk'
// 在MobSDK的扩展中注册SMSSDK的相关信息
MobSDK {
appKey "d580ad5*****"//填写自己获取的appKey
appSecret "7fcae59a******7e2759e9e397c82bdd"//填写自己获取的appSecret
SMSSDK {}
}
这里面的appkey和appSecret是自己注册而获取到的.替换一下就行了,
3、初始化MobSDK
如果您没有在AndroidManifest中设置appliaction的类名,MobSDK会将这个设置为com.mob.MobApplication,但如果您设置了,请在您自己的Application类中调用:
MobSDK.init(this);
以初始化MobSDK。
二 具体代码
这里有两种方式去实现,一个是可视化界面,一个是非UI实现,
因为我个人是是使用无UI的方式,所以会讲述无Ui实现的方法,这里给出可视化界面的文档(http://wiki.mob.com/sdk-sms-android-3-0-0/)
首先介绍一下EventHandler这是一个异步操作,和Handler类似所有的业务逻辑将会在这里处理,它是和registerEventHandler一起使用的,registerEventHandler是一个事件接收器,他是专门负责接收所有被触发的事件,而registerEventHandler本身可以注册多个,所有接收器也会在事件被触发时候接收消息.一般是配套使用,否则容易产生内存泄露
下面给出具体代码
//请求短信验证码
private void SendCode(String country, String number) {
EventHandler eh = new EventHandler() {
@Override
public void afterEvent(int i, int i1, Object o) {
if (i == SMSSDK.EVENT_GET_VERIFICATION_CODE) {//获取短信验证码事件
//获取验证码成功
if (i1 == SMSSDK.RESULT_COMPLETE) {
listener.getCodeSuccess();
} else if (i1 == SMSSDK.RESULT_ERROR) {
listener.getCodeFailure();
}
}
}
};
SMSSDK.registerEventHandler(eh);
SMSSDK.getVerificationCode(country, number);
}
可以看到这是我自定义的一个方法,首先获取国家,中国是86开头,没啥好说的,然后是你将要发送短信验证的用户手机号码,这也没啥好说的,至于listener是我自定义的接口,会统一处理事务逻辑.
//提交验证码
private void SubmitCode(String country, final String number, String code) {
EventHandler eh = new EventHandler() {
@Override
public void afterEvent(int i, int i1, Object o) {
if (i == SMSSDK.EVENT_SUBMIT_VERIFICATION_CODE) {//验证码提交事件
if (i1 == SMSSDK.RESULT_COMPLETE) {
//回调成功
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
listener.submitCodeSuccesss(number, dateFormat.format(new Date()));
} else if (i1 == SMSSDK.RESULT_ERROR) {//提交验证码失败
listener.submitCodeFailure();
}
}
}
};
SMSSDK.registerEventHandler(eh);
SMSSDK.submitVerificationCode(country, number, code);
}
提交验证码多出一个你接收短信验证的信息code,其他都差不多的。
这里给出mob业务逻辑,可以最大限度自定义出符合你自己要求的业务逻辑。
Handler mhandle = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
int event = msg.arg1;
int result = msg.arg2;
Object data = msg.obj;
if (event == SMSSDK.EVENT_SUBMIT_VERIFICATION_CODE) {//验证码提交事件
if (result == SMSSDK.RESULT_COMPLETE) {
//回调成功
Toast.makeText(context, "提交验证码成功"+result, Toast.LENGTH_LONG).show();
} else if (result == SMSSDK.RESULT_ERROR) {
Toast.makeText(context, "提交验证码失败"+data, Toast.LENGTH_LONG).show();
}
} else if (event == SMSSDK.EVENT_GET_VERIFICATION_CODE) {//获取短信验证码事件
//获取验证码成功
if (result == SMSSDK.RESULT_COMPLETE) {
Toast.makeText(context, "获取短信验证码成功", Toast.LENGTH_LONG).show();
boolean mobcheck = (Boolean) data;
if (mobcheck) {
//通过智能验证
Toast.makeText(context, "mob云验证", Toast.LENGTH_LONG).show();
} else {
//依然走短信验证
Toast.makeText(context, "短信验证", Toast.LENGTH_LONG).show();
}
} else if (result == SMSSDK.RESULT_ERROR) {
Toast.makeText(context, "获取短信验证码失败"+data, Toast.LENGTH_LONG).show();
}
} else if (event == SMSSDK.EVENT_GET_SUPPORTED_COUNTRIES) {
SMSSDK.getSupportedCountries();
} else {
try {
((Throwable) data).printStackTrace();
Throwable throwable = (Throwable) data;
JSONObject jsonObject = new JSONObject(throwable.getMessage());
String des = jsonObject.optString("detail");
int status = 0;
status = jsonObject.optInt("status");
if (TextUtils.isEmpty(des)) {
}
} catch (Exception e) {
SMSLog.getInstance().w(e);
}
}
}
};
Ps:SMSSDK已经做了混淆处理,再次混淆会导致不可预期的错误,请在您的混淆脚本中添加如下的配置,跳过对SMSSDK的混淆操作:
-keep class com.mob.**{*;}
-keep class cn.smssdk.**{*;}
-dontwarn com.mob.**
这个时候,大家可能就有疑问了,没有UI界面我如何去输入收到的短信验证码去验证,很简单,自己写个UI就行了啊,一般现在流行的界面可能就是如下图所示了,引用一下图片https://www.jianshu.com/p/91b0b8038dd5
这位大牛的效果还是不错的,我说下自己的思路,简单的就是TextView+Edittext去组合实现的,下面,附上xml资源,大家可以详细了解一下
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorWhite">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_code1"
style="@style/codeTextView" />
<View
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView
android:id="@+id/tv_code2"
style="@style/codeTextView" />
<View
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView
android:id="@+id/tv_code3"
style="@style/codeTextView" />
<View
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView
android:id="@+id/tv_code4"
style="@style/codeTextView" />
<View
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView
android:id="@+id/tv_code5"
style="@style/codeTextView" />
<View
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView
android:id="@+id/tv_code6"
style="@style/codeTextView" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:orientation="horizontal">
<View
android:id="@+id/view1"
android:layout_width="40dp"
android:layout_height="2dp"
android:layout_gravity="bottom"
android:background="@color/colorGray707061" />
<View
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<View
android:id="@+id/view2"
android:layout_width="40dp"
android:layout_height="2dp"
android:layout_gravity="bottom"
android:background="@color/colorGray707061" />
<View
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<View
android:id="@+id/view3"
android:layout_width="40dp"
android:layout_height="2dp"
android:layout_gravity="bottom"
android:background="@color/colorGray707061" />
<View
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<View
android:id="@+id/view4"
android:layout_width="40dp"
android:layout_height="2dp"
android:layout_gravity="bottom"
android:background="@color/colorGray707061" />
<View
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<View
android:id="@+id/view5"
android:layout_width="40dp"
android:layout_height="2dp"
android:layout_gravity="bottom"
android:background="@color/colorGray707061" />
<View
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<View
android:id="@+id/view6"
android:layout_width="40dp"
android:layout_height="2dp"
android:layout_gravity="bottom"
android:background="@color/colorGray707061" />
</LinearLayout>
<lf.com.android.blackfishdemo.view.CodeEditTextView
android:id="@+id/et_code_text"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#00000000"
android:inputType="number"
android:longClickable="false"
android:maxLength="6"
android:textColor="#00000000" />
</RelativeLayout>
乍一看貌似很复杂,其实就是绘制了可以输入6位数的Textview和对应的下标Item,然后上面再被我自定义的EditText所覆盖
/**
* 验证码控件,去掉传统EditText双击选中EditText的内容
* 和去掉光标位置会随点击改变
*/
public class CodeEditTextView extends AppCompatEditText {
private long lastTime = 0;
public CodeEditTextView(Context context) {
super(context);
}
public CodeEditTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CodeEditTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onSelectionChanged(int selStart, int selEnd) {
super.onSelectionChanged(selStart, selEnd);
this.setSelection(this.getText().length());
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN://判断点击事件,防止用户多次点击
long currenTime = System.currentTimeMillis();
if (currenTime - lastTime < 500) {
lastTime = currenTime;
return true;
} else {
lastTime = currenTime;
}
break;
}
return super.onTouchEvent(event);
}
}
自定义的Edittext首先会通过long currenTime = System.currentTimeMillis();获取到当前时间点,判断两个点击时间间隔,从而屏蔽双击事件,长按会走 onSelectionChanged这个方法,所以,设置光标始终在文本后面,也就屏蔽了长按事件
然后通过对Edittext事件进行TextWatcher监听(后面会详细讲解),依次将输入内容分写到Textview上,就完成了上面的效果.至此,如何使用短信验证就告一段落.
好了,同通过短信验证就意味着,用户登录过程已经接近尾声甚至已经结束了,但是,在这里,我还是想一起说了吧,因为短信验证不仅仅用于登录,也用于修改密码,而且,用户输入的内容我们要去判断,不可能用户随便输入一个文本我们就要对其进行一次短信验证吧,毕竟,喜欢找Bug的用户也不少,所以,我们要对用户输入的内容进行判断。这就是我么要说的5.对于用户输入信息的判断.在说这个之前,我先向大家介绍一下,什么是正则表达式。
正则表达式是一种查找以及字符串替换操作。正则表达式在文本编辑器中广泛使用,比如正则表达式被用于:
- 检查文本中是否含有指定的特征词
- 找出文中匹配特征词的位置
- 从文本中提取信息,比如:字符串的子串修改文本与文本编辑器相似,几乎所有的高级编程语言都支持正则表达式。在这样的语境下,“文本”也就是一个字符串,可以执行的操作都是类似的。一些编程语言(比如Perl,JavaScript)会检查正则表达式的语法。
正则表达式的语法是是一种轻量级、简洁、适用于特定领域的编程语言。
关于详解,建议大家去看这篇文章https://www.jianshu.com/p/67af3eeb6798
这里给大家常用正则表达式的链接https://www.cnblogs.com/zxin/archive/2013/01/26/2877765.html
所以,当我们使用正则表达式时候,可利用事件监听
private void setEditTextLitener() {
mEditText.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) {
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
我们可以通过监听afterTextChanged获取到输入内容,从而进行判断,然后去编写我们所需要的逻辑,至此,用户登录过程就完美结束了,欢迎大家留言,提出问题.谢谢大家的阅读。