1. 基本原理:SearchView是android中一个搜索框组件,它不是一个单独的view,而是一个LinearLayout布局,包括表示Search图标和清除图标等其它图标的imageView、具有下拉建议列表的AutoCompleteTextView等,有时候我们需要对SearchView做一些特定的修改,而SearchView本身却又没有提供相关的接口,这时需要对SearchView进行自顶向下的深度遍历,提取它的每一个子孙view,如果只要对特定类型的view进行修改,只需根据view的类型来提取(viewinstanceof XXXView),若要提取特定的某一个view,则需要知道该view的id号,可以从SearchView源代码找到。
search_view.xml:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/search_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
>
<!-- This is actually used for the badge icon *or* the badge label (or neither) -->
<TextView
android:id="@+id/search_badge"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:layout_marginBottom="2dip"
android:drawablePadding="0dip"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorPrimary"
android:visibility="gone"
/>
<ImageView
android:id="@+id/search_button"
style="?android:attr/actionButtonStyle"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:src="?android:attr/searchViewSearchIcon"
android:contentDescription="@string/searchview_description_search"
/>
<LinearLayout
android:id="@+id/search_edit_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:layout_marginTop="4dip"
android:layout_marginBottom="4dip"
android:layout_marginLeft="8dip"
android:layout_marginRight="8dip"
android:orientation="horizontal">
<ImageView
android:id="@+id/search_mag_icon"
android:layout_width="@dimen/dropdownitem_icon_width"
android:layout_height="wrap_content"
android:scaleType="centerInside"
android:layout_marginLeft="@dimen/dropdownitem_text_padding_left"
android:layout_gravity="center_vertical"
android:src="?android:attr/searchViewSearchIcon"
android:visibility="gone"
/>
<!-- Inner layout contains the app icon, button(s) and EditText -->
<LinearLayout
android:id="@+id/search_plate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:orientation="horizontal"
android:background="?android:attr/searchViewTextField">
<view class="android.widget.SearchView$SearchAutoComplete"
android:id="@+id/search_src_text"
android:layout_height="36dip"
android:layout_width="0dp"
android:layout_weight="1"
android:minWidth="@dimen/search_view_text_min_width"
android:layout_gravity="bottom"
android:paddingLeft="@dimen/dropdownitem_text_padding_left"
android:paddingRight="@dimen/dropdownitem_text_padding_right"
android:singleLine="true"
android:ellipsize="end"
android:background="@null"
android:inputType="text|textAutoComplete|textNoSuggestions"
android:imeOptions="actionSearch"
android:dropDownHeight="wrap_content"
android:dropDownAnchor="@id/search_edit_frame"
android:dropDownVerticalOffset="0dip"
android:dropDownHorizontalOffset="0dip"
android:contentDescription="@string/searchview_description_query"
/>
<ImageView
android:id="@+id/search_close_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingLeft="8dip"
android:paddingRight="8dip"
android:layout_gravity="center_vertical"
android:background="?android:attr/selectableItemBackground"
android:src="?android:attr/searchViewCloseIcon"
android:focusable="true"
android:contentDescription="@string/searchview_description_clear"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/submit_area"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?android:attr/searchViewTextFieldRight">
<ImageView
android:id="@+id/search_go_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:paddingLeft="16dip"
android:paddingRight="16dip"
android:background="?android:attr/selectableItemBackground"
android:src="?android:attr/searchViewGoIcon"
android:visibility="gone"
android:focusable="true"
android:contentDescription="@string/searchview_description_submit"
/>
<ImageView
android:id="@+id/search_voice_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:paddingLeft="16dip"
android:paddingRight="16dip"
android:src="?android:attr/searchViewVoiceIcon"
android:background="?android:attr/selectableItemBackground"
android:visibility="gone"
android:focusable="true"
android:contentDescription="@string/searchview_description_voice"
/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
<h2>2. 我的使用格式:</h2><div> 根据我自己百度的资料,searchview有两种使用方式:1:放置到actionBar的位置。2:直接放置到布局中。自己跑了两个程序,感觉searchview放哪里都一样。</div><div> <strong>下面以放置到布局为例:</strong></div><div> 程序功能:实现一个SD卡的浏览器功能。listview中的数据就是当前目录下所有文件的(图标+文件名)。在搜索框输入搜索文本,listview内的数据刷新,只显示含义搜索文本的条目。(程序有点小bug,部分功能没实现,但能够说明searchview的大多数的基本用法)</div><div> 下面的代码copy到程序就可跑(文件名字有需要自己改动的地方:布局代码文件的文件名和主程序的文件名,图片资源需你自己更换一下)</div><div> 布局代码:<pre style="font-size: 9pt; color: rgb(169, 183, 198); font-family: 宋体; background-color: rgb(43, 43, 43);"><span style="color: rgb(152, 118, 170); "><em>activity_sdpathbrowse1.xml</em></span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(240, 240, 240);"> </span>
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <RelativeLayout android:layout_width="match_parent" android:layout_height="45dp" android:background="#000000"> <ImageView android:id="@+id/iv_back" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:padding="10dp" android:src="@mipmap/back" /> <TextView android:id="@+id/tv_path" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_toRightOf="@+id/iv_back" android:text="555555555555555555" android:textColor="#ffffff" /> </RelativeLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="60dp" android:background="#f2f2f2f2" android:orientation="vertical"> <SearchView android:id="@+id/sv_" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="8dp" android:background="@drawable/shape_bg_search" android:imeOptions="actionSearch" android:inputType="text" android:queryHint="请输入您要查找的内容" tools:ignore="NewApi" /> </LinearLayout> <ListView android:id="@+id/lv_list" android:layout_width="match_parent" android:layout_height="wrap_content"></ListView> </LinearLayout>
主程序代码:
package com.SDPathBrowse; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.os.Environment; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.BaseAdapter; import android.widget.Filter; import android.widget.Filterable; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.SearchView; import android.widget.TextView; import android.widget.Toast; import android.view.LayoutInflater; import android.view.inputmethod.InputMethodManager; import engineering.R; /** * @version 1.0 */ @SuppressLint("NewApi") public class SDPathBrowse1 extends Activity implements SearchView.OnQueryTextListener { //显示本路径下所有的文件夹和文件. ListView listView; //标题头,显示当前路径. TextView textView; // 记录当前的父文件夹 File currentParent; // 记录当前路径下的所有文件的文件数组 File[] currentFiles; //返回图标 ImageView parent; SearchView sv_; //listItems存储listview要加载的数据,根据serachView的输入,listView加载部分数据或全部数据. List<Map<String, Object>> listItems; //tmplistItems是listItem全部数据的拷贝。 List<Map<String, Object>> tmplistItems; MyAdapter myAdapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sdpathbrowse1); initView(); initData(); initListener(); } public void initView() { // 获取列出全部文件的ListView listView = (ListView) findViewById(R.id.lv_list); listView.setTextFilterEnabled(true); //标题头,显示当前路径. textView = (TextView) findViewById(R.id.tv_path); // 返回上一级目录的 图标 parent = (ImageView) findViewById(R.id.iv_back); sv_ = (SearchView) findViewById(R.id.sv_); //searchView输入后 右面有搜索按钮.false则没有. sv_.setSubmitButtonEnabled(false); //搜索图标是否在输入框中 sv_.setIconifiedByDefault(true); // 创建一个List集合,List集合的元素是Map listItems = new ArrayList<Map<String, Object>>(); tmplistItems = new ArrayList<Map<String, Object>>(); //用到的数据集合(listItems)在本类中是全局变量,adapter类写到了同一个文件中。若不在同一个文件中,adapter的构造函数需传递数据集合,用于本文件的逻辑就需要改变。 myAdapter = new MyAdapter(this); listView.setAdapter(myAdapter); } public void initData() { // 获取系统的SD卡的目录,两段代码都可。 //File root = new File("/mnt/sdcard/"); File root = Environment.getExternalStorageDirectory(); Log.i("lyw", root.toString());//storage/emulated/0 // 如果 SD卡存在 if (root.exists()) { currentParent = root; //获取当前文件夹下的所有文件和文件夹 currentFiles = root.listFiles(); trimCurrentFiles(currentFiles); } } @SuppressLint("NewApi") public void initListener() { // 为ListView的列表项的单击事件绑定监听器 listView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // 用户单击了文件,直接返回,不做任何处理 if (currentFiles[position].isFile()) { //此处可做对文件的各种操作,如对MP3的播放,图片的预览(预览界面可滑动),文本的打开等。 return; } // 获取用户点击的文件夹下的所有文件 File[] tmp = currentFiles[position].listFiles(); if (tmp == null || tmp.length == 0) { Toast.makeText(SDPathBrowse1.this , "当前路径不可访问或该路径下没有文件", Toast.LENGTH_SHORT).show(); } else { // 获取用户单击的列表项对应的文件夹,设为当前的父文件夹 currentParent = currentFiles[position]; // 保存当前的父文件夹内的全部文件和文件夹(即将临时tmp赋值给currentFiles). currentFiles = tmp; trimCurrentFiles(currentFiles); } } }); parent.setOnClickListener(new OnClickListener() { @Override public void onClick(View source) { try { String currentPath = currentParent.getCanonicalPath(); Log.i("lyw", currentPath); if (!currentParent.getCanonicalPath()//返回规范路径名 .equals("/")) { // 获取上一级目录 currentParent = currentParent.getParentFile(); // 列出当前目录下所有文件 currentFiles = currentParent.listFiles(); trimCurrentFiles(currentFiles); } } catch (IOException e) { e.printStackTrace(); } } }); //为searchview设置监听事件 sv_.setOnQueryTextListener(this); } /** * 初始化,返回上一级路径,进入下一级路径, * 整理数据,放置到listItems中。然后adapter.notifyDataSetChanged(). */ private void trimCurrentFiles(File[] files) { listItems.clear(); Map<String, Object> listItem; for (int i = 0; i < files.length; i++) { listItem = new HashMap<String, Object>(); // 如果当前File是文件夹,使用folder图标;否则使用file图标 if (files[i].isDirectory()) { listItem.put("icon", R.mipmap.folder); } else { listItem.put("icon", R.mipmap.file); } listItem.put("fileName", files[i].getName()); // 添加List项 listItems.add(listItem); } tmplistItems.addAll(listItems); myAdapter.notifyDataSetChanged();//每次listItems中的数据改变,通知adapter. try { textView.setText("当前路径为:" + currentParent.getCanonicalPath()); } catch (IOException e) { e.printStackTrace(); } } @Override public boolean onQueryTextChange(String s) { //当搜索文字发生改变,此处执行的代码与activity_searchView相应位置执行的代码做比对,同一功能两种不同的实现。 if (TextUtils.isEmpty(s)) { listView.clearTextFilter(); } else { listView.setFilterText(s.toString()); } return true; } //单击搜索按钮时激发该方法 @Override public boolean onQueryTextSubmit(String s) { return true; } public class MyAdapter extends BaseAdapter implements Filterable { private MyFilter myFilter; // private List<Map<String, Object>> list_Items; private LayoutInflater mInflater; public ImageView iv; public TextView tv; Bitmap bitmap; public MyAdapter(Context c) { mInflater = (LayoutInflater) c.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @Override public int getCount() { if (listItems == null) return 0; return listItems.size(); } @Override public View getView(int position, View convertView, ViewGroup Parent) { convertView = mInflater.inflate(R.layout.activity_sdpathbrowse1_item, null); iv = (ImageView) convertView.findViewById(R.id.iv_folder); tv = (TextView) convertView.findViewById(R.id.tv_filename); bitmap = BitmapFactory.decodeResource(getResources(), (Integer) listItems.get(position).get("icon")); iv.setImageBitmap(bitmap); tv.setText((CharSequence) listItems.get(position).get("fileName")); return convertView; } @Override public long getItemId(int position) { return position; } @Override public Object getItem(int position) { return listItems.get(position); } @Override public Filter getFilter() { if (null == myFilter) { myFilter = new MyFilter(); } return myFilter; } class MyFilter extends Filter { @Override // 该方法在子线程中执行 // 自定义过滤规则 protected FilterResults performFiltering(CharSequence charSequence) { FilterResults results = new FilterResults(); List<Map<String, Object>> newValues = new ArrayList<Map<String, Object>>(); String filterString = charSequence.toString().trim(); if (TextUtils.isEmpty(filterString)) { newValues = tmplistItems; } else { for (int i = 0; i < tmplistItems.size(); i++) { String str = (String) tmplistItems.get(i).get("fileName"); if (-1 != str.indexOf(filterString)) { newValues.add(tmplistItems.get(i)); } } } results.values = newValues; results.count = newValues.size(); return results; } @Override protected void publishResults(CharSequence charSequence, FilterResults filterResults) { listItems = (List<Map<String, Object>>) filterResults.values; if (filterResults.count > 0) { myAdapter.notifyDataSetChanged(); } else { myAdapter.notifyDataSetInvalidated(); } } } } }
架构 简单的说明:
public class SDPathBrowse1 extends Activity implements SearchView.OnQueryTextListener
本类implement SearchView.OnQueryTextListener
然后实现接口中未实现的方法:
public boolean onQueryTextChange(String s) {if (TextUtils.isEmpty(s)) { listView.clearTextFilter(); } else { listView.setFilterText(s.toString()); } return true;}public boolean onQueryTextSubmit(String s) {}在onQueryTextChange()中,listView调用setFilterText()方法。此方法是listView通过适配中getFilter()方法得到的。所以上面的MyAdapter类实现了 Filterable接口。
MyFilter继承Filter 作为MyAdapter的内部类。
至此,这就是整个程序的逻辑架构。