需求描述
各家电商App的登陆页面大同小异,要么是用户名与密码组合等等,要么是手机号码和验证码组合登录,若是做好一点的,则会提供找回密码与记住密码功能。先来看一下登录页面是什么样,因为有两种组合登陆方式,所以登陆页面页分成两个效果图。
从以上两个登录效果图可以看到,密码登录与验证码登录的界面主要存在一下几点区别:
(1)密码输入框和验证码输入框的左侧标题以及输入框内部的提示语各不相同。
(2)如果是密码登录,则需要支持找回密码。如果是验证码登录,则需要支持向用户手机发送验证码。
(3)密码登录可以提供记住密码的功能,而验证码每次的数值不一样,无需也没法记住验证码。
对于找回密码功能,一般直接跳到找回密码页面,在该页面输入和确认新的密码,并校验找回密码的合法性(通过短信验证码检查),据此勾勒出密码找回页面的轮廓概貌,如下图所示。
在找回密码的操作过程中,为了更好地增强用户体验,有必要在几个关键节点处提醒用户。比如发送成功验证码之后,要及时提示用户注意查收短信,这里暂且做成提醒对话框的形式。又比如密码登录成功之后,也要告知用户已经修改成功登录,注意继续后面的操作。
界面设计
用户登录与找回密码界面看似简单,用到的控件却不少。大致从上到下、从左到右分布着下列Android控件。
1.单选按钮RadioButton: 用来区分是密码登录和验证码登录。
2.文本视图TextView: 输入框左侧要显示此处应该输入什么信息。
3.编辑框EditText:用来输入手机号码、密码和验证码。
4.复选框CheckBox:用于判断是否记住密码。
5.按钮Button: 除了“登录”按钮,还要忘记密码和获取验证码两个按钮。
6.线性布局LinearLayout:整体界面从上到下排列,用到了垂直方向的线性布局。
7.相对布局:RelativeLayout:忘记密码按钮和密码输入框是叠加的。且“忘记密码”与上级视图右对齐。
8.单选组RadioGroup:密码登录和验证码登录这两个单选按钮,需要放在单选组之中。
9.提醒对话框AlertDialog:为了演示方便,获取验证码与登陆成功都通过提醒对话框向用户反馈结果。
另外,整个登录模块是由登录页面和找回密码页面组成,因此这两个页面之间要进行数据交互,也就是在页面跳转之时传递参数。譬如,从登录页面跳到找回密码界面,要携带唯一地标识的手机号码作为请求参数,不然找回密码界面不知道要给哪个手机号码修改密码。同时,从找回密码页面回到登录页面,也要将修改之后的新密码作为应答参数传出去,否则登录页面不知道密码被改成什么了。
注意事项
ViewUtil.java
package com.example.retrievepassword.util;
import android.app.Activity;
import android.content.Context;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
public class ViewUtil {
public static void hideAllInputMethod(Activity act) {
// 从系统服务中获取输入法管理器
InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm.isActive()) { // 软键盘如果已经打开则关闭之
imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
}
}
public static void hideOneInputMethod(Activity act, View v) {
// 从系统服务中获取输入法管理器
InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE);
// 关闭屏幕上的输入法软键盘
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
}
}
action_login_main.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="5dp">
<RadioGroup
android:id="@+id/rg_login"
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal"
>
<RadioButton
android:id="@+id/rb_password"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:checked="true"
android:gravity="left|center"
android:text="密码登录"
android:textColor="@color/black"
android:textSize="17sp"
/>
<RadioButton
android:id="@+id/rb_verifycode"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:checked="false"
android:gravity="left|center"
android:text="验证码登录"
android:textColor="@color/black"
android:textSize="17sp"
/>
</RadioGroup>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp">
<TextView
android:id="@+id/tv_phone"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="手机号码"
android:textColor="@color/black"
android:textSize="17sp"
/>
<EditText
android:id="@+id/et_phone"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:layout_toRightOf="@+id/tv_phone"
android:background="@drawable/editable_selector"
android:gravity="left|center"
android:hint="请输入手机号码"
android:inputType="number"
android:maxLength="11"
android:textColor="@color/black"
android:textSize="17sp"
android:textColorHint="@color/grey"
/>
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp">
<TextView
android:id="@+id/tv_password"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="登录密码:"
android:textColor="@color/black"
android:textSize="17sp"
/>
<RelativeLayout
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:background="@drawable/editable_selector"
android:gravity="left|center"
android:hint="请输入密码"
android:inputType="numberPassword"
android:maxLength="6"
android:textColor="@color/black"
android:textColorHint="@color/grey"
android:textSize="17sp">
<Button
android:id="@+id/btn_forget"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="忘记密码"
android:textColor="@color/black"
android:textSize="17sp"
/>
</RelativeLayout>
</RelativeLayout>
<CheckBox
android:id="@+id/ck_remember"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:button="@drawable/checkbox_selector"
android:checked="false"
android:padding="10dp"
android:text="记住密码"
android:textColor="@color/black"
android:textSize="17sp"
/>
<Button
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="登录"
android:textColor="@color/black"
android:textSize="20sp"
/>
</LinearLayout>
LoginMainActivity.java
package com.example.retrievepassword;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.example.retrievepassword.util.ViewUtil;
import java.util.Random;
@SuppressLint("DefaultLocale")
public class LoginMainActivity extends AppCompatActivity implements View.OnClickListener {
private RadioGroup rg_login;//声明一个单选组对象
private RadioButton rb_password;//声明一个单选按钮对象
private RadioButton rb_verifycode;//声明一个单选按钮按钮对象
private EditText et_phone;//声明一个编辑框对象
private TextView tv_password;//声明一个文本视图对象
private EditText et_password;//声明一个编辑狂对象
private Button btn_forget;//声明一个按钮控件对象
private CheckBox ck_remember;//声明一个复选框对象
private int mRequestCode = 0;//跳转页面时的请求常量
private boolean isRemember = false;//是否记住密码
private String mPassword = "111111";//默认密码
private String mVerifyCode;//验证码
@SuppressLint("WrongViewCast")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login_main);
rg_login = findViewById(R.id.rg_login);
rb_password = findViewById(R.id.rb_password);
rb_verifycode = findViewById(R.id.rb_verifycode);
et_phone=findViewById(R.id.et_phone);
tv_password=findViewById(R.id.tv_password);
et_password=findViewById(R.id.et_password);
btn_forget = findViewById(R.id.btn_forget);
ck_remember = findViewById(R.id.ck_remember);
//设置监听器
rg_login.setOnCheckedChangeListener(new RadioListener());//监听按钮组改变
ck_remember.setOnCheckedChangeListener(new CheckListener());//监听记住密码复选框
et_phone.addTextChangedListener(new HideTextWatcher(et_phone,11));//监听手机号码输入框的文本改变
et_password.addTextChangedListener(new HideTextWatcher(et_password,6));//监听登录密码输入框的改变
btn_forget.setOnClickListener(this);//监听忘记密码按钮的点击事件
findViewById(R.id.btn_login).setOnClickListener(this);//监听登录按钮的点击事件
}
//定义登录方式的单选监听器
private class RadioListener implements RadioGroup.OnCheckedChangeListener {
@Override
public void onCheckedChanged(RadioGroup group,int checkId) {
if(checkId == R.id.rb_password) { //选择了密码登录
tv_password.setText("登录密码: ");
et_password.setHint("请输入密码");
btn_forget.setText("记住密码");
ck_remember.setVisibility(View.VISIBLE);
} else if(checkId == R.id.rb_verifycode) {
tv_password.setText("验证码:");
et_password.setHint("请输入验证码:");
btn_forget.setText("获取验证码:");
ck_remember.setVisibility(View.GONE);
}
}
}
//定义是否记住密码的监听器
private class CheckListener implements CompoundButton.OnCheckedChangeListener {
@Override
public void onCheckedChanged(CompoundButton buttonView,boolean isChecked) {
if(buttonView.getId() == R.id.ck_remember) {
isRemember = isChecked;
}
}
}
//定义一个编辑框监听器,再输入文本达到指定长度时自动隐藏输入法
private class HideTextWatcher implements TextWatcher {
private EditText mView;//声明一个编辑框对象
private int mMaxLength;//声明一个最大长度变量
public HideTextWatcher(EditText v,int maxLength) {
super();
mView = v;
mMaxLength = maxLength;
}
// 在编辑框的输入文本变化前触发
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
// 在编辑框的输入文本变化时触发
public void onTextChanged(CharSequence s, int start, int before, int count) {}
// 在编辑框的输入文本变化后触发
public void afterTextChanged(Editable s) {
String str = s.toString();//获得已输入的文本字符串
//输入文本达到11位(如手机号码)或者达到6位,如登录密码时关闭输入法
if((str.length() == 11 && mMaxLength == 11) || (str.length() == 6 && mMaxLength == 6)) {
ViewUtil.hideOneInputMethod(LoginMainActivity.this,mView);
}
}
}
@Override
public void onClick(View v) {
String phone = et_phone.getText().toString();
if(v.getId() == R.id.btn_forget) //点击了忘记密码按钮
{
if(phone.length() < 11) {
//手机号码长度不足11位
Toast.makeText(this,"请输入正确的手机号",Toast.LENGTH_SHORT).show();
return;
}
if(rb_password.isChecked()) {
//选择了密码方式校验,此时要跳转到找回密码界面
Intent intent = new Intent(this,LoginForgetActivity.class);
intent.putExtra("phone",phone);
startActivityForResult(intent,mRequestCode);//携带意图返回上一个页面
} else if(rb_verifycode.isChecked()) {
//选择了验证码方式校验,提示用户记住六位验证码数字
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("请记住验证码");
builder.setMessage("手机号" + phone + "本次验证码是: " + mVerifyCode + ",请输入验证码!");
builder.setPositiveButton("好的",null);
AlertDialog alert = builder.create();
alert.show();//显示提醒对话框
}
} else if(v.getId() == R.id.btn_login) {
//点击了登录按钮
if(phone.length() < 11) {
//手机号码不足11位
Toast.makeText(this,"请输入正确的手机号",Toast.LENGTH_SHORT).show();
}
if(rb_password.isChecked()) {
//密码方式校验
if(!et_password.getText().toString().equals(mPassword)) {
Toast.makeText(this,"请输入正确的密码",Toast.LENGTH_SHORT).show();
} else {
//密码校验通过
loginSuccess();//提示用户登录成功
}
} else if(rb_verifycode.isChecked()) {
//验证码方式校验
if(!et_password.getText().toString().equals(mVerifyCode)) {
Toast.makeText(this,"请输入正确的验证码",Toast.LENGTH_SHORT).show();
} else {
//验证码校验通过
loginSuccess();//提示用户登录成功
}
}
}
}
//从下一个页面携带参数返回当前页面时触发
@Override
protected void onActivityResult(int requestCode,int resultCode,Intent data) {
super.onActivityResult(requestCode,resultCode,data);
if(requestCode == mRequestCode && data != null) {
//用户密码已经改为新的密码,故更新密码变量
mPassword = data.getStringExtra("new_password");
}
}
//从修改密码页面返回登录页面,要清空密码的输入框
@Override
protected void onRestart() {
super.onRestart();
et_password.setText("");
}
//校验通过,登录成功
private void loginSuccess() {
String desc = String.format("您的手机号码是:%s,恭喜您通过登录验证,点击确定按钮返回上个页面",et_phone.getText().toString());
//以下弹出提醒对话框,提示用户登录成功
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("登录成功");
builder.setMessage(desc);
builder.setPositiveButton("确定返回", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
finish();//结束当前活动页面
}
});
builder.setNegativeButton("我再看看",null);
AlertDialog alert = builder.create();
alert.show();//显示提醒对话框
}
}
activity_login_forget.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="5dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp">
<TextView
android:id="@+id/tv_password_first"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="输入新密码:"
android:textColor="@color/black"
android:textSize="17sp"
/>
<EditText
android:id="@+id/et_password_first"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:layout_toRightOf="@+id/tv_password_first"
android:background="@drawable/editext_selector"
android:gravity="left|center"
android:hint="请输入新密码"
android:inputType="numberPassword"
android:maxLength="11"
android:textColor="@color/black"
android:textColorHint="@color/grey"
android:textSize="17sp"
/>
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp" >
<TextView
android:id="@+id/tv_password_second"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="确认新密码:"
android:textColor="@color/black"
android:textSize="17sp" />
<EditText
android:id="@+id/et_password_second"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:layout_toRightOf="@+id/tv_password_second"
android:background="@drawable/editext_selector"
android:gravity="left|center"
android:hint="请再次输入新密码"
android:inputType="numberPassword"
android:maxLength="11"
android:textColor="@color/black"
android:textColorHint="@color/grey"
android:textSize="17sp" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp" >
<TextView
android:id="@+id/tv_verifycode"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text=" 验证码:"
android:textColor="@color/black"
android:textSize="17sp" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toRightOf="@+id/tv_verifycode" >
<EditText
android:id="@+id/et_verifycode"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:background="@drawable/editext_selector"
android:gravity="left|center"
android:hint="请输入验证码"
android:inputType="numberPassword"
android:maxLength="6"
android:textColor="@color/black"
android:textColorHint="@color/grey"
android:textSize="17sp" />
<Button
android:id="@+id/btn_verifycode"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:gravity="center"
android:text="获取验证码"
android:textColor="@color/black"
android:textSize="17sp" />
</RelativeLayout>
</RelativeLayout>
<Button
android:id="@+id/btn_confirm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="确 定"
android:textColor="@color/black"
android:textSize="20sp" />
</LinearLayout>
colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
<color name="black">#000000</color>
<color name="white">#ffffff</color>
<color name="grey">#cccccc</color>
</resources>
checkbox_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:drawable="@drawable/check_choose"></item>
<item android:drawable="@drawable/check_unchoose"></item>
</selector>
editable_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true" android:drawable="@drawable/shape_edit_focus"></item>
<item android:drawable="@drawable/shape_edit_normal"></item>
</selector>
editext_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true" android:drawable="@drawable/shape_edit_focus"></item>
<item android:drawable="@drawable/shape_edit_normal"></item>
</selector>
shape_edit_focus.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#ffffff"/>
<stroke android:width="1dp" android:color="#00ffff"/>
<corners android:radius="5dp"/>
<padding android:bottom="2dp" android:left="2dp" android:right="2dp" android:top="2dp"/>
</shape>
shape_edit_normal.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#ffffff"/>
<stroke android:width="1dp"/>
<corners android:radius="5dp"/>
<pading android:bottom="2dp" android:left="2dp" android:right="2dp" android:top="2dp"/>
</shape>
利用共享参数实现记住密码功能
(1)声明一个共享参数对象,并在onCreate中调用getSharedPreferences方法获取共享参数的实例。
(2)登录成功时,如果用户勾选了“记住密码”,就使用共享参数保存手机号码与密码,也就是在loginSuccess方法中增加代码.
// 校验通过,登录成功
private void loginSuccess() {
String desc = String.format("您的手机号码是%s,恭喜你通过登录验证,点击“确定”按钮返回上个页面",
et_phone.getText().toString());
// 以下弹出提醒对话框,提示用户登录成功
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("登录成功");
builder.setMessage(desc);
builder.setPositiveButton("确定返回", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish(); // 结束当前的活动页面
}
});
builder.setNegativeButton("我再看看", null);
AlertDialog alert = builder.create();
alert.show(); // 显示提醒对话框
// 如果勾选了“记住密码”,就把手机号码和密码都保存到共享参数中
if (isRemember) {
SharedPreferences.Editor editor = mShared.edit(); // 获得编辑器的对象
editor.putString("phone", et_phone.getText().toString()); // 添加名叫phone的手机号码
editor.putString("password", et_password.getText().toString()); // 添加名叫password的密码
editor.commit(); // 提交编辑器中的修改
}
}