Android 基於AppCompatAutoCompleteTextView實現的自動補全郵箱地址控件

標籤: Android Email AutoCompleteTextViewjava

本篇文章主要講述郵箱地址自動補全的功能實現,對於AutoCompleteTextView的基本功能請自行百度或者閱讀源碼。 參考連接:http://my.oschina.net/fengheju/blog/176656?fromerr=vA0tbaoqandroid

文章原址請點擊這裏 歡迎關注git

近日我司工做上有需求:在用戶登陸註冊時 自動補全用戶輸入的郵箱帳號(本產品服務於外國友人,因此僅支持郵箱註冊),以便於用戶操做,給用戶一個良好體驗。拿到手後感受並無什麼難度,便火急火燎的去實現了,哪想到一步一坑,克服了一些問題後纔有了此篇文章中的勞動成果。由於在實現過程當中搜索到的資源利用率不高,便於此記錄一下,方便後人乘涼, 謹以此篇文章的記錄下本身挖坑填坑之旅,且之後在實現需求時要時時告誡本身:源碼拜讀一遍,可省三日之功github

實現思路

1.利用AutoCompleteTextView + 自定義ArrayAdapter實現pop UI;
2.重寫AutoCompleteTextView 的performFiltering方法,修改此方法傳入的原始文本:當用戶未輸入 @ 時,原始文本置換爲 @ ;在輸入 @ 後,原始文本置換爲 原始文本的 @及其以後的部分 ;當用戶輸入錯誤字符時關閉pop。app

@Override
        protected void performFiltering(CharSequence text, int keyCode) {
            String t = text.toString();
            int index = t.indexOf("@");
            if (index == -1) {
                if (t.matches("^[a-zA-Z0-9_]+$")) {
                    super.performFiltering("@", keyCode);
                } else
                    this.dismissDropDown();//關閉pop
            } else {
                super.performFiltering(t.substring(index), keyCode);
            }
        }
複製代碼

3.因爲補全郵箱時我我的想實現的的功能是補全全部含有過濾文本的匹配內容,而不是全部以過濾文本開頭的匹配內容,因此只能重寫ArrayAdapter 的filter來實現此功能ide

private class ArrayFilter extends Filter {
            @Override
            protected FilterResults performFiltering(CharSequence prefix) {
                final FilterResults results = new FilterResults();
                if (prefix == null || prefix.length() == 0) {
                    final List<String> list;
                    synchronized (this) {
                        list = Arrays.asList(address.clone());
                    }
                    results.values = list;
                    results.count = list.size();
                } else {
                    String prefixString = prefix.toString().toLowerCase();
                    prefixString = prefixString.substring(1);
                    final ArrayList<String> values;
                    synchronized (this) {
                        values = new ArrayList<>(ConvertUtils.toList(address));
                    }
                    final int count = values.size();
                    final ArrayList<String> newValues = new ArrayList<>();

                    for (int i = 0; i < count; i++) {
                        final String value = values.get(i);
                        final String valueText = value.toString().toLowerCase();
                        // First match against the whole, non-splitted value
                        if (valueText.contains(prefixString)) {
                            newValues.add(value);
                        } else {
                            final String[] words = valueText.split(" ");
                            for (String word : words) {
                                if (word.contains(prefixString)) {
                                    newValues.add(value);
                                    break;
                                }
                            }
                        }
                    }

                    results.values = newValues;
                    results.count = newValues.size();
                }

                return results;
            }

            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {
                //noinspection unchecked
                clear();
                if (results.count > 0) {
                    for (String str : (List<String>) results.values){
                        add(str);
                    }
                    notifyDataSetChanged();
                } else {
                    notifyDataSetInvalidated();
                }
            }
        }
複製代碼

4.AutoCompleteTextView 在點擊pop item後會將咱們傳入的原始值賦值給TextView,但在實現自動補全郵箱域名時,這個結果並非咱們想看到的。因此咱們須要去重寫AutoCompleteTextView的replaceText()方法,將咱們自動補全後的郵箱地址賦值TextView.oop

