【Android】自定义控件实现自动补齐邮箱后缀的输入框

来源:互联网 发布:成交量彩带均线源码 编辑:程序博客网 时间:2024/05/01 07:16

自定义控件实现自动补齐邮箱后缀输入框

前言

前段时间项目中需要实现一个能够自动补齐,并判断邮箱格式是否正确的控件,在GitHub上学习了几位大神的思路,自己结合需求,实现了一下,写的不好的地方,欢迎大家留言指教。先看一下效果:
输入邮箱,自动补齐
修改邮箱,自动补齐
我们可以看到,本文实现的邮箱输入框主要做了以下几件事:

  1. 自定义输入框的样式,包括背景色、hint颜色、字体颜色、输入框形状等。
  2. 实时校验输入的邮箱格式是否正确。
  3. 校验结果正确,根据校验结果在输入框右侧显示不同的图片以提示用户。
  4. 如果用户输入的内容可以自动补齐邮箱后缀,帮助用户补齐。当触摸控件外部,把补齐内容写入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();

总结

这个空间看起来挺复杂,其实仔细分析,对每个需求进行考虑,其实还是不难的,做的时候遇到了一些坑:

  1. 对setCompoundBitmap的掌握不够,导致绘制出来的补全内容不在想要的位置上,后来在GitHub上看了一圈,站在巨人的肩膀上,才看见了茫茫沙漠的一片绿洲。
  2. 对画笔和画布的学习不够,每次用都是现学现卖,需要好好系统的理一理这个东西的使用。
  3. 做实时校验的时候,没有理解beforeTextChange/afterTextChange/onTextChange三者的区别和回调时机,以及想当然的把afterTextChange中传入的editable对象当做用户输入的内容,导致结果总是奇奇怪怪,卡了好一阵子。

接下来要花时间好好补补坑~第一次写博客,写的不好欢迎各位大大留言指正!

本文参考:https://github.com/heheLT/AutoCompleteEditText,感谢巨人的肩膀!

1 0