Android开发日常-编写一个登录界面
看完这篇文章你可以学到什么知识呢?
- 自定义组合控件 封装view,只暴露接口,模块独立,设计思想。
- 会用到正则表达式 如何禁止EditText在获取到焦点的时候拉起键盘
- 学会登录中获取验证码的倒计时效果
- EditText的内容添加与删除
效果图:
我们为了做得好一点,首先需要分析一下!其实对于UI来说,也是可以封装起来的,比如说,我们这里面涉及到了手机号码的检查
涉及到验证码的检查,涉及到是否同意协议来控制按钮是否可用,这些动作我们都可以隐藏起来。把这些所有看得见的,都当成一个View,这个View具备这些功能。
然后适用者暴露接口:1、获取验证码按钮被点击了;2、协议内容被点击了;3、登录按钮被点击了。
除此之外,应该没有其他动作给使用者使用了。
问题点:
1、因为这个界面是平板的界面,我们自己写了一个键盘输入,所以当我们的输入框获取到焦点的时候,不弹出键盘出来
2、做一个倒计时的效果
3、获取到焦点的edittext内容输入
实现
我们要实现这个类,要用一个很大的坑来填充这些内容,所以我们要写一个LoginUiView,继承自RelativeLayout。这个时候,需要实现一些方法:
有没有注意到,前两个构造函数都是this,表示调用自己的构造函数。不同的参数而已。这样子就可以确保不管是哪种方式创建这个类的,都会进入第三个类,也就是同一个入口。
这个类的全部代码如下:
package cn.com.sunofbeaches.loginkeypaddemo;
import android.content.Context;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.lang.reflect.Method;
public class LoginUiView extends RelativeLayout implements LoginKeyboard.KeyPadActionListener {
//手机号码的规则
public static final String REGEX_MOBILE_EXACT = "^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\\d{8}$";
//输入框
private EditText mPhoneNumInputBox;
//验证码输入框
private EditText mVerifyCodeBox;
//登录键盘
private LoginKeyboard mInputKeypad;
//服务条款的文字
private TextView mItemTextView;
//同意协议的选择框
private CheckBox mAgreeCheckBox;
//确定按钮
private TextView mCommitBtn;
private ViewActionCallback mViewActionCallback = null;
private TextView mGetVerifyCodeBtn;
private boolean isVerifyCodeOk = false;
private boolean isPhoneNumberOk = false;
//默认已经选中的
private boolean isItemAgreementOk = true;
public LoginUiView(Context context) {
this(context, null);
}
public LoginUiView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LoginUiView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LayoutInflater.from(context).inflate(R.layout.login_view, this, true);
initView();
disableShowInput();
initListener();
}
/**
* 设置监听
*/
private void initListener() {
mInputKeypad.setKeyPadActionListener(this);
mItemTextView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
//这个是跳转到协议部分
mViewActionCallback.onItemTipsClick();
}
});
/**
* 选择框的点击
*/
mAgreeCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
//如果是没有选中,确定按钮
mCommitBtn.setEnabled(b);
isItemAgreementOk = b;
updateCommitBtnState();
}
});
/**
* 提交数据,登录
*/
mCommitBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if (mViewActionCallback != null) {
String phoneNum = mPhoneNumInputBox.getText().toString().trim();
String verifyCode = mVerifyCodeBox.getText().toString().trim();
mViewActionCallback.onCommitClick(phoneNum, verifyCode);
}
}
});
mPhoneNumInputBox.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
//当内容改变的时候,如果手机号码不正确,获取验证码不能点击
//对数据进行检查
String phoneNum = mPhoneNumInputBox.getText().toString().trim();
boolean isMatch = phoneNum.matches(REGEX_MOBILE_EXACT);
mGetVerifyCodeBtn.setEnabled(isMatch);
isPhoneNumberOk = isMatch;
updateCommitBtnState();
}
@Override
public void afterTextChanged(Editable editable) {
}
});
/**
* 设置验证码输入框的内容变化监听
*/
mVerifyCodeBox.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
//当这个输入以后,去检查登录按钮是否可以点击
if (mVerifyCodeBox.getText().toString().length() >= 4) {
isVerifyCodeOk = true;
} else {
isVerifyCodeOk = false;
}
updateCommitBtnState();
}
@Override
public void afterTextChanged(Editable editable) {
}
});
/**
* 获取验证码
*/
mGetVerifyCodeBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
//点击以后,直接不能点,然后开始倒计时,倒计时完成以后
// 要判断手机号码是否正确,如果不正确,则不能点击
mGetVerifyCodeBtn.setEnabled(false);
mSecond = 60;
mVerifyCodeBox.requestFocus();
startVerifyCodeBtnTextUpdate();
if (mViewActionCallback != null) {
mViewActionCallback.onGetVerifyCodeClick();
}
}
});
}
/**
* 这个方法处理获取验证码的倒计时。
*/
private int mSecond;
private void startVerifyCodeBtnTextUpdate() {
post(new Runnable() {
@Override
public void run() {
if (mSecond >= 0) {
mGetVerifyCodeBtn.setText(mSecond + "S");
mSecond--;
postDelayed(this, 1000);
} else {
String phoneNumber = mPhoneNumInputBox.getText().toString().trim();
boolean matches = phoneNumber.matches(REGEX_MOBILE_EXACT);
mGetVerifyCodeBtn.setText(R.string.get_verify_code_text);
mGetVerifyCodeBtn.setEnabled(matches);
mSecond = 60;
}
}
});
}
private void updateCommitBtnState() {
//满足三个条件,这个按钮才可以适用
//1、手机号码匹配
//2、验证码匹配
//3、协议已经勾选同意
mCommitBtn.setEnabled(isPhoneNumberOk && isVerifyCodeOk && isItemAgreementOk);
}
private void initView() {
mAgreeCheckBox = (CheckBox) this.findViewById(R.id.agree_check_box);
isItemAgreementOk = mAgreeCheckBox.isChecked();
mPhoneNumInputBox = (EditText) this.findViewById(R.id.phone_number_input_box);
mVerifyCodeBox = (EditText) this.findViewById(R.id.verify_code_input_box);
mInputKeypad = (LoginKeyboard) this.findViewById(R.id.input_keypad_view);
mItemTextView = (TextView) this.findViewById(R.id.use_tips_text);
mCommitBtn = (TextView) this.findViewById(R.id.commit_button);
mGetVerifyCodeBtn = (TextView) this.findViewById(R.id.get_verify_code_btn);
}
private void disableShowInput() {
if (android.os.Build.VERSION.SDK_INT <= 10) {
mPhoneNumInputBox.setInputType(InputType.TYPE_NULL);
mVerifyCodeBox.setInputType(InputType.TYPE_NULL);
} else {
Class<EditText> cls = EditText.class;
Method method;
try {
method = cls.getMethod("setShowSoftInputOnFocus", boolean.class);
method.setAccessible(true);
method.invoke(mPhoneNumInputBox, false);
method.invoke(mVerifyCodeBox, false);
} catch (Exception e) {
e.printStackTrace();
}
try {
method = cls.getMethod("setSoftInputShownOnFocus", boolean.class);
method.setAccessible(true);
method.invoke(mPhoneNumInputBox, false);
method.invoke(mVerifyCodeBox, false);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void onNumberClick(View view, String number) {
//数字键盘被点击了
if (getFocusEditTextView() != null) {
int index = getFocusEditTextView().getSelectionStart();
Editable editable = getFocusEditTextView().getText();
editable.insert(index, number);
}
}
private EditText getFocusEditTextView() {
View focusView = findFocus();
if (focusView instanceof EditText) {
return (EditText) focusView;
}
return null;
}
@Override
public void onDelClick(View view) {
deleteContent();
}
/**
* 删除内容..
*/
private void deleteContent() {
if (getFocusEditTextView() != null) {
int index = getFocusEditTextView().getSelectionStart();
if (index > 0) {
Editable editable = getFocusEditTextView().getText();
editable.delete(index - 1, index);
}
}
}
@Override
public boolean onDelLongPress(View view) {
//循环删除
post(new Runnable() {
@Override
public void run() {
deleteContent();
if (getFocusEditTextView() != null && getFocusEditTextView().getSelectionStart() > 0) {
postDelayed(this, 50);
}
}
});
return true;
}
public void setViewActionCallback(ViewActionCallback callback) {
this.mViewActionCallback = callback;
}
/**
* 界面的操作动作
*/
public interface ViewActionCallback {
//确定按钮被点击了
void onCommitClick(String phoneNum, String verifyCode);
//获取验证码被点击了
void onGetVerifyCodeClick();
//打开协议文档被点击了
void onItemTipsClick();
}
}
代码讲解: 一开始,我们把view绑定到这个坑里头,也就是这句代码:
LayoutInflater.from(context).inflate(R.layout.auto_map_login_view, this, true);
inflate,后面两个是重点,this,表示当前这个容器,也就是我们创建的LoginUiView,true表示绑定到这个容器里。所以,这行代码的意思是把这个布局文件里的内容,加载进来,放到这个viewGrop里面,也就是放到LoginUiView里面。
键盘的输入等会再说,接着我们是初始化事件:
我们有什么事件呢?
1、监听两个输入框内容的变化,第一个输入框,只有匹配了手机号码的规则,获取验证码才可以适用,否则是disable,也就是不能点击,除非手机号码是对的;监听第二个框框是用于判断有没有输入验证码,如果没有输入,那么,登录按钮不可以点击。
2、获取验证码这个按钮,点击以后要disable,并且开始倒计时,调用暴露的接口。
3、checkbox这个被点击以后,更新登录按钮是否可以点击,其实登录按钮要满足三个条件才可以点击:手机号码是对的,验证码有输入,同意适用协议。
4、登录按钮不需要做太多的动作,因为前面的动作已经做了相关的检查了。
布局文件的代码如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/login_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="42px"
android:orientation="horizontal">
<ImageView
android:layout_width="50px"
android:layout_height="50px"
android:src="@mipmap/icon_user"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="26px"
android:gravity="center_vertical"
android:text="@string/sunofbeach_account_login_text"
android:textColor="#FD7541"
android:textSize="32px"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/login_title"
android:layout_marginTop="50px"
android:orientation="horizontal"
android:paddingLeft="40px">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<EditText
android:id="@+id/phone_number_input_box"
android:layout_width="430px"
android:layout_height="60px"
android:background="@drawable/shape_login_input_bg"
android:drawableLeft="@mipmap/icon_phone_number"
android:drawablePadding="14px"
android:hint="@string/phone_number_input_hint_text"
android:maxLength="11"
android:paddingLeft="34px"
android:textColor="@color/white"
android:textSize="22px"/>
<RelativeLayout
android:layout_width="430px"
android:layout_height="60px"
android:layout_marginTop="16px">
<EditText
android:id="@+id/verify_code_input_box"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/shape_login_input_bg"
android:drawableLeft="@mipmap/icon_password"
android:drawablePadding="14px"
android:hint="@string/please_input_verify_code_text"
android:maxLength="8"
android:paddingLeft="34px"
android:textColor="@color/white"
android:textSize="22px"/>
<TextView
android:id="@+id/get_verify_code_btn"
android:layout_width="110px"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="34px"
android:enabled="false"
android:gravity="center"
android:text="@string/get_verify_code_text"
android:textColor="@drawable/selector_text_state_color"
android:textSize="22px"/>
<View
android:layout_width="1px"
android:layout_height="match_parent"
android:layout_marginBottom="10px"
android:layout_marginRight="9px"
android:layout_marginTop="10dp"
android:layout_toLeftOf="@id/get_verify_code_btn"
android:background="@drawable/line_bg"/>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="44px"
android:gravity="center_vertical">
<CheckBox
android:id="@+id/agree_check_box"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="22px"
android:button="@drawable/selector_checkbox_button"
android:checked="true"
android:padding="2px"/>
<TextView
android:id="@+id/use_tips_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10px"
android:text="@string/user_tbox_text"
android:textColor="#FD7541"
android:textSize="20px"/>
</LinearLayout>
<TextView
android:id="@+id/commit_button"
android:layout_width="match_parent"
android:layout_height="60px"
android:layout_marginTop="18px"
android:background="@drawable/selector_commit_button_bg"
android:enabled="false"
android:gravity="center"
android:text="@string/commit_text"
android:textColor="@color/white"
android:textSize="24px"/>
</LinearLayout>
<cn.com.sunofbeaches.loginkeypaddemo.LoginKeyboard
android:id="@+id/input_keypad_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="42px"/>
</LinearLayout>
</RelativeLayout>
效果是这样子的:
这里面有一个键盘,它的代码其实是个view来的,跟上面一个,编写一个组合控件。
代码如下:
package cn.com.sunofbeaches.loginkeypaddemo;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
public class LoginKeyboard extends LinearLayout implements View.OnClickListener {
private ImageView mDelBtn;
private KeyPadActionListener mKeyPadActionListener = null;
public LoginKeyboard(Context context) {
this(context, null);
}
public LoginKeyboard(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LoginKeyboard(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
View.inflate(context, R.layout.include_login_keyboard, this);
initView();
initListener();
}
/**
* 找到各个控件
*/
private void initView() {
this.findViewById(R.id.key_pad_number_1).setOnClickListener(this);
this.findViewById(R.id.key_pad_number_2).setOnClickListener(this);
this.findViewById(R.id.key_pad_number_3).setOnClickListener(this);
this.findViewById(R.id.key_pad_number_4).setOnClickListener(this);
this.findViewById(R.id.key_pad_number_5).setOnClickListener(this);
this.findViewById(R.id.key_pad_number_6).setOnClickListener(this);
this.findViewById(R.id.key_pad_number_7).setOnClickListener(this);
this.findViewById(R.id.key_pad_number_8).setOnClickListener(this);
this.findViewById(R.id.key_pad_number_9).setOnClickListener(this);
this.findViewById(R.id.key_pad_number_0).setOnClickListener(this);
mDelBtn = (ImageView) this.findViewById(R.id.key_pad_delete);
}
private void initListener() {
mDelBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
//把这个回调出去
if (mKeyPadActionListener != null) {
//处理删除按钮被点击了
mKeyPadActionListener.onDelClick(view);
}
}
});
mDelBtn.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
//这个是删除按钮被长按了
if (mKeyPadActionListener != null) {
//处理删除按钮被点击了
return mKeyPadActionListener.onDelLongPress(view);
}
return false;
}
});
}
@Override
public void onClick(View view) {
//数字按钮被点击了,把所点击的内容传到外面去
if (mKeyPadActionListener != null) {
String number = ((TextView) view).getText().toString();
mKeyPadActionListener.onNumberClick(view, number);
}
}
/**
* 设置按钮的点击事件
*
* @param listener
*/
public void setKeyPadActionListener(KeyPadActionListener listener) {
this.mKeyPadActionListener = listener;
}
/**
* 这个接口用于通知点击事件
* 包括数字键盘被点击了,删除键盘被点击了
* 删除按钮被长按了。
*/
public interface KeyPadActionListener {
void onNumberClick(View view, String number);
void onDelClick(View view);
boolean onDelLongPress(View view);
}
}
其他的drawable文件:
从压缩包里下载吧,这里面就不在给大看这些不太重要的文件了。
使用
使用很简单,直接把登录的View的全路径名称,复制到要适用的地方即可。
接着,我们在Activity的代码里找到这个View,给它设置相关的接口实现即可。
package cn.com.sunofbeaches.loginkeypaddemo;
import android.app.Activity;
import android.os.Bundle;
public class LoginActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
LoginUiView loginView = this.findViewById(R.id.login_view);
loginView.setViewActionCallback(new LoginUiView.ViewActionCallback() {
@Override
public void onCommitClick(String phoneNum, String verifyCode) {
}
@Override
public void onGetVerifyCodeClick() {
}
@Override
public void onItemTipsClick() {
}
});
}
}
到此代码结束,有问题到网站里发帖子吧!
测试
测试,手机号码的正确性,这个由测试人员来完成了,不过我们简单地使用一下。如果手机号码正确,才可以点击获取验证码!
okay 到此,欢迎点赞!