@Override
    protected void replaceText(CharSequence text) {
        String t = this.getText().toString();
        //當咱們在下拉框中選擇一項時,android會默認使用AutoCompleteTextView中Adapter裏的文原本填充文本域
        //由於這裏Adapter中只是存了經常使用email的後綴
        //所以要從新replace邏輯,將用戶輸入的部分與後綴合併
        int index = t.indexOf("@");
        if (index != -1)
            t = t.substring(0, index);
        super.replaceText(t + text);

    }
複製代碼

5.在文本框獲取焦點時喚出pop。post

@Override
    protected void onFocusChanged(boolean hasFocus, int direction, Rect previouslyFocusedRect) {
        super.onFocusChanged(hasFocus, direction, previouslyFocusedRect);
        if (hasFocus) {
            String text = EmailAutoCompleteTextView.this.getText().toString();
            if (!"".equals(text))
                performFiltering(text, 0);
        }
    }

複製代碼

遇到的問題:

在重寫Filter類時須要在publishResults(CharSequence constraint, FilterResults results)方法中將過濾後的結果results賦值到Adapter的origin Value中,修改完此處代碼時調試運行,一直遇到一個異常:this

E/UncaughtException: java.lang.UnsupportedOperationException  

at java.util.AbstractList.remove(AbstractList.java:161)                                                 
at java.util.AbstractList$Itr.remove(AbstractList.java:374)
at java.util.AbstractList.removeRange(AbstractList.java:571)                                               at java.util.AbstractList.clear(AbstractList.java:234)
at android.widget.ArrayAdapter.clear(ArrayAdapter.java:320)
at com.xxxxx.EmailAutoCompleteTextView$EmailAdapter$ArrayFilter.publishResults(EmailAutoCompleteTextView.java:200)
at android.widget.Filter$ResultsHandler.handleMessage(Filter.java:282)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6809)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
複製代碼

查詢一些帖子才發現形成緣由是:在重寫filter方法中對adapter使用了 add() remove() 等操做,傳入的List 爲Arrays.asList(address),可是在使用Arrays.asLisvt()後調用add,remove這些method時會出現java.lang.UnsupportedOperationException異常。 這是因爲: Arrays.asLisvt() 返回java.util.Arrays.ArrayList, 而不是ArrayList。Arrays.ArrayList和ArrayList都是繼承AbstractList,remove,add等 method在AbstractList中是默認throw UnsupportedOperationException並且不做任何操做。ArrayList override這些method來對list進行操做,可是Arrays$ArrayList沒有override remove(int),add(int)等,因此throw UnsupportedOperationException。idea

解決方法是使用Iterator,或者轉換爲ArrayList

List list = Arrays.asList(a[]);
List arrayList = new ArrayList(list);

or

public static <T> List<T> toList(T[] array) {
    List<T> tmpList = new ArrayList<T>();
    if (array == null) return tmpList;
    for (T item : array) {
        tmpList.add(item);
    }
    return tmpList;
}
複製代碼

完整源碼以下:

package com.xxxxxxx.view;

import android.content.Context;
import android.graphics.Rect;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.AppCompatAutoCompleteTextView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.TextView;

import com.xxxx.xxx.R;
import com.xxxxxx.library.utils.ConvertUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/** * 根據用戶輸入的內容自動補全郵箱地址 * Created on 17-12-26 下午4:20 by LiuXi0314 */

public class EmailAutoCompleteTextView extends AppCompatAutoCompleteTextView {

    private String[] address = new String[]{
            "@gmail.com",
            "@yahoo.com",
            "@hotmail.com",
            "@outlook.com",
            "@aol.com",
            "@excite.com",
            "@icloud.com",
            "@ideakings.com",
            "@juno.com",
            "@langevinri.com",
            "@matpost.com",
            "@netzero.com",
            "@tradesult.com",
            "@tds.net",
            "@verizon.net",
            "@windstream.net",
            "@att.net",
            "@bellsouth.net",
            "@charter.net",
            "@comcast.net",
            "@gci.net",};

    public EmailAutoCompleteTextView(Context context) {
        super(context);
        initViews();
    }

