相信普通的用户在使用手机的时候经常遇到一个输入框没输入的时候会有灰色的提示,可一旦输入些许字符后,用户很快忘记了这个输入框要输入什么,但是退回去却又要重新输入,对于用户的这个要求,谷歌看在眼里,在2015推出了TextInputLayout来满足这个需求。那么对于怎么使用这个控件,我将用模仿网易邮箱大师的登录界面来一一告诉大家怎么畅快的玩起来。
下面来看看,我们将要实现的界面。
首先导入:
implementation 'com.android.support:design:29.0.0'
对于TextInputLayout并不能单独的使用,必须配合TextInputEditText使用起来。下面我们来看看登录界面的布局XML代码:
上面只是简单的两个输入框EditText,与一个Button,唯一不同是EditText被TextInputLayout所包裹。这样我们的邮箱登录初始化界面就完成了。现在运行界面,我们就可以得到有动画效果的提示信息界面,如下:
邮箱提示可以用android.support.v7.widget.ListPopupWindow来实现。它与ListView用法相同,也与PopupMenu,PopupWindow用法一样(此一样是说的其中的特性,不是完全)。
现在我们来初始化ListPopupWindow:
不过先还是写一下用到的所有成员变量:
private TextInputLayout usernameTil;//用户名输入框包裹的TextInputLayout
private TextInputLayout passwordTil;//密码输入框包裹的TextInputLayout
private TextInputEditText usernameEdit;//用户名输入框
private Button okBut;
private ListPopupWindow listPopupWindow;//输入框弹出菜单
private List strLists = new ArrayList<>();//adatper参数
private List strListsFlag = new ArrayList<>();//记录所有邮箱提示,当匹配后提示邮箱减少后可以直接从该list中重启获取添加到strLists;
private ArrayAdapter adapter;//listpopupwindow适配器
private int numStartFlag = 0;//输入框的变化返回的永远是整个字符,要想字符加入提示框不重复,必须获取输入框@字符的索引
private int mLastNumFlag=0;//记录输入用户名后的前一次字符数,也就是输入是增加了还是减少了。
private boolean isHaveFlag=true;//记录删除用户名输入框的一个字符后,其还有匹配原来的邮箱吗?没有为false则需增加提示框邮箱。
后面的代码除方法外全部在onCreate()中:
this.listPopupWindow = new ListPopupWindow(this);//初始化
this.adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, strLists);
this.listPopupWindow.setAdapter(adapter);
this.listPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
this.listPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
this.listPopupWindow.setAnchorView(usernameTil);//设置弹出菜单相对谁的位置
this.listPopupWindow.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
usernameEdit.setText(strLists.get(position));//当点击提示弹出菜单的时候,设置输入框与弹出菜单选项一致
listPopupWindow.dismiss();//当然点击菜单后,关闭菜单
}
});
适配器自不必说,setAnchor设置相对谁的位置。当然是我们的TextInputLayout,那么将会显示在其下面。当你输入一段输入到输入框的时候,点击ListPopupWindow中的选项,代码将会将选项写入到输入框,且这个时候也要关闭ListPopupWindow提示信息。
但是TextInputLayout虽然说可以直接从其获取他包裹的EditText的文本信息,可是其没有负责监听输入框变化的回调函数,故还是要获取EditText的控件进行操作。实现其监听如下:
this.usernameEdit.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
listPopupWindow.show();//当开始输入的时候弹出提示listPopupWindow
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
listPopupWindowChanged(s);
}
@Override
public void afterTextChanged(Editable s) {
}
});
我们将提示算法分离出来写入listPopupWindowChanged(s)中,代码如下:
private void listPopupWindowChanged(CharSequence s) {
//这就是大名鼎鼎的算法,我花了2个小时写的,只能说我算法不行啊。既然一个小小的匹配算法花了我2个小时
//当输入框已经输入@符号后
if (s.toString().indexOf('@') != -1) {
//其实这里还可以判断一下@是不是文本框最后一个字符,判断后可以节省一个for循环时间。
//比如当我输入"liyuanjinglyj@1"到输入框后,这个时候,凡是没有包含这个的提示listpopupwindow子项都会被删除
for (int i = 0; i < strLists.size(); i++) {
if (!strLists.get(i).trim().contains(s.toString().trim())) {
strLists.remove(i);
i--;//那么删除后strLists长度肯定减一,不然,不是匹配掉,就是数组越界。
}
}
//当我在进行删去文本框字符的时候调用里面的方法,
// 因为我输入的时候,要匹配我输入的邮箱,已经删除了以前的listpopupwindow子项,
//这个时候我删除字符,那么以前因为匹配而删除的并且有匹配能的应该添加到listpopupwindow中
if (s.toString().trim().length()0){
for (int j=0;j
对于这个算法,注释我写的已经很详细了。细节看注释。
不过这里还有一个问题,有必要说明一下,我的字符串数组是写在资源文件res/value/array.xml中的,但字符串数组string-array中
这里暂且设置一个疑问,如果谁知道如何解决可以告诉我,回复在文章下面。
这里我的处理方式如下:
this.strLists = Arrays.asList(getResources().getStringArray(R.array.email_string_array));
this.strLists = new ArrayList<>(this.strLists);//上面解释了,如下不这样,那么匹配删除就是抛出上面的异常
//为什么还要循环添加@,而不写入资源文件
//是因为@在资源文件里面报错。具体怎么解决,我没有百度到,好像从来没人这么用。
for (int i = 0; i < this.strLists.size(); i++) {
this.strLists.set(i, "@" + this.strLists.get(i));
}
this.strListsFlag = new ArrayList<>(this.strLists);//复制给保存弹出listpopupwindow,免得删除提示后无法恢复
为什么要strLists=new ArrayList<>(this.strLists)原因如下:
Arrays.asList() 返回java.util.Arrays$ArrayList,而不是ArrayList。Arrays$ArrayList和ArrayList都是继承AbstractList,remove,add等method在AbstractList中是默认throw UnsupportedOperationException而且不作任何操作,ArrayList 覆盖这些method来对list进行操作,但是Arrays$ArrayList没有覆盖 remove(),add()等,所以使用就会throw UnsupportedOperationException。
对于这类需求想必随处可以,比如要你输入电话号码,你去随便输入一通,肯定会提示你格式不正确,那么TextInputLayout自带显示错误信息功能,不需要你设置额外的控件来提示用户,你会用到TextInputLayout中的setError与setErrorEnabled,前者是提示用户输入错误的信息,后则是当你输入错误后又输入正确,那么其会隐藏刚才的错误信息。
下面我们写按钮的点击事件的监听方法:
this.okBut.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
hideKeyboard();
String username = usernameTil.getEditText().getText().toString();//获取用户名输入框字符串
String password = passwordTil.getEditText().getText().toString();//获取密码输入框字符串
if (!validateEmail(username)) {
//如果错误显示错误信息
usernameTil.setError(getResources().getString(R.string.login_username_validate_error_string));
return;
} else {
//否则隐藏上次错误
usernameTil.setErrorEnabled(false);
}
if (!validatePassword(password)) {
//设置密码错误信息
passwordTil.setError(getResources().getString(R.string.login_password_validate_error_string));
return;
} else {
//否则隐藏上次错误
passwordTil.setErrorEnabled(false);
}
Snackbar.make(MainActivity.this.findViewById(R.id.lyj_layout), "登录成功", Snackbar.LENGTH_SHORT).show();
}
});
当你点击按钮的时候,你会判断你的密码和用户名是否输入正确,如果不正确会得到如图所示的提示:
主要的代码都在上面,不过这里还有一个几个附带功能代码有必要介绍一下:
①点击按钮关闭输入键盘
每个应用的登录界面在点击登录按钮后输入键盘都会被关闭,如果你不关闭,会降低用户的体验的,那么关闭输入键盘的代码如下:
private void hideKeyboard() {
View view = getCurrentFocus();
if (view != null) {
((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)).
hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
}
}
②验证输入的是否为邮箱
private static final String EMAIL_PATTERN = "^[a-zA-Z0-9#_~!$&'()*+,;=:.\"(),:;<>@\\[\\]\\\\]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*$";
private Pattern pattern = Pattern.compile(EMAIL_PATTERN);
private Matcher matcher;
/***
* 验证输入格式是否为邮箱
* @param email
* @return
*/
public boolean validateEmail(String email) {
matcher = pattern.matcher(email);
return matcher.matches();
}
至于那一串正则表达式有兴趣可以了解,没兴趣没关系,匹配字符串永远就那么几个比如电话,密码,邮箱,姓名等,百度上经典的完整的正则表达式一大堆,比你写的好的人多了。基本了解符号意思就够了,用的时候复制粘贴。
③密码验证
这里简单设置了大于5位:
public boolean validatePassword(String password) {
return password.length() > 5;
}
本篇博文源码下载地址:
http://download.csdn.net/detail/liyuanjinglyj/9388101
记得前面用到一句话snackbar,那么这里还是一起介绍了吧。专门用一篇博文讲解这个类似于Toast的Snackbar有点大材小用的意思,不过你真的完全了解Snackbar怎么使用吗?下面我们将一一介绍其使用方式。
this.fabOne.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
snackbar.dismiss();
}
});
this.fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
snackbar = Snackbar.make(coordinatorLayout, "我是Snackbar", Snackbar.LENGTH_SHORT)
.setAction("你好", new View.OnClickListener() {
@Override
public void onClick(View v) {
PopupMenu popupMenu = new PopupMenu(MainActivity.this, fab);
popupMenu.getMenuInflater().inflate(R.menu.menu_main, popupMenu.getMenu());
popupMenu.show();
}
});
snackbar.setDuration(4000);
snackbar.setActionTextColor(ColorStateList.valueOf(Color.RED));
snackbar.getView().setBackgroundColor(Color.BLUE);
snackbar.setCallback(new Snackbar.Callback() {
@Override
public void onShown(Snackbar snackbar) {
super.onShown(snackbar);
Log.i("liyuanjinglyj", "我显示了你知道吗?");
}
@Override
public void onDismissed(Snackbar snackbar, int event) {
super.onDismissed(snackbar, event);
Log.i("liyuanjinglyj", "我关闭了你知道吗?");
}
});
snackbar.show();
}
});
上面的代码基本囊括了Snackbar的所有方法。下面我们将一一介绍其使用。
①其构造方法有两个如下:
make(View view, int resId, int duration)
Make a Snackbar to display a message.
make(View view, CharSequence text, int duration)
Snackbar will try and find a parent view to hold Snackbar's view from the value given to view
.
唯一的区别在于第二个参数,一个引用资源ID字符串,一个直接使用字符串。
②setAction:其有也有两个方法与构造方法一样,引用字符串与直接使用字符串的区别。
效果如下:
显示的字符串在黑色圈圈里,并且点击后会调用回调函数,与点击Button效果一样。
③setDuration:设置其显示的时间,单位毫秒,这里为4毫秒。
④setActionTextColor:就是setAction第一个字符串的颜色。
⑤getView().setBackgroundColor(Color.BLUE):设置其snackbar背景
⑥setCallback:设置回调函数,负责监听snackbar的显示与隐藏:显示调用onShown方法,隐藏调用onDismissed。效果如下:
⑦show():很显然为显示
⑧dismiss:当显示的snackbar还显示在界面没有关闭的时候,调用该方法,snackbar会立即关闭。
这段代码实现的效果就如下:
记住将snackbar构造方法的第一个参数设置为CoordinatorLayout容器,就不会遮盖FAB按钮。