【Android】自定义控件实现自动补齐邮箱后缀的输入框
来源:互联网 发布:成交量彩带均线源码 编辑:程序博客网 时间:2024/05/01 07:16
自定义控件实现自动补齐邮箱后缀输入框
前言
前段时间项目中需要实现一个能够自动补齐,并判断邮箱格式是否正确的控件,在GitHub上学习了几位大神的思路,自己结合需求,实现了一下,写的不好的地方,欢迎大家留言指教。先看一下效果:
我们可以看到,本文实现的邮箱输入框主要做了以下几件事:
- 自定义输入框的样式,包括背景色、hint颜色、字体颜色、输入框形状等。
- 实时校验输入的邮箱格式是否正确。
- 校验结果正确,根据校验结果在输入框右侧显示不同的图片以提示用户。
- 如果用户输入的内容可以自动补齐邮箱后缀,帮助用户补齐。当触摸控件外部,把补齐内容写入edittext。
思路
下面我们对每点需求进行分析:
需求1:可以通过常用的自定义控件实现,这点在接下来的代码中呈现。
需求2:实时校验,第一反应应该联想到给我们的EditText添加一个监听来监听输入的内容,在afterTextChanged里面使用正则表达式对输入内容进行校验,这个接下来在代码中呈现。
需求3:我们知道EditText以及TextView其实自带一个setCompoundDrawables()方法,这个方法可以给我们的EditText/TextView的上下左右设置一个图片,但是使用这个方法有一个弊端就是只能放一张图片,我们在实现需求4的时候使用了这个方法来在输入内容的尾部画出我们要补全的内容,所以这里不再使用这个方法设置一个用来提示用户的图片。转而考虑实现一个自定义ViewGroup,在ViewGroup的右边放一个imageview来呈现结果。
需求4:实时校验的结果取得后,我们希望讲我们的补齐内容以类似于hint的灰色文字的形式呈现在输入内容的右边,简单的设置文本不能满足这个需求,所以考虑使用画笔画出我们想要补齐的文字,再通过EditText的setCompoundDrawables方法将我们画出的结果放在右边。
实现
实力分(bi)析(bi)了一波,两横一竖就是干!上代码:
/** * 自定义一个线性布局 * @创建人: lingeng */public class AutoCompleteEmailView extends LinearLayout implements TextWatcher, View.OnFocusChangeListener, View.OnTouchListener { ImageView iv_check;//右侧显示校验结果的ImageView LinearLayout ll_container;//整个控件的线性布局 EditText et_email;//我们的输入框 Drawable ok;//校验正确图片 Drawable error;//错误图片 String hint; int txt_color; Drawable bg_drawable; int hint_color; float txt_size; private Bitmap mBitmap; private int mHeight; private int mWidth; private Paint mPaint; private int mBaseLine; private Canvas mCanvas; private BitmapDrawable mDrawable; private boolean mFlag;//标记是否需要绘制空文本:false不需要,true需要 private String mAddedText; private float mPaddingStart; private float mPaddingEnd; //定义默认资源,当我们不去自定义这些属性的时候,有一个默认的值 final Drawable default_bg_color = getContext().getResources().getDrawable(R.drawable.edit_3dffffff);//整个控件的背景色 final int default_txt_color = getContext().getResources().getColor(R.color.beijingbai);//输入字颜色 final int default_hint_color = getContext().getResources().getColor(R.color.hint_txt_color);//hint颜色 final int default_txt_size = getContext().getResources().getDimensionPixelSize(R.dimen.step_hint_size_small); final Drawable default_ok_res = getContext().getResources().getDrawable(R.drawable.icon_edit_v_ok);//实时校验通过图标 final Drawable default_err_res = getContext().getResources().getDrawable(R.drawable.icon_edit_v_err);//实时校验错误图标 final float default_content_padding_start = 5; final float default_content_padding_end = 10; /** * 正则表达式:验证邮箱 */ public static final String REGEX_EMAIL = "\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*"; /** * 常用邮箱 */ final String[] emails = new String[]{"@gmail.com", "@yahoo.com", "@hotmail.com", "@aol.com"}; /** *这个接口用来返回我们的edittext,以便外部可以获取我们的输入框的内容 */ public EditText getEt_email() { return et_email; } public AutoCompleteEmailView(Context context) { super(context); } //在这个构造方法中,我们获取在xml中设置的各项参数 public AutoCompleteEmailView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.auto_complete_email_view, this); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.AutoCompleteEmailView);//获取装载参数的TypeArray ok = typedArray.getDrawable(R.styleable.AutoCompleteEmailView_drawable_ok); error = typedArray.getDrawable(R.styleable.AutoCompleteEmailView_drawable_error); hint = typedArray.getString(R.styleable.AutoCompleteEmailView_hint_text); txt_color = typedArray.getColor(R.styleable.AutoCompleteEmailView_input_color, default_txt_color); bg_drawable = typedArray.getDrawable(R.styleable.AutoCompleteEmailView_view_bg_drawable); hint_color = typedArray.getColor(R.styleable.AutoCompleteEmailView_hint_color, default_hint_color); txt_size = typedArray.getDimensionPixelSize(R.styleable.AutoCompleteEmailView_txt_size, default_txt_size); mPaddingStart = typedArray.getDimension(R.styleable.AutoCompleteEmailView_content_padding_start, default_content_padding_start); mPaddingEnd = typedArray.getDimension(R.styleable.AutoCompleteEmailView_content_padding_end, default_content_padding_end); typedArray.recycle();//记得把typeArray对象recycle掉 } public AutoCompleteEmailView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public AutoCompleteEmailView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } /** *加载完typeArray的各个参数后会进入这个回调,我们在这里初始化布局 */ @Override protected void onFinishInflate() { super.onFinishInflate(); bindView(); setListener(); ll_container.setBackground(bg_drawable == null ? default_bg_color : bg_drawable); et_email.setTextColor(txt_color); et_email.setTextSize(TypedValue.COMPLEX_UNIT_PX, txt_size);//注意这里,需要将获取到的值进行格式限定,否则效果会有点尴尬…… et_email.setHintTextColor(hint_color); et_email.setFocusable(true); et_email.setFocusableInTouchMode(true); et_email.requestFocus(); ll_container.setPadding((int) mPaddingStart, 0, (int) mPaddingEnd, 0); } private void bindView() { iv_check = (ImageView) findViewById(R.id.iv_check); et_email = (EditText) findViewById(R.id.et_email_complete); ll_container = (LinearLayout) findViewById(R.id.ll_container); } private void setListener() { et_email.addTextChangedListener(this); et_email.setOnFocusChangeListener(this);//我们需要在该空间失去焦点的时候,将我们“画”上去的补全内容,转变成实实在在的文字装进edittext,所以监听一下他的焦点变化 } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { if (s.length() == 0 || s == null) { iv_check.setVisibility(INVISIBLE);//开始的时候不显示提示图片,用户开始输入才开始显示校验图片 } } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { String text = s.toString(); mFlag = true; if (s.length() > 0) { iv_check.setVisibility(VISIBLE); } // 遍历常用邮箱 for (int i = 0; i < emails.length; i++) { if (text.length() > 1 && canCompleteEmail(text, emails[i])) { mAddedText = getAddText(emails[i], text); drawAddedText(mAddedText); if (ok == null || error == null) iv_check.setImageDrawable(default_ok_res); else iv_check.setImageDrawable(ok); if (callback != null) callback.typeOk(); mFlag = false; break; } } // 如果没有匹配,就画一个空 if (mFlag) { drawAddedText(""); mAddedText = ""; if (!isEmail(et_email.getText().toString())) { iv_check.setImageDrawable(error == null ? default_err_res : error); if (callback != null) callback.typeError(); } else { iv_check.setImageDrawable(ok == null ? default_ok_res : ok); if (callback != null) callback.typeOk(); } } } private String getAddText(String email, String input) { String s = ""; int pos_start = input.indexOf("@"); int pos_end = input.length() - 1; if (pos_end >= pos_start) s = email.substring(pos_end - pos_start + 1); return s; } /** * 画出后缀字符串 * * @param addedText */ @SuppressWarnings("deprecation") private void drawAddedText(String addedText) { // 如果字符串为空,画空 if (addedText.equals("")) { et_email.setCompoundDrawables(null, null, null, null); return; } if (mBitmap == null) { mHeight = et_email.getHeight(); mWidth = et_email.getWidth(); // 初始化画笔 mPaint = new Paint(); mPaint.setColor(Color.GRAY); mPaint.setAntiAlias(true);// 去除锯齿 mPaint.setFilterBitmap(true);// 对位图进行滤波处理 mPaint.setTextSize(et_email.getTextSize()); } Rect rect = new Rect(); int baseLineLocation = et_email.getLineBounds(0, rect); mBaseLine = baseLineLocation - rect.top; // 添加的字符窜的长度 int addedTextWidth = (int) (mPaint.measureText(addedText) + 1); // 创建bitmap mBitmap = Bitmap.createBitmap(addedTextWidth, mHeight, Bitmap.Config.ARGB_8888); mCanvas = new Canvas(mBitmap); // 绘制后缀字符串 mCanvas.drawText(addedText, 0, mBaseLine, mPaint); // bitmap转化为Drawable mDrawable = new BitmapDrawable(mBitmap); String text = et_email.getText().toString(); // 计算后缀字符串在输入框中的位置 int addedTextLeft = (int) (mPaint.measureText(text) - mWidth + addedTextWidth); int addedTextRight = addedTextLeft + addedTextWidth; int addedTextTop = 0; int addedTextBottom = addedTextTop + mHeight; // 设置后缀字符串位置 mDrawable.setBounds(addedTextLeft, addedTextTop, addedTextRight, addedTextBottom); // 显示后缀字符串 et_email.setCompoundDrawables(null, null, mDrawable, null); } @Override public void onFocusChange(View v, boolean hasFocus) { if (v.getId() == R.id.et_email_complete) { if (!hasFocus) { if (!TextUtils.isEmpty(hint)) et_email.setHint(hint); if (!TextUtils.isEmpty(mAddedText)) et_email.append(mAddedText); } else { et_email.setHint(""); InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS); } } } //************************************Utils方法************************************** public static boolean canCompleteEmail(String string, String email_name) { if (string.contains("@")) {//从用户输入@开始判断 int pos = string.indexOf("@"); String content = string.substring(pos);//截取@后面的字符串 if (email_name.startsWith(content)) return true; } return false; } /** *使用正则表达式判断格式是否正确 */ public static boolean isEmail(String email) { if (TextUtils.isEmpty(email)) return false; Pattern p = Pattern.compile(REGEX_EMAIL); Matcher m = p.matcher(email); return m.matches(); } // ********************************************************************************** EmailCheckCallback callback;//用来刷新button状态的callback public void setEmailCheckCallback(EmailCheckCallback emailCheckCallback) { this.callback = emailCheckCallback; } @Override public boolean onTouch(View v, MotionEvent event) { return false; }//定义接口,外部通过setEmailCheckCallback设置该接口,这个接口可用来设置外部的button状态(enable/disable) public interface EmailCheckCallback { void typeOk(); void typeError(); }}
这个自定义控件本身的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"> <LinearLayout android:id="@+id/ll_container" android:layout_width="match_parent" android:layout_height="@dimen/step_btn_height" android:layout_marginTop="5dp" android:background="@drawable/edit_3dffffff" android:gravity="center_vertical" android:orientation="horizontal" android:paddingLeft="10dp" android:paddingRight="10dp"> <EditText android:id="@+id/et_email_complete" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:background="@color/transparent" android:hint="@string/hint_email" android:inputType="textEmailAddress" android:textColorHint="@color/hint_txt_color"> <requestFocus /> </EditText> <ImageView android:id="@+id/iv_check" android:layout_width="25dp" android:layout_height="25dp" /> </LinearLayout></LinearLayout>
我们可以看到他其实就是一个线性布局,放了一个edittext,一个imageview,如果我们不需要实时校验,可以把这里的imageview可见性设置为gone就行。
我们上文中的各个属性是我们自定义的,需要在res/values/attrs中做一个声明:
<declare-styleable name="AutoCompleteEmailView"> <attr name="drawable_ok" format="reference" /> <attr name="drawable_error" format="reference" /> <attr name="hint_text" format="string" /> <attr name="hint_color" format="color" /> <attr name="input_color" format="color" /> <attr name="view_bg_drawable" format="color" /> <attr name="txt_size" format="dimension"/> <attr name="content_padding_start" format="dimension"/> <attr name="content_padding_end" format="dimension"/> </declare-styleable>
有了这个声明以后,我们可以在xml中自定义他的样式:
<com.lg.view.AutoCompleteEmailView android:id="@+id/autocomplete_emai_view" fontPath="fonts/Roboto-Light.ttf" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="37.5dp" android:layout_marginRight="37.5dp" android:layout_marginTop="20dp" android:focusable="true" android:focusableInTouchMode="true" app:content_padding_end="12dp" app:content_padding_start="22.5dp" app:drawable_error="@drawable/icon_edit_v_err" app:drawable_ok="@drawable/icon_edit_v_ok" app:hint_text="@string/form_login_bind_email_hint" app:input_color="@color/black" app:view_bg_drawable="@drawable/edit_ffffff"> </com.lg.view.AutoCompleteEmailView>
这里我们定义了他的背景、字体大小、右侧提示图片的资源等等,当然,需要在xml的开头加一行:
xmlns:app="http://schemas.android.com/apk/res-auto"
否则会找不到对应的属性声明。
这样我们就基本可以在我们的布局中呈现一个自动补齐的邮箱输入框,当然,在对应的activity/fragment中,你可以实现一个EmailCheckCallback接口,在这个接口的方法中去刷新你的button的状态,再调用autoCompleteEditText的setEmailCheckCallback方法把这个接口传进去即可~
autoCompleteEmailView = (AutoCompleteEmailView) ViewIndect(mView, R.id.autocomplete_emai_view); autoCompleteEmailView.setEmailCheckCallback(this);//这里我们让这个fragment自身实现这个接口
那么这个fragment就需要实现两个方法:
@Override public void typeOk() { btn.setEnabled(true);//校验成功设置button为enable } @Override public void typeError() { btn.setEnabled(false);//不成功设置为disable }
然后再在xml文件中将button的background设置成区分enable=true/false的selector即可(预览图中没有设置)。
想要获取editText中的输入内容,也只需要调用getEt_email方法得到输入框,再gettext就行了:
String tmpEmail = autoCompleteEmailView.getEt_email().getText().toString();
总结
这个空间看起来挺复杂,其实仔细分析,对每个需求进行考虑,其实还是不难的,做的时候遇到了一些坑:
- 对setCompoundBitmap的掌握不够,导致绘制出来的补全内容不在想要的位置上,后来在GitHub上看了一圈,站在巨人的肩膀上,才看见了茫茫沙漠的一片绿洲。
- 对画笔和画布的学习不够,每次用都是现学现卖,需要好好系统的理一理这个东西的使用。
- 做实时校验的时候,没有理解beforeTextChange/afterTextChange/onTextChange三者的区别和回调时机,以及想当然的把afterTextChange中传入的editable对象当做用户输入的内容,导致结果总是奇奇怪怪,卡了好一阵子。
接下来要花时间好好补补坑~第一次写博客,写的不好欢迎各位大大留言指正!
本文参考:https://github.com/heheLT/AutoCompleteEditText,感谢巨人的肩膀!
- 【Android】自定义控件实现自动补齐邮箱后缀的输入框
- android 自定义控件之AutoCompleteTextView邮箱后缀自动补全
- Android-输入邮件自动补齐后缀
- Android学习系列之控件 AutoCompleteTextView邮箱后缀自动补全
- Android学习系列之控件 AutoCompleteTextView邮箱后缀自动补全
- 带有删除按钮,自动补全邮箱后缀的自定义EditText
- Android实现登录邮箱的自动补全功能
- Android实现登录邮箱的自动补全功能
- Android 邮箱自动补全-MultiAutoCompleteTextView实现
- Android 自定义View--实现带有按钮点击效果的自动补全输入框(搜索框)
- 2种方法,当文本框输入@自动补全邮箱后缀(特别是命名空间的引用,共三种方法)
- js实现的邮箱自动补全
- ajax邮箱后缀自动补全
- JS输入用户名自动显示邮箱后缀列表的方法
- 有你要用到的:邮箱输入框+自动补全
- 文本框输入邮箱自动联想补全
- 【Android自定义控件】密码输入框+数字键盘的实现
- 【Android自定义控件】选择输入框的实现
- linux的源码安装步骤(以安装nginx为例)
- em和rem区别
- 【ssm个人博客项目实战07】博客的后台实现
- 自定义filter中配置不被过滤的资源
- IplImage数据结构
- 【Android】自定义控件实现自动补齐邮箱后缀的输入框
- Oracle协议适配器错误解决办法
- android 插件化机制之AMS&PMS
- mark
- Java API中的常用类
- 使用Nexus搭建Maven私服
- 挑战 P243 poj3686 (WA) 最小费用流,不知道为什么。
- JSP request对象、response对象、contentType属性,HTTP状态码
- 微型秒杀模型的几点思考