    public EmailAutoCompleteTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initViews();
    }

    public EmailAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initViews();
    }

    private void initViews() {
        setAdapter(new EmailAdapter(ConvertUtils.toList(address)));
        setThreshold(1);	//使得在輸入1個字符以後便彈出pop
    }


    @Override
    protected void onFocusChanged(boolean hasFocus, int direction, Rect previouslyFocusedRect) {
        super.onFocusChanged(hasFocus, direction, previouslyFocusedRect);
        if (hasFocus) {
            String text = EmailAutoCompleteTextView.this.getText().toString();
            if (!"".equals(text))
                performFiltering(text, 0);
        }
    }

    @Override
    protected void replaceText(CharSequence text) {
        String t = this.getText().toString();
        //當咱們在下拉框中選擇一項時,android會默認使用AutoCompleteTextView中Adapter裏的文原本填充文本域
        //由於這裏Adapter中只是存了經常使用email的後綴
        //所以要從新replace邏輯,將用戶輸入的部分與後綴合併
        int index = t.indexOf("@");
        if (index != -1)
            t = t.substring(0, index);
        super.replaceText(t + text);

    }

    @Override
    protected void performFiltering(CharSequence text, int keyCode) {
        //該方法會在用戶輸入文本以後調用,將已輸入的文本與adapter中的數據對比,若它匹配
        //adapter中數據的前半部分,那麼adapter中的這條數據將會在下拉框中出現
        String t = text.toString();
        //由於用戶輸入郵箱時,都是以字母,數字開始,而咱們的adapter中只會提供以相似於"@163.com"
        //的郵箱後綴,所以在調用super.performFiltering時,傳入的必定是以"@"開頭的字符串
        int index = t.indexOf("@");
        if (index == -1) {
            if (t.matches("^[a-zA-Z0-9_]+$")) {
                super.performFiltering("@", keyCode);
            } else
                this.dismissDropDown();//當用戶中途輸入非法字符時,關閉下拉提示框
        } else {
            super.performFiltering(t.substring(index), keyCode);
        }
    }


    class EmailAdapter extends ArrayAdapter<String>{

        public EmailAdapter(@NonNull List<String> objects) {
            super(EmailAutoCompleteTextView.this.getContext(),R.layout.item_email_auto_complete , R.id.textView, objects);
        }

        @NonNull
        @Override
        public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
            View v = convertView;
            if (v == null)
                v = LayoutInflater.from(getContext()).inflate(
                        R.layout.item_email_auto_complete, null);
            String originStr = EmailAutoCompleteTextView.this.getText().toString();
            int index = originStr.indexOf("@");
            if (index != -1)
                originStr = originStr.substring(0, index);
            final TextView textView = (TextView) v.findViewById(R.id.textView);
            textView.setText(originStr + getItem(position));
            return textView;
        }

        @NonNull
        @Override
        public Filter getFilter() {
            return new ArrayFilter();
        }

        /** * 修改ArrayAdapter源碼 中的ArrayFilter,以改變其過濾規則 * 默認過濾規則爲 startWith == true * 修改後的規則: contains == true */
        private class ArrayFilter extends Filter {
            @Override
            protected FilterResults performFiltering(CharSequence prefix) {
                final FilterResults results = new FilterResults();
                if (prefix == null || prefix.length() == 0) {
                    final List<String> list;
                    synchronized (this) {
                        list = Arrays.asList(address.clone());
                    }
                    results.values = list;
                    results.count = list.size();
                } else {
                    String prefixString = prefix.toString().toLowerCase();
                    prefixString = prefixString.substring(1);
                    final ArrayList<String> values;
                    synchronized (this) {
                        values = new ArrayList<>(ConvertUtils.toList(address));
                    }
                    final int count = values.size();
                    final ArrayList<String> newValues = new ArrayList<>();

                    for (int i = 0; i < count; i++) {
                        final String value = values.get(i);
                        final String valueText = value.toString().toLowerCase();
                        // First match against the whole, non-splitted value
                        if (valueText.contains(prefixString)) {
                            newValues.add(value);
                        } else {
                            final String[] words = valueText.split(" ");
                            for (String word : words) {
                                if (word.contains(prefixString)) {
                                    newValues.add(value);
                                    break;
                                }
                            }
                        }
                    }

                    results.values = newValues;
                    results.count = newValues.size();
                }

                return results;
            }

            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {
                //noinspection unchecked
                clear();
                if (results.count > 0) {
                    for (String str : (List<String>) results.values){
                        add(str);
                    }
                    notifyDataSetChanged();
                } else {
                    notifyDataSetInvalidated();
                }
            }
        }

    }
}

複製代碼
相關文章
相關標籤/搜索