自动补全文本框在app中十分常见,我们平时接触到的搜索框都是autoCompleteTextView,当我们输入关键字的时候,就会出现一个下拉列表,方便我们快速查找需要的内容或提示我们进一步输入。
比如上图这样的就是一个自动补全文本框,但是这样的文本框有很多问题:1、不够美观。2、只能匹配开头相同的字符串。这些都是因为我用的是ArrayAdapter的原因,这样的自动补全文本框肯定不能满足应用的需求。于是我又做了一个自定义的适配器来解决这两个问题。
比如上图这样的,类似ListView可以做成图文混排的样子。
1、需要JavaBean存放数据
1 public class MyBean { 2 private int imageID; 3 private String userName; 4 5 public MyBean(int imageID, String userName) { 6 this.imageID = imageID; 7 this.userName = userName; 8 } 9 10 public int getImageID() { 11 return imageID; 12 } 13 14 public void setImageID(int imageID) { 15 this.imageID = imageID; 16 } 17 18 public String getUserName() { 19 return userName; 20 } 21 22 public void setUserName(String userName) { 23 this.userName = userName; 24 } 25 }
2、AutoCompleteTextView所在的布局文件(app/res/layout/activity_main.xml)
AutoCompleteTextView的相关属性:(更多属性可参考:菜鸟教程的AutoCompleteTextView的基本使用)
- android:completionHint:设置下拉菜单中的提示标题
- android:completionHintView:设置下拉菜单中提示标题的视图
- android:completionThreshold:指定用户至少输入多少个字符才会显示提示
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:tools="http://schemas.android.com/tools" 5 android:layout_width="match_parent" 6 android:layout_height="match_parent" 7 android:orientation="vertical" 8 tools:context=".MainActivity"> 9 10 <LinearLayout 11 android:layout_width="match_parent" 12 android:layout_height="wrap_content" 13 android:layout_marginTop="80dp" 14 android:orientation="horizontal"> 15 16 <TextView 17 android:id="@+id/t1" 18 android:layout_width="0dp" 19 android:layout_height="wrap_content" 20 android:layout_weight="1" 21 android:text="ArrayAdapter:" /> 22 23 <AutoCompleteTextView 24 android:id="@+id/actv1" 25 android:layout_width="0dp" 26 android:layout_height="wrap_content" 27 android:layout_weight="2" /> 28 </LinearLayout> 29 30 <LinearLayout 31 android:layout_width="match_parent" 32 android:layout_height="wrap_content" 33 android:layout_marginTop="80dp" 34 android:orientation="horizontal"> 35 36 <TextView 37 android:id="@+id/t2" 38 android:layout_width="0dp" 39 android:layout_height="wrap_content" 40 android:layout_weight="1" 41 android:text="自定义适配器:" /> 42 43 <AutoCompleteTextView 44 android:id="@+id/actv2" 45 android:background="@drawable/actv_bg" 46 android:completionHint="选择联系人" 47 android:completionHintView="@layout/actv_title" 48 android:layout_width="0dp" 49 android:layout_height="wrap_content" 50 android:layout_weight="2" 51 android:completionThreshold="1" /> 52 53 </LinearLayout> 54 55 </LinearLayout>
3、自动补全文本框的背景(app/res/drawable/actv_bg.xml)
1 <?xml version="1.0" encoding="utf-8"?> 2 <shape xmlns:android="http://schemas.android.com/apk/res/android" 3 android:shape="rectangle"> 4 <solid android:color="#ffffff"/><!--填充色--> 5 <corners android:radius="30dp"/><!--圆角弧度--> 6 <padding 7 android:bottom="10dp" 8 android:left="10dp" 9 android:right="10dp" 10 android:top="10dp"/><!--内容与边框的间距--> 11 <stroke 12 android:color="#DCB879" 13 android:width="1dp"/><!--描边的粗细和颜色--> 14 <gradient 15 android:startColor="#ffffff" 16 android:endColor="#ffffff" 17 android:type="linear" 18 android:angle="360"/><!--渐变[渐变>填充]--> 19 </shape>
4、设计每一项的样式布局(app/res/layout/actv_item.xml)
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:app="http://schemas.android.com/apk/res-auto" 4 android:layout_width="match_parent" 5 android:layout_height="wrap_content"> 6 <ImageView 7 android:id="@+id/imageView" 8 android:layout_width="50dp" 9 android:layout_height="50dp" 10 android:layout_gravity="right" 11 app:srcCompat="@mipmap/ic_launcher" /> 12 <TextView 13 android:id="@+id/tv_userName" 14 android:layout_width="0dp" 15 android:layout_height="match_parent" 16 android:layout_weight="1" 17 android:paddingLeft="30dp" 18 android:gravity="center_vertical" 19 android:text="TextView" /> 20 </LinearLayout>
5、设置下拉菜单中提示标题的视图的布局文件(app/res/layout/actv_title.xml)
- TextView的id必须为 android:id="@android:id/text1"
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="wrap_content"> 5 6 <TextView 7 android:id="@android:id/text1" 8 android:layout_width="match_parent" 9 android:layout_height="wrap_content" 10 android:padding="2dp" 11 android:textColor="#cfcfcf" 12 android:textSize="10sp" 13 android:gravity="end" 14 /> 15 </LinearLayout>
6、定义适配器(MyAdapter.java)
- 仿照ListView的做法,要修改下拉菜单的样式,就重写适配器的getView方法
- 特别的是还需要适配器实现接口Filterable,并重写getFilter(),可以参考ArrayAdapter的源码实现,因为源码里用了a.startsWith(b),只能筛选开头相同的字符串,所以我改用a.contains(b),只要包含用户输入关键字就加入筛选后的列表。
- 附上:ArrayAdapter的部分源码
1 @Override 2 public @NonNull Filter getFilter() { 3 if (mFilter == null) { 4 mFilter = new ArrayFilter(); 5 } 6 return mFilter; 7 } 8 9 /** 10 * <p>An array filter constrains the content of the array adapter with 11 * a prefix. Each item that does not start with the supplied prefix 12 * is removed from the list.</p> 13 */ 14 private class ArrayFilter extends Filter { 15 @Override 16 protected FilterResults performFiltering(CharSequence prefix) { 17 final FilterResults results = new FilterResults(); 18 19 if (mOriginalValues == null) { 20 synchronized (mLock) { 21 mOriginalValues = new ArrayList<>(mObjects); 22 } 23 } 24 25 if (prefix == null || prefix.length() == 0) { 26 final ArrayList<T> list; 27 synchronized (mLock) { 28 list = new ArrayList<>(mOriginalValues); 29 } 30 results.values = list; 31 results.count = list.size(); 32 } else { 33 final String prefixString = prefix.toString().toLowerCase(); 34 35 final ArrayList<T> values; 36 synchronized (mLock) { 37 values = new ArrayList<>(mOriginalValues); 38 } 39 40 final int count = values.size(); 41 final ArrayList<T> newValues = new ArrayList<>(); 42 43 for (int i = 0; i < count; i++) { 44 final T value = values.get(i); 45 final String valueText = value.toString().toLowerCase(); 46 47 // First match against the whole, non-splitted value 48 if (valueText.startsWith(prefixString)) { 49 newValues.add(value); 50 } else { 51 final String[] words = valueText.split(" "); 52 for (String word : words) { 53 if (word.startsWith(prefixString)) { 54 newValues.add(value); 55 break; 56 } 57 } 58 } 59 } 60 61 results.values = newValues; 62 results.count = newValues.size(); 63 } 64 65 return results; 66 }
1 public class MyAdapter extends BaseAdapter implements Filterable { 2 private Context mcontext; 3 private List<MyBean> mFilteredList;//过滤后的列表 4 private final List<MyBean> all_List;//需要过滤的列表 5 private ArrayFilter mFilter; 6 7 public MyAdapter(Context mcontext, List<MyBean> all_List) { 8 this.mcontext = mcontext; 9 this.all_List =all_List; 10 mFilteredList = all_List; 11 12 } 13 14 public List<MyBean> getmFilteredList() { 15 return mFilteredList; 16 } 17 18 @Override 19 public int getCount() { 20 return mFilteredList.size(); 21 } 22 23 @Override 24 public Object getItem(int position) { 25 return mFilteredList.get(position); 26 } 27 28 @Override 29 public long getItemId(int position) { 30 return position; 31 } 32 33 public class ViewHolder{ 34 ImageView imageID; 35 TextView tv_userName; 36 } 37 38 @Override 39 public View getView(int position, View convertView, ViewGroup parent) { 40 ViewHolder holder; 41 if(convertView==null){ 42 convertView=View.inflate(mcontext,R.layout.actv_item,null); 43 holder=new ViewHolder(); 44 holder.tv_userName=convertView.findViewById(R.id.tv_userName); 45 holder.imageID=convertView.findViewById(R.id.imageView); 46 convertView.setTag(holder); 47 }else{ 48 holder=(ViewHolder)convertView.getTag(); 49 } 50 holder.tv_userName.setText(mFilteredList.get(position).getUserName()); 51 holder.imageID.setImageResource(mFilteredList.get(position).getImageID()); 52 return convertView; 53 } 54 55 @Override 56 public Filter getFilter() { 57 if (mFilter == null) { 58 mFilter = new ArrayFilter(); 59 } 60 return mFilter; 61 } 62 63 private class ArrayFilter extends Filter { 64 @Override 65 protected FilterResults performFiltering(CharSequence constraint) {//constraint是用户的输入 66 //进行过滤的操作 67 FilterResults results = new FilterResults(); 68 69 if (constraint == null || constraint.length() == 0) { 70 ArrayList<MyBean> list = new ArrayList<>(all_List); 71 results.values = list; 72 results.count = list.size(); 73 } else { 74 String prefixString = constraint.toString().toLowerCase();//大写换成小写 75 ArrayList<MyBean> values =new ArrayList<>(all_List);//未过滤前的List 76 int count = values.size(); 77 ArrayList<MyBean> newValues = new ArrayList<>();//被过滤后的匹配的List 78 /*以下为过滤的条件,可按照个人的需求修改*/ 79 for (int i = 0; i < count; i++) { 80 MyBean value = values.get(i); 81 String valueText = value.getUserName();//ValueText是每一项筛选的依据 82 83 if (valueText != null && valueText.startsWith(prefixString)) { 84 newValues.add(value); 85 } else { 86 if(valueText.contains(prefixString)) { 87 newValues.add(value); 88 } 89 } 90 } 91 results.values = newValues; 92 results.count = newValues.size(); 93 } 94 return results; 95 } 96 97 @Override 98 protected void publishResults(CharSequence constraint, FilterResults results) { 99 //发表过滤的结果 100 mFilteredList = (List<MyBean>) results.values; 101 if (results.count > 0) { 102 notifyDataSetChanged(); 103 } else { 104 notifyDataSetInvalidated(); 105 } 106 } 107 } 108 }
7、在app/res/values/strings.xml存放用到的字符串和图片ID
1 <resources> 2 <string name="app_name">test</string> 3 <string-array name="NameList"> 4 <item>张三</item> 5 <item>李四</item> 6 <item>王五</item> 7 <item>张李</item> 8 <item>李六</item> 9 <item>陈八</item> 10 <item>杨七</item> 11 <item>常九</item> 12 </string-array> 13 <integer-array name="imageID"> 14 <item>@drawable/images_01</item> 15 <item>@drawable/images_02</item> 16 <item>@drawable/images_03</item> 17 <item>@drawable/images_04</item> 18 <item>@drawable/images_05</item> 19 <item>@drawable/images_06</item> 20 <item>@drawable/images_07</item> 21 <item>@drawable/images_08</item> 22 </integer-array> 23 </resources>
8、在MainActivity里为AutoCompleteTextView添加setOnItemClickListener的点击事件。
1 public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener { 2 MyAdapter adapter2; 3 List<MyBean> userList; 4 AutoCompleteTextView auto_tv2; 5 @Override 6 protected void onCreate(Bundle savedInstanceState) { 7 super.onCreate(savedInstanceState); 8 setContentView(R.layout.activity_main); 9 //ArrayAdapter的自动补全文本框 10 String[] countries={"辽宁-沈阳","辽宁-大连","辽宁-鞍山","辽宁-营口","辽宁-盘锦", 11 "辽宁-锦州","辽宁-抚顺","辽宁-本溪","辽宁-丹东","辽宁-朝阳","辽宁-辽阳","辽宁葫芦岛","辽宁-铁岭","辽宁-阜新"}; 12 AutoCompleteTextView auto_tv1=findViewById(R.id.actv1); 13 ArrayAdapter adapter1=new ArrayAdapter(getApplicationContext(),android.R.layout.simple_list_item_1,countries); 14 auto_tv1.setAdapter(adapter1); 15 //自定义适配器的自动补全文本框 16 auto_tv2 =findViewById(R.id.actv2); 17 userList=init();//初始化数据 18 adapter2 =new MyAdapter(getApplicationContext(),userList); 19 auto_tv2.setAdapter(adapter2); 20 auto_tv2.setOnItemClickListener(this); 21 } 22 23 protected List<MyBean> init(){//初始化数据 24 List<MyBean> list=new ArrayList<>(); 25 String[] userName=getResources().getStringArray(R.array.NameList); 26 TypedArray typedArray =getResources().obtainTypedArray(R.array.imageID); 27 int[] imageID=new int[typedArray.length()]; 28 for(int i=0;i< typedArray.length();i++){ 29 imageID[i]=typedArray.getResourceId(i,0); 30 } 31 typedArray.recycle(); 32 for(int i=0;i<imageID.length;i++){ 33 list.add(new MyBean(imageID[i],userName[i])); 34 } 35 return list; 36 } 37 38 @Override 39 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 40 /*使用自定义的Adapter后,需要修改点击后的事件,重新设置监听器 41 * *具体的响应事件可根据需求修改 42 */ 43 MyBean data= adapter2.getmFilteredList().get(position);//data为筛选后的被点击的项 44 auto_tv2.setText(data.getUserName()); 45 } 46